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} + ) : ( +
+ )} + {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} - ) : ( -
- )} - {nickName} -
+ - {leaveAndChatActions && ( + {shouldShowButtons && ( )}
@@ -127,7 +112,7 @@ const Card = ({ progress={progress} /> - {leaveAndChatActions && ( + {shouldShowButtons && (