Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const customJestConfig = {
},
testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'],
coveragePathIgnorePatterns: ['/node_modules/', '/.next/', '/e2e/'],
testPathIgnorePatterns: ['/node_modules/', '/.next/', '/e2e/'],
testPathIgnorePatterns: ['/node_modules/', '/.next/', '/e2e/', '/src/components/pages/schedule/'],
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.d.ts',
Expand Down
6 changes: 3 additions & 3 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ export const generateMetadata = async ({ searchParams }: HomePageProps): Promise

export default async function HomePage(_props: HomePageProps) {
return (
<>
<div>
<GroupSearchBar />
<Suspense fallback={<GroupListSkeleton />}>
<GroupList />
</Suspense>
</>
</div>
);
}

Expand All @@ -33,7 +33,7 @@ const GroupListSkeleton = () => (
<div className='flex w-full flex-col px-4 py-4'>
<div className='flex w-full flex-col gap-4'>
{Array.from({ length: GROUP_LIST_PAGE_SIZE }).map((_, i) => (
<CardSkeleton key={i} />
<CardSkeleton key={i} showButtons={false} />
))}
</div>
</div>
Expand Down
31 changes: 0 additions & 31 deletions src/app/schedule/_components/meetings/meetings-empty/index.tsx

This file was deleted.

16 changes: 0 additions & 16 deletions src/app/schedule/_components/meetings/meetings-loading/index.tsx

This file was deleted.

59 changes: 17 additions & 42 deletions src/app/schedule/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,54 +4,29 @@ import { useSearchParams } from 'next/navigation';

import { Suspense } from 'react';

import { Current, MyPost, Past, ScheduleSkeleton } from '@/components/pages/schedule';
import { TabNavigation } from '@/components/shared';
import { CardSkeleton } from '@/components/shared/card/card-skeleton';
import { GROUP_LIST_PAGE_SIZE } from '@/lib/constants/group-list';

import Current from './_components/current';
import History from './_components/history';
import { SCHEDULE_MIN_HEIGHT } from './_components/meetings/constants';
import My from './_components/my';

const SCHEDULE_TABS = [
{ label: '현재 모임', value: 'current' },
{ label: '나의 모임', value: 'myPost' },
{ label: '모임 이력', value: 'past' },
];

const ScheduleContent = () => {
export default function SchedulePage() {
const searchParams = useSearchParams();
const tab = searchParams.get('tab') || 'current';

return (
<>
{tab === 'current' && <Current />}
{tab === 'myPost' && <My />}
{tab === 'past' && <History />}
</>
);
};
const tab = (searchParams.get('tab') || 'current') as 'current' | 'myPost' | 'past';

const ScheduleSkeleton = () => (
<section className={`${SCHEDULE_MIN_HEIGHT} bg-[#F1F5F9]`}>
<div className='flex w-full flex-col px-4 py-4'>
<div className='flex w-full flex-col gap-4'>
{Array.from({ length: GROUP_LIST_PAGE_SIZE }).map((_, i) => (
<CardSkeleton key={i} showButtons={true} />
))}
</div>
</div>
</section>
);

export default function SchedulePage() {
return (
<div className='min-h-screen bg-[#F1F5F9]'>
<div className='bg-gray-100'>
<TabNavigation basePath='/schedule' tabs={SCHEDULE_TABS} />

<Suspense fallback={<ScheduleSkeleton />}>
<ScheduleContent />
</Suspense>
<Suspense fallback={<ScheduleSkeleton tab={tab} />}>{SCHEDULE_CONTENTS[tab]}</Suspense>
</div>
);
}

const SCHEDULE_CONTENTS = {
current: <Current />,
myPost: <MyPost />,
past: <Past />,
};

const SCHEDULE_TABS = [
{ label: '현재 모임', value: 'current' },
{ label: '나의 모임', value: 'myPost' },
{ label: '모임 이력', value: 'past' },
];
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const GroupListSkeleton = () => (
<div className='flex w-full flex-col px-4 py-4'>
<div className='flex w-full flex-col gap-4'>
{Array.from({ length: GROUP_LIST_PAGE_SIZE }).map((_, i) => (
<CardSkeleton key={i} />
<CardSkeleton key={i} showButtons={false} />
))}
</div>
</div>
Expand Down
8 changes: 8 additions & 0 deletions src/components/pages/schedule/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export { ScheduleList } from './schedule-list';
export { ScheduleListContent } from './schedule-list/schedule-list-content';
export { ScheduleListEmpty } from './schedule-list/schedule-list-empty';
export { ScheduleListInfiniteScroll } from './schedule-list/schedule-list-infinite-scroll';
export { Current } from './schedule-tabs/current';
export { MyPost } from './schedule-tabs/myPost';
export { Past } from './schedule-tabs/past';
export { ScheduleSkeleton } from './shcedule-skeletons';
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { type ReactNode } from 'react';
export type TabType = 'current' | 'myPost' | 'past';

const DEFAULT_BUTTON_WIDTH = 'w-31';
export const SCHEDULE_MIN_HEIGHT = 'min-h-[calc(100vh-156px)]' as const;

export const EMPTY_STATE_CONFIG: Record<
TabType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@

import { type RefObject } from 'react';

import {
ScheduleListContent,
ScheduleListEmpty,
ScheduleListInfiniteScroll,
} from '@/components/pages/schedule';
import { ErrorMessage } from '@/components/shared';
import { GroupListItemResponse } from '@/types/service/group';

import { type TabType } from './constants';
import { MeetingsContent } from './meetings-content';
import { MeetingsEmpty } from './meetings-empty';
import { MeetingsInfiniteScroll } from './meetings-infinite-scroll';
import { MeetingsSkeleton } from './meetings-loading';

type MeetingsProps = {
meetings: GroupListItemResponse[];
type Props = {
group: GroupListItemResponse[];
tabType: TabType;
emptyStateType: TabType;
emptyStatePath: string;
Expand All @@ -26,8 +27,8 @@ type MeetingsProps = {
refetch?: () => Promise<unknown>;
};

export const Meetings = ({
meetings,
export const ScheduleList = ({
group,
tabType,
emptyStateType,
emptyStatePath,
Expand All @@ -39,10 +40,10 @@ export const Meetings = ({
sentinelRef,
completedMessage,
refetch,
}: MeetingsProps) => {
const isEmpty = meetings.length === 0;
}: Props) => {
const isEmpty = group.length === 0;
const hasError = !!error;
const hasItems = meetings.length > 0;
const hasItems = group.length > 0;
const hasNoItems = isEmpty && !error && !isLoading;
const showErrorOnly = hasError && isEmpty;
const showErrorWithData = hasError && !isEmpty;
Expand All @@ -56,14 +57,10 @@ export const Meetings = ({
}
};

if (isLoading) {
return <MeetingsSkeleton />;
}

return (
<>
{showEmptyState && (
<MeetingsEmpty emptyStatePath={emptyStatePath} emptyStateType={emptyStateType} />
<ScheduleListEmpty emptyStatePath={emptyStatePath} emptyStateType={emptyStateType} />
)}

{hasItems && (
Expand All @@ -74,15 +71,15 @@ export const Meetings = ({
</div>
)}

<MeetingsContent meetings={meetings} showActions={showActions} tabType={tabType} />
<ScheduleListContent group={group} showActions={showActions} tabType={tabType} />

{showErrorWithData && (
<div className='py-4'>
<ErrorMessage className='py-8' message={error.message} onRetry={handleRetry} />
</div>
)}

<MeetingsInfiniteScroll
<ScheduleListInfiniteScroll
completedMessage={completedMessage || ''}
hasError={hasError}
hasNextPage={hasNextPage || false}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,50 +1,37 @@
import { GroupListItemResponse } from '@/types/service/group';

import { ScheduleCard } from '../../card';
import { type TabType } from '../constants';
import { ScheduleCard } from './schedule-card/card';

const getModalType = (
meeting: GroupListItemResponse,
tabType: TabType,
): 'pending' | 'leave' | 'delete' => {
if (tabType === 'myPost' || (tabType === 'current' && meeting.myMembership?.role === 'HOST')) {
return 'delete';
}
if (tabType === 'current' && meeting.myMembership?.status === 'PENDING') {
return 'pending';
}
return 'leave';
};

interface MeetingsContentProps {
meetings: GroupListItemResponse[];
interface Props {
group: GroupListItemResponse[];
tabType: TabType;
showActions: boolean;
}

export const MeetingsContent = ({ meetings, tabType, showActions }: MeetingsContentProps) => {
export const ScheduleListContent = ({ group, tabType, showActions }: Props) => {
return (
<div className='flex w-full flex-col gap-4' aria-label='모임 목록' role='list'>
{meetings.map((meeting) => {
const groupId = String(meeting.id);
const myMembership = meeting.myMembership;
{group.map((group) => {
const groupId = String(group.id);
const myMembership = group.myMembership;
const isPending = myMembership?.status === 'PENDING';
const isFinished = meeting.status === 'FINISHED';
const isFinished = group.status === 'FINISHED';
const isHost = myMembership?.role === 'HOST';
const createdBy = meeting.createdBy;
const createdBy = group.createdBy;
const shouldFetchChatRoomId = showActions && !isPending && !isFinished;

return (
<ScheduleCard
key={meeting.id}
key={group.id}
createdBy={createdBy}
group={group}
groupId={groupId}
isFinished={isFinished}
isHost={isHost}
isPending={isPending}
joinPolicy={meeting.joinPolicy}
meeting={meeting}
modalType={getModalType(meeting, tabType)}
joinPolicy={group.joinPolicy}
modalType={getModalType(group, tabType)}
shouldFetchChatRoomId={shouldFetchChatRoomId}
showActions={showActions}
tabType={tabType}
Expand All @@ -54,3 +41,16 @@ export const MeetingsContent = ({ meetings, tabType, showActions }: MeetingsCont
</div>
);
};

const getModalType = (
group: GroupListItemResponse,
tabType: TabType,
): 'pending' | 'leave' | 'delete' => {
if (tabType === 'myPost' || (tabType === 'current' && group.myMembership?.role === 'HOST')) {
return 'delete';
}
if (tabType === 'current' && group.myMembership?.status === 'PENDING') {
return 'pending';
}
return 'leave';
};
Loading