Skip to content

Commit 22a9fa8

Browse files
authored
Merge pull request #162 from GDGoCINHA/develop
Fix: 25-1 문구 25-2 로 수정
2 parents 1254f7e + 93b35fc commit 22a9fa8

File tree

8 files changed

+2698
-68
lines changed

8 files changed

+2698
-68
lines changed

package-lock.json

Lines changed: 1558 additions & 64 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@
2828
"react-dom": "^18.2.0",
2929
"react-error-boundary": "^6.0.0",
3030
"react-icons": "^5.4.0",
31+
"react-markdown": "^10.1.0",
3132
"recharts": "^3.1.2",
33+
"remark-breaks": "^4.0.0",
34+
"remark-gfm": "^4.0.1",
3235
"type-hangul": "^0.2.4"
3336
},
3437
"devDependencies": {

src/app/notice/page.jsx

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
'use client';
2+
3+
import { useMemo, useState } from 'react';
4+
import NoticeCard from '@/components/notice/NoticeCard';
5+
import NoticeModal from '@/components/notice/NoticeModal';
6+
import { notices, NOTICE_CATEGORIES, NOTICE_STATUSES } from '@/mock/notices';
7+
8+
export default function NoticePage() {
9+
const [category, setCategory] = useState('all');
10+
const [status, setStatus] = useState('all');
11+
const [selected, setSelected] = useState(null);
12+
const [loading, setLoading] = useState(false);
13+
14+
const filtered = useMemo(() => {
15+
return notices.filter((n) => {
16+
const categoryOk = category === 'all' || n.category === category;
17+
const statusOk = status === 'all' || n.status === status;
18+
return categoryOk && statusOk;
19+
});
20+
}, [category, status]);
21+
22+
if (!loading) {
23+
return (
24+
<div className="flex flex-col items-center justify-center min-h-[60vh] text-white">
25+
<h2 className="text-2xl font-bold mb-2">Error</h2>
26+
</div>
27+
);
28+
}
29+
30+
return (
31+
<div className="px-6 tablet:px-10 desktop:px-16 py-10">
32+
{/* Hero */}
33+
<div className="max-w-screen-xl mx-auto text-white">
34+
<h1 className="text-3xl font-extrabold">GDGoC INHA에서 진행중인 프로젝트 둘러보기</h1>
35+
<p className="text-gray-300 mt-2">개발자 커뮤니티의 공지, 행사, 프로젝트 소식을 확인하세요.</p>
36+
</div>
37+
38+
{/* Filter Bar */}
39+
<div className="sticky top-0 z-10 mt-6 bg-[#0f0f0f]/80 backdrop-blur supports-[backdrop-filter]:bg-[#0f0f0f]/60 border-b border-white/5">
40+
<div className="max-w-screen-xl mx-auto flex items-center justify-between gap-4 py-3 px-1">
41+
<div className="flex gap-2 overflow-x-auto">
42+
{NOTICE_CATEGORIES.map((c) => (
43+
<button
44+
key={c.key}
45+
onClick={() => setCategory(c.key)}
46+
className={`px-4 h-9 rounded-full whitespace-nowrap text-sm font-medium ${category === c.key ? 'bg-white text-black' : 'bg-[#1e1e1e] text-gray-200 hover:bg-[#2a2a2a]'}`}
47+
>
48+
{c.label}
49+
</button>
50+
))}
51+
</div>
52+
<div className="flex gap-2 items-center">
53+
{NOTICE_STATUSES.map((s) => (
54+
<button
55+
key={s.key}
56+
onClick={() => setStatus(s.key)}
57+
className={`px-3 h-9 rounded-full text-sm font-medium flex items-center gap-2 ${status === s.key ? 'bg-white text-black' : 'bg-[#1e1e1e] text-gray-200 hover:bg-[#2a2a2a]'}`}
58+
>
59+
{s.key !== 'all' && (
60+
<span className={`w-2 h-2 rounded-full ${s.key === 'ongoing' ? 'bg-green-500' : 'bg-red-500'}`} />
61+
)}
62+
{s.label}
63+
</button>
64+
))}
65+
</div>
66+
</div>
67+
</div>
68+
69+
{/* Grid */}
70+
<div className="max-w-screen-xl mx-auto mt-8 grid gap-6 grid-cols-1 tablet:grid-cols-2 desktop:grid-cols-3">
71+
{filtered.map((item) => (
72+
<NoticeCard key={item.id} item={item} onClick={setSelected} />
73+
))}
74+
</div>
75+
76+
<NoticeModal item={selected} onClose={() => setSelected(null)} />
77+
</div>
78+
);
79+
}
80+
81+
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use client';
2+
3+
import Image from 'next/image';
4+
5+
export default function NoticeCard({ item, onClick }) {
6+
const statusDot = item.status === 'ongoing' ? 'bg-green-500' : 'bg-red-500';
7+
8+
return (
9+
<button
10+
onClick={() => onClick?.(item)}
11+
className="text-left bg-[#1f1f1f] rounded-2xl overflow-hidden hover:shadow-lg transition-transform hover:-translate-y-0.5 focus:outline-none focus:ring-2 focus:ring-green-500"
12+
>
13+
<div className="relative aspect-video w-full bg-[#2a2a2a]">
14+
{item.image && (
15+
<Image src={item.image} alt={item.title} fill className="object-cover" />
16+
)}
17+
</div>
18+
<div className="p-4 flex flex-col gap-y-2">
19+
<h3 className="text-white font-bold text-lg line-clamp-2">{item.title}</h3>
20+
<p className="text-gray-300 text-sm line-clamp-1">{item.summary}</p>
21+
<div className="flex items-center justify-between mt-2">
22+
<div className="flex gap-2">
23+
{item.tags?.map((t) => (
24+
<span key={t} className="text-xs px-2 py-0.5 rounded-full bg-[#2b2b2b] text-gray-200">#{t}</span>
25+
))}
26+
</div>
27+
<div className="flex items-center gap-1 text-xs text-gray-200">
28+
<span className={`inline-block w-2 h-2 rounded-full ${statusDot}`} />
29+
<span>{item.status === 'ongoing' ? '진행중' : '종료'}</span>
30+
</div>
31+
</div>
32+
</div>
33+
</button>
34+
);
35+
}
36+
37+
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
'use client';
2+
3+
import Image from 'next/image';
4+
import ReactMarkdown from 'react-markdown';
5+
import remarkGfm from 'remark-gfm';
6+
import remarkBreaks from 'remark-breaks';
7+
import { Modal, ModalContent, ModalHeader, ModalBody } from '@nextui-org/react';
8+
9+
export default function NoticeModal({ item, onClose }) {
10+
const isOpen = !!item;
11+
12+
return (
13+
<Modal isOpen={isOpen} onOpenChange={(o) => { if (!o) onClose?.(); }} size="5xl" backdrop="blur" className="bg-[#1b1b1b] text-white">
14+
<ModalContent className="bg-[#1b1b1b] text-white max-h-[85vh] overflow-y-auto">
15+
{() => (
16+
<div className="flex flex-col">
17+
<ModalHeader className="px-6 pb-0 pt-6">
18+
<h3 className="text-2xl font-bold">{item?.title}</h3>
19+
</ModalHeader>
20+
21+
{/* 이미지: 세로 최대 높이 제한 */}
22+
<div className="px-6 pt-4">
23+
<div className="relative w-full h-[36vh] md:h-[42vh] rounded-xl overflow-hidden">
24+
{item?.image && <Image src={item.image} alt={item.title} fill className="object-cover" />}
25+
</div>
26+
</div>
27+
28+
{/* 상세 설명 | 소개글 */}
29+
<ModalBody className="px-6 pb-6">
30+
<div className="grid md:grid-cols-2 gap-8">
31+
{/* 좌: 상세 설명(요약 카드 + 정보) */}
32+
<div>
33+
<h4 className="font-semibold mb-2">상세 설명</h4>
34+
<div className="bg-[#222] rounded-xl p-4 space-y-4">
35+
<p className="text-gray-200 leading-7">{item?.summary}</p>
36+
<div className="border-t border-white/10 pt-3 text-sm space-y-2">
37+
<div className="flex"><span className="w-20 text-gray-300">기간</span><span className="text-gray-100">{item?.details?.period || '-'}</span></div>
38+
<div className="flex"><span className="w-20 text-gray-300">모집</span><span className="text-gray-100">{item?.details?.recruitment || '-'}</span></div>
39+
<div className="flex"><span className="w-20 text-gray-300">일정</span><span className="text-gray-100">{item?.details?.schedule || '-'}</span></div>
40+
<div className="flex"><span className="w-20 text-gray-300">장소</span><span className="text-gray-100">{item?.details?.location || '-'}</span></div>
41+
<div className="flex"><span className="w-20 text-gray-300">링크</span><a href={item?.details?.link || '#'} target="_blank" rel="noreferrer" className="text-blue-400 break-all hover:underline">{item?.details?.link || '-'}</a></div>
42+
</div>
43+
</div>
44+
</div>
45+
{/* 우: 소개글(줄글 Markdown) */}
46+
<div>
47+
<h4 className="font-semibold mb-2">소개글</h4>
48+
{item?.md ? (
49+
<div className="prose prose-invert max-w-none">
50+
<ReactMarkdown
51+
remarkPlugins={[remarkGfm, remarkBreaks]}
52+
components={{
53+
a: ({node, ...props}) => (
54+
<a {...props} target="_blank" rel="noopener noreferrer" />
55+
)
56+
}}
57+
>
58+
{item.md}
59+
</ReactMarkdown>
60+
</div>
61+
) : (
62+
<p className="text-gray-200 whitespace-pre-line leading-7">{item?.summary}</p>
63+
)}
64+
</div>
65+
</div>
66+
</ModalBody>
67+
</div>
68+
)}
69+
</ModalContent>
70+
</Modal>
71+
);
72+
}
73+
74+
75+
76+

src/components/recruit/screen/Recruit11.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export default function Recruit11({ step, setChecked, updateRecruitData }) {
4242
<strong> GDGoC INHA</strong>
4343
</li>
4444

45-
<li className='text-base font-semibold'>• 💵 25-1 회비</li>
45+
<li className='text-base font-semibold'>• 💵 25-2 회비</li>
4646
<li className='ml-[8px] mb-[15px]'>
4747
: <strong className='text-[#F9AB00]'>20,000</strong>
4848
</li>

src/mock/notices.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
'use client';
2+
3+
import buildwithai from '@public/images/activity/buildwithai.png';
4+
import seminar from '@public/images/activity/seminar.jpg';
5+
import conf from '@public/images/activity/conf.jpg';
6+
7+
// 카테고리: notice | event | project
8+
// 상태: ongoing | closed
9+
export const notices = [
10+
{
11+
id: 'n-1',
12+
title: 'GDGoC INHA에서 진행중인 프로젝트 둘러보기',
13+
summary: '현재 진행 중인 웹/앱 프로젝트를 한눈에 확인하고 팀에 합류하세요.',
14+
category: 'notice',
15+
status: 'ongoing',
16+
image: buildwithai,
17+
tags: ['공지'],
18+
details: {
19+
period: '상시',
20+
recruitment: '-',
21+
schedule: '-',
22+
location: '-',
23+
link: '-'
24+
}
25+
},
26+
{
27+
id: 'n-2',
28+
title: 'GDGoC 25-2 신입 멤버 모집: 배우고, 만들고, 함께 성장해요! 🚀',
29+
summary: '인하대학교 유일의 Google 공식 IT 커뮤니티에서 새로운 멤버를 모집합니다.',
30+
category: 'notice',
31+
status: 'ongoing',
32+
image: buildwithai,
33+
tags: ['공지'],
34+
md: `인하대학교 유일의 Google 공식 IT 커뮤니티, **GDGoC INHA**에서 새로운 멤버를 모집합니다!\n\n개발 공부만으로는 뭔가 부족하다고 느끼셨나요?\nGDGoC에서는 **스터디로 배우고 → 프로젝트로 직접 만들고 → 해커톤과 연합 활동으로 성장**하는 경험까지, 한 학기 동안 차곡차곡 쌓아갈 수 있습니다.\n\n---\n\n## 🏷️ 우리가 함께하는 활동들\n\n### 1. 기초부터 차근차근, 정규 스터디 📚\n처음이라도 괜찮습니다. Python, 데이터 분석, AI, UX/UI 등 다양한 주제를 다루며, 팀원들과 같이 배우고 토론합니다.\n- 학기 중 두 차례 진행\n- 인프런 강의 지원 + 협력 학습\n\n### 2. 배운 걸 직접 활용하는 포트폴리오 프로젝트 💻\n한 학기 동안 팀을 이루어 **아이디어 기획 → 개발 → 배포 → 발표**까지!\n실제 서비스 개발 과정을 경험하며, **내 손으로 만든 결과물**을 남길 수 있습니다.\n\n### 3. 도전으로 성장하는 해커톤 🚀\n다양한 분야의 사람들과 짧은 시간에 함께 몰입해 아이디어를 구현하며 성장합니다.\n- DevSprint: 구글 스프린트 방식을 적용한 집중 개발 프로젝트\n- 글로벌/연합 해커톤: Solution Challenge, GreenTech Globalthon 등\n- 스타트업 비즈니스 솔루션 콘테스트: 개발자뿐 아니라 **창업·비즈니스·데이터**에 관심 있는 사람들이 함께 참여해, 문제 해결과 **사업화 아이디어**를 함께 만들어가는 특별한 기회\n\n---\n\n## 🏷️ GDGoC INHA만의 강점\n\n- ✅ **다양한 연합 네트워킹** – 인천내 대학 연합 ‘뭉(Moong)’, 타 GDGoC 지부, AIESEC·아이바스 등과의 협업으로 더 넓은 무대에서 사람들과 연결!\n- ✅ **내부 성장 프로그램** – 정규 스터디, 포트폴리오 프로젝트, 해커톤으로 실력+협업+커뮤니티 활동까지 한 번에!\n- ✅ **든든한 커뮤니티** – MT, e스포츠 대회, 한강 나들이, 크리스마스파티 등 다양한 친목 활동으로 언제든 함께할 동료!\n\n---\n\n## 🏷️ 우리는 이런 분을 기다립니다!\n\n- 개발자 혹은 개발에 관심 있는 분\n- 서비스 기획, UX/UI 디자인, 마케팅 등 다양한 분야에서 함께할 분 *(전공 무관, 이미 40여개 학과가 활동 중)*\n- 앞으로의 프로젝트에서 함께할 든든한 팀원을 찾고 싶은 분\n- 혼자보다는 함께 성장하고 싶은 분\n- 다양한 사람들과 네트워킹하며 시너지를 내고 싶은 분\n\n> ⭐ **Core Member(운영진) 모집**도 곧 시작됩니다. 관심 있는 분들은 눈여겨봐주세요!\n\n---\n\n## 📍 모집 정보\n- **집중 모집 기간**: 2025.08.29(금) ~ 2025.09.15(월)\n- **모집 링크**: https://gdgocinha.com/recruit\n- **개강 총회**: 2025.09.16(화) ✨필참✨\n- **동아리 방**: 5동 021\n\n### 문의\n- 회장: 박우찬 010-2087-1816\n- 부회장: 김정훈 010-4540-1432\n- 부회장: 최준빈 010-4252-8910\n\n- 인스타그램: @gdgoc.inha\n- 오픈채팅방 문의: https://open.kakao.com/o/sJCx9HNh\n- 더 알아보기: https://info.gdgocinha.com\n`,
35+
details: {
36+
period: '2025.08.29 ~ 2025.09.15',
37+
recruitment: '학부 재학생 누구나',
38+
schedule: '개강 총회 2025.09.16(화)',
39+
location: '인하대학교 5동 021',
40+
link: 'https://gdgocinha.com/recruit'
41+
}
42+
},
43+
{
44+
id: 'e-1',
45+
title: '정기 세미나 안내',
46+
summary: 'AI/웹 개발 주제로 진행하는 내부 세미나에 참여하세요.',
47+
category: 'event',
48+
status: 'ongoing',
49+
image: seminar,
50+
tags: ['행사'],
51+
details: {
52+
period: '2025.09.20',
53+
recruitment: '선착순 50명',
54+
schedule: '토 14:00~17:00',
55+
location: '인하대학교',
56+
link: 'https://forms.gle/'
57+
}
58+
},
59+
{
60+
id: 'p-1',
61+
title: '하우미 프로젝트',
62+
summary: '1인 가구를 위한 AI 인테리어 스타일링 서비스',
63+
category: 'project',
64+
status: 'closed',
65+
image: conf,
66+
tags: ['프로젝트'],
67+
details: {
68+
period: '2025.06 ~ 진행중',
69+
recruitment: '디자이너 1, FE 1',
70+
schedule: '주 1회',
71+
location: '인하대학교',
72+
link: '-'
73+
}
74+
}
75+
];
76+
77+
export const NOTICE_CATEGORIES = [
78+
{ key: 'all', label: '전체' },
79+
{ key: 'notice', label: '공지' },
80+
{ key: 'event', label: '행사' },
81+
{ key: 'project', label: '프로젝트' },
82+
];
83+
84+
export const NOTICE_STATUSES = [
85+
{ key: 'all', label: '전체' },
86+
{ key: 'ongoing', label: '진행중' },
87+
{ key: 'closed', label: '종료' },
88+
];
89+
90+

0 commit comments

Comments
 (0)