diff --git a/src/app/schedule/(components)/current.tsx b/src/app/schedule/(components)/current.tsx
index 08ac7bc1..bed97f88 100644
--- a/src/app/schedule/(components)/current.tsx
+++ b/src/app/schedule/(components)/current.tsx
@@ -1,10 +1,9 @@
'use client';
-import { useRouter } from 'next/navigation';
+import { MeetingList } from './meeting-list';
+import type { Meeting } from './types';
-import Card from '@/components/shared/card';
-
-const MOCK_MEETINGS = [
+const MOCK_MEETINGS: Meeting[] = [
{
id: 1,
title: '네즈코와 무한성에서 정모 하실 분',
@@ -14,33 +13,19 @@ const MOCK_MEETINGS = [
nickName: 'Hope Lee',
participantCount: 8,
maxParticipants: 10,
- tags: ['#태그', '#태그', '#태그'],
+ tags: ['#젠이츠', '#기유', '#네즈코'],
},
];
export default function Current() {
- const router = useRouter();
-
return (
-
- {MOCK_MEETINGS.map((meeting) => (
- console.log('모임 탈퇴', meeting.id),
- onChat: () => router.push(`/chat/${meeting.id}`),
- }}
- location={meeting.location}
- maxParticipants={meeting.maxParticipants}
- nickName={meeting.nickName}
- participantCount={meeting.participantCount}
- tags={meeting.tags}
- title={meeting.title}
- onClick={() => router.push(`/meetup/${meeting.id}`)}
- />
- ))}
-
+
);
}
diff --git a/src/app/schedule/(components)/empty-state.tsx b/src/app/schedule/(components)/empty-state.tsx
new file mode 100644
index 00000000..98c770a5
--- /dev/null
+++ b/src/app/schedule/(components)/empty-state.tsx
@@ -0,0 +1,51 @@
+'use client';
+
+import { Icon } from '@/components/icon';
+import { Button } from '@/components/ui';
+import { cn } from '@/lib/utils';
+
+type EmptyStateProps = {
+ type: 'current' | 'my' | 'history';
+ onButtonClick: () => void;
+};
+
+const EMPTY_STATE_CONFIG = {
+ current: {
+ text: '현재 참여 중인 모임이 없어요.\n지금 바로 모임을 참여해보세요!',
+ buttonText: '모임 보러가기',
+ buttonWidth: 'w-[124px]',
+ },
+ my: {
+ text: '아직 생성한 모임이 없어요.\n지금 바로 모임을 만들어보세요!',
+ buttonText: '모임 만들기',
+ buttonWidth: 'w-[112px]',
+ },
+ history: {
+ text: '아직 참여한 모임이 없어요.\n마음에 드는 모임을 발견해보세요!',
+ buttonText: '모임 보러가기',
+ buttonWidth: 'w-[124px]',
+ },
+} as const;
+
+export const EmptyState = ({ type, onButtonClick }: EmptyStateProps) => {
+ const config = EMPTY_STATE_CONFIG[type];
+
+ return (
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/app/schedule/(components)/history.tsx b/src/app/schedule/(components)/history.tsx
index ff6b0ec2..518b4037 100644
--- a/src/app/schedule/(components)/history.tsx
+++ b/src/app/schedule/(components)/history.tsx
@@ -1,46 +1,18 @@
'use client';
-import { useRouter } from 'next/navigation';
+import { MeetingList } from './meeting-list';
+import type { Meeting } from './types';
-import Card from '@/components/shared/card';
-
-const MOCK_MEETINGS = [
- {
- id: 3,
- title: '동네 책모임 신규 멤버 구함',
- images: [],
- location: '망원동 카페 거리',
- dateTime: '25. 12. 10 - 19:30',
- nickName: 'Book Lover',
- participantCount: 3,
- maxParticipants: 8,
- tags: ['#책모임', '#수다환영'],
- },
-];
+const MOCK_MEETINGS: Meeting[] = [];
export default function History() {
- const router = useRouter();
-
return (
-
- {MOCK_MEETINGS.map((meeting) => (
- console.log('모임 탈퇴', meeting.id),
- onChat: () => router.push(`/chat/${meeting.id}`),
- }}
- location={meeting.location}
- maxParticipants={meeting.maxParticipants}
- nickName={meeting.nickName}
- participantCount={meeting.participantCount}
- tags={meeting.tags}
- title={meeting.title}
- onClick={() => router.push(`/meetup/${meeting.id}`)}
- />
- ))}
-
+
);
}
diff --git a/src/app/schedule/(components)/meeting-list.tsx b/src/app/schedule/(components)/meeting-list.tsx
new file mode 100644
index 00000000..4f018362
--- /dev/null
+++ b/src/app/schedule/(components)/meeting-list.tsx
@@ -0,0 +1,60 @@
+'use client';
+
+import { useRouter } from 'next/navigation';
+
+import Card from '@/components/shared/card';
+
+import { EmptyState } from './empty-state';
+import type { Meeting, TabType } from './types';
+
+type MeetingListProps = {
+ meetings: Meeting[];
+ tabType: TabType;
+ emptyStateType: TabType;
+ emptyStatePath: string;
+ showActions: boolean;
+ leaveActionText?: string;
+};
+
+export const MeetingList = ({
+ meetings,
+ tabType,
+ emptyStateType,
+ emptyStatePath,
+ showActions,
+ leaveActionText,
+}: MeetingListProps) => {
+ const router = useRouter();
+
+ if (meetings.length === 0) {
+ return router.push(emptyStatePath)} />;
+ }
+
+ return (
+
+ {meetings.map((meeting) => (
+ console.log(leaveActionText || '모임 탈퇴', meeting.id),
+ onChat: () => router.push(`/chat/${meeting.id}`),
+ }
+ : undefined
+ }
+ location={meeting.location}
+ maxParticipants={meeting.maxParticipants}
+ nickName={meeting.nickName}
+ participantCount={meeting.participantCount}
+ tabType={tabType}
+ tags={meeting.tags}
+ title={meeting.title}
+ onClick={() => router.push(`/meetup/${meeting.id}`)}
+ />
+ ))}
+
+ );
+};
diff --git a/src/app/schedule/(components)/my.tsx b/src/app/schedule/(components)/my.tsx
index c650feda..f10c6d28 100644
--- a/src/app/schedule/(components)/my.tsx
+++ b/src/app/schedule/(components)/my.tsx
@@ -1,10 +1,9 @@
'use client';
-import { useRouter } from 'next/navigation';
+import { MeetingList } from './meeting-list';
+import type { Meeting } from './types';
-import Card from '@/components/shared/card';
-
-const MOCK_MEETINGS = [
+const MOCK_MEETINGS: Meeting[] = [
{
id: 2,
title: '주말 러닝 크루 모집',
@@ -19,28 +18,14 @@ const MOCK_MEETINGS = [
];
export default function My() {
- const router = useRouter();
-
return (
-
- {MOCK_MEETINGS.map((meeting) => (
- console.log('모임 탈퇴', meeting.id),
- onChat: () => router.push(`/chat/${meeting.id}`),
- }}
- location={meeting.location}
- maxParticipants={meeting.maxParticipants}
- nickName={meeting.nickName}
- participantCount={meeting.participantCount}
- tags={meeting.tags}
- title={meeting.title}
- onClick={() => router.push(`/meetup/${meeting.id}`)}
- />
- ))}
-
+
);
}
diff --git a/src/app/schedule/(components)/types.ts b/src/app/schedule/(components)/types.ts
new file mode 100644
index 00000000..bdf9537b
--- /dev/null
+++ b/src/app/schedule/(components)/types.ts
@@ -0,0 +1,13 @@
+export type Meeting = {
+ id: number;
+ title: string;
+ images: string[];
+ location: string;
+ dateTime: string;
+ nickName: string;
+ participantCount: number;
+ maxParticipants: number;
+ tags: string[];
+};
+
+export type TabType = 'current' | 'my' | 'history';
diff --git a/src/components/shared/card/card-profile/index.tsx b/src/components/shared/card/card-profile/index.tsx
new file mode 100644
index 00000000..5917628f
--- /dev/null
+++ b/src/components/shared/card/card-profile/index.tsx
@@ -0,0 +1,28 @@
+import Image from 'next/image';
+
+type CardProfileProps = {
+ nickName: string;
+ profileImage?: string | null;
+ size?: number;
+};
+
+const DEFAULT_SIZE = 16;
+
+export const CardProfile = ({ nickName, profileImage, size = DEFAULT_SIZE }: CardProfileProps) => {
+ return (
+
+ {profileImage ? (
+
+ ) : (
+
+ )}
+
{nickName}
+
+ );
+};
diff --git a/src/components/shared/card/index.tsx b/src/components/shared/card/index.tsx
index 8f89b35e..7771f113 100644
--- a/src/components/shared/card/index.tsx
+++ b/src/components/shared/card/index.tsx
@@ -1,7 +1,5 @@
'use client';
-import Image from 'next/image';
-
import { useState } from 'react';
import { Button } from '@/components/ui';
@@ -9,6 +7,7 @@ import { cn } from '@/lib/utils';
import { CardInfoRow } from './card-info-row';
import { CardParticipationRow } from './card-participation-row';
+import { CardProfile } from './card-profile';
import { type CardTag, CardTags } from './card-tags';
import { CardThumbnail } from './card-thumbnail';
@@ -27,10 +26,9 @@ type CardProps = {
onLeave: () => void;
onChat: () => void;
};
+ tabType?: 'current' | 'my' | 'history';
};
-const PROFILE_IMAGE_SIZE = 16;
-
const calculateProgress = (count: number, max: number): number => {
const safeMax = max > 0 ? max : 1;
const rawProgress = (count / safeMax) * 100;
@@ -53,6 +51,7 @@ const Card = ({
profileImage,
onClick,
leaveAndChatActions,
+ tabType,
}: CardProps) => {
const [imageError, setImageError] = useState(false);
@@ -60,6 +59,8 @@ const Card = ({
const hasThumbnail = !!thumbnail && !imageError;
const cardTags = convertToCardTags(tags);
const progress = calculateProgress(participantCount, maxParticipants);
+ const shouldShowButtons = leaveAndChatActions && tabType !== 'history';
+ const leaveButtonText = tabType === 'my' ? '모임 취소' : '모임 탈퇴';
return (
setImageError(true)}
/>
-
- {profileImage ? (
-
- ) : (
-
- )}
-
{nickName}
-
+
- {leaveAndChatActions && (
+ {shouldShowButtons && (
)}
@@ -127,7 +112,7 @@ const Card = ({
progress={progress}
/>
- {leaveAndChatActions && (
+ {shouldShowButtons && (