diff --git a/jest.config.js b/jest.config.js index c01b7b82..5eca0a2b 100644 --- a/jest.config.js +++ b/jest.config.js @@ -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', diff --git a/src/app/page.tsx b/src/app/page.tsx index abafb5ff..df75db7f 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -19,12 +19,12 @@ export const generateMetadata = async ({ searchParams }: HomePageProps): Promise export default async function HomePage(_props: HomePageProps) { return ( - <> +
}> - +
); } @@ -33,7 +33,7 @@ const GroupListSkeleton = () => (
{Array.from({ length: GROUP_LIST_PAGE_SIZE }).map((_, i) => ( - + ))}
diff --git a/src/app/schedule/_components/meetings/meetings-empty/index.tsx b/src/app/schedule/_components/meetings/meetings-empty/index.tsx deleted file mode 100644 index 13ec73e5..00000000 --- a/src/app/schedule/_components/meetings/meetings-empty/index.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { useRouter } from 'next/navigation'; - -import { EmptyState } from '@/components/layout/empty-state'; -import { Button } from '@/components/ui'; - -import { EMPTY_STATE_CONFIG, SCHEDULE_MIN_HEIGHT, type TabType } from '../constants'; - -interface MeetingsEmptyProps { - emptyStateType: TabType; - emptyStatePath: string; -} - -export const MeetingsEmpty = ({ emptyStateType, emptyStatePath }: MeetingsEmptyProps) => { - const router = useRouter(); - const config = EMPTY_STATE_CONFIG[emptyStateType]; - - const handleEmptyStateClick = () => router.push(emptyStatePath); - - return ( -
- {config.text} - - -
- ); -}; diff --git a/src/app/schedule/_components/meetings/meetings-loading/index.tsx b/src/app/schedule/_components/meetings/meetings-loading/index.tsx deleted file mode 100644 index a6ebd94e..00000000 --- a/src/app/schedule/_components/meetings/meetings-loading/index.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { CardSkeleton } from '@/components/shared/card/card-skeleton'; -import { GROUP_LIST_PAGE_SIZE } from '@/lib/constants/group-list'; - -import { SCHEDULE_MIN_HEIGHT } from '../constants'; - -export const MeetingsSkeleton = () => ( -
-
-
- {Array.from({ length: GROUP_LIST_PAGE_SIZE }).map((_, i) => ( - - ))} -
-
-
-); diff --git a/src/app/schedule/page.tsx b/src/app/schedule/page.tsx index b1cde478..0cff11dd 100644 --- a/src/app/schedule/page.tsx +++ b/src/app/schedule/page.tsx @@ -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' && } - {tab === 'myPost' && } - {tab === 'past' && } - - ); -}; + const tab = (searchParams.get('tab') || 'current') as 'current' | 'myPost' | 'past'; -const ScheduleSkeleton = () => ( -
-
-
- {Array.from({ length: GROUP_LIST_PAGE_SIZE }).map((_, i) => ( - - ))} -
-
-
-); - -export default function SchedulePage() { return ( -
+
- - }> - - + }>{SCHEDULE_CONTENTS[tab]}
); } + +const SCHEDULE_CONTENTS = { + current: , + myPost: , + past: , +}; + +const SCHEDULE_TABS = [ + { label: '현재 모임', value: 'current' }, + { label: '나의 모임', value: 'myPost' }, + { label: '모임 이력', value: 'past' }, +]; diff --git a/src/components/pages/group-list/group-list-loading/index.tsx b/src/components/pages/group-list/group-list-loading/index.tsx index 3fba03f3..32aefc36 100644 --- a/src/components/pages/group-list/group-list-loading/index.tsx +++ b/src/components/pages/group-list/group-list-loading/index.tsx @@ -6,7 +6,7 @@ export const GroupListSkeleton = () => (
{Array.from({ length: GROUP_LIST_PAGE_SIZE }).map((_, i) => ( - + ))}
diff --git a/src/components/pages/schedule/index.ts b/src/components/pages/schedule/index.ts new file mode 100644 index 00000000..ecebaf78 --- /dev/null +++ b/src/components/pages/schedule/index.ts @@ -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'; diff --git a/src/app/schedule/_components/meetings/constants.tsx b/src/components/pages/schedule/schedule-list/constants.tsx similarity index 93% rename from src/app/schedule/_components/meetings/constants.tsx rename to src/components/pages/schedule/schedule-list/constants.tsx index b4d858c7..1530bf4a 100644 --- a/src/app/schedule/_components/meetings/constants.tsx +++ b/src/components/pages/schedule/schedule-list/constants.tsx @@ -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, diff --git a/src/app/schedule/_components/meetings/index.tsx b/src/components/pages/schedule/schedule-list/index.tsx similarity index 72% rename from src/app/schedule/_components/meetings/index.tsx rename to src/components/pages/schedule/schedule-list/index.tsx index 29d708fd..edd95b32 100644 --- a/src/app/schedule/_components/meetings/index.tsx +++ b/src/components/pages/schedule/schedule-list/index.tsx @@ -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; @@ -26,8 +27,8 @@ type MeetingsProps = { refetch?: () => Promise; }; -export const Meetings = ({ - meetings, +export const ScheduleList = ({ + group, tabType, emptyStateType, emptyStatePath, @@ -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; @@ -56,14 +57,10 @@ export const Meetings = ({ } }; - if (isLoading) { - return ; - } - return ( <> {showEmptyState && ( - + )} {hasItems && ( @@ -74,7 +71,7 @@ export const Meetings = ({
)} - + {showErrorWithData && (
@@ -82,7 +79,7 @@ export const Meetings = ({
)} - { - 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 (
- {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 ( ); }; + +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'; +}; diff --git a/src/app/schedule/_components/card.tsx b/src/components/pages/schedule/schedule-list/schedule-list-content/schedule-card/card.tsx similarity index 84% rename from src/app/schedule/_components/card.tsx rename to src/components/pages/schedule/schedule-list/schedule-list-content/schedule-card/card.tsx index df2657cc..c423d683 100644 --- a/src/app/schedule/_components/card.tsx +++ b/src/components/pages/schedule/schedule-list/schedule-list-content/schedule-card/card.tsx @@ -14,14 +14,14 @@ import { GroupListItemResponse } from '@/types/service/group'; type TabType = 'current' | 'myPost' | 'past'; -interface ScheduleCardProps { +interface Props { createdBy: GroupListItemResponse['createdBy']; groupId: string; isFinished: boolean; isHost: boolean; isPending: boolean; joinPolicy: GroupListItemResponse['joinPolicy']; - meeting: GroupListItemResponse; + group: GroupListItemResponse; modalType: 'pending' | 'leave' | 'delete'; shouldFetchChatRoomId: boolean; showActions: boolean; @@ -35,12 +35,12 @@ export const ScheduleCard = ({ isHost, isPending, joinPolicy, - meeting, + group, modalType, shouldFetchChatRoomId, showActions, tabType, -}: ScheduleCardProps) => { +}: Props) => { const router = useRouter(); const { open } = useModal(); @@ -65,8 +65,8 @@ export const ScheduleCard = ({ return ( ); diff --git a/src/components/pages/schedule/schedule-list/schedule-list-empty/index.tsx b/src/components/pages/schedule/schedule-list/schedule-list-empty/index.tsx new file mode 100644 index 00000000..522eb72b --- /dev/null +++ b/src/components/pages/schedule/schedule-list/schedule-list-empty/index.tsx @@ -0,0 +1,32 @@ +import { useRouter } from 'next/navigation'; + +import { EmptyState } from '@/components/layout/empty-state'; +import { Button } from '@/components/ui'; + +import { EMPTY_STATE_CONFIG, type TabType } from '../constants'; + +interface Props { + emptyStateType: TabType; + emptyStatePath: string; +} + +export const ScheduleListEmpty = ({ emptyStateType, emptyStatePath }: Props) => { + const router = useRouter(); + const config = EMPTY_STATE_CONFIG[emptyStateType]; + + const handleEmptyStateClick = () => router.push(emptyStatePath); + + return ( +
+ +

{config.text}

+ +
+
+ ); +}; diff --git a/src/app/schedule/_components/meetings/meetings-infinite-scroll/index.tsx b/src/components/pages/schedule/schedule-list/schedule-list-infinite-scroll/index.tsx similarity index 90% rename from src/app/schedule/_components/meetings/meetings-infinite-scroll/index.tsx rename to src/components/pages/schedule/schedule-list/schedule-list-infinite-scroll/index.tsx index cd367cc2..5d011f3d 100644 --- a/src/app/schedule/_components/meetings/meetings-infinite-scroll/index.tsx +++ b/src/components/pages/schedule/schedule-list/schedule-list-infinite-scroll/index.tsx @@ -1,6 +1,6 @@ import { type RefObject } from 'react'; -interface MeetingsInfiniteScrollProps { +interface Props { sentinelRef?: RefObject; hasNextPage: boolean; isFetchingNextPage: boolean; @@ -8,13 +8,13 @@ interface MeetingsInfiniteScrollProps { hasError: boolean; } -export const MeetingsInfiniteScroll = ({ +export const ScheduleListInfiniteScroll = ({ sentinelRef, hasNextPage, isFetchingNextPage, completedMessage, hasError, -}: MeetingsInfiniteScrollProps) => { +}: Props) => { if (hasNextPage && !hasError) { return ( <> diff --git a/src/app/schedule/_components/current/index.test.tsx b/src/components/pages/schedule/schedule-tabs/current/index.test.tsx similarity index 100% rename from src/app/schedule/_components/current/index.test.tsx rename to src/components/pages/schedule/schedule-tabs/current/index.test.tsx diff --git a/src/app/schedule/_components/current/index.tsx b/src/components/pages/schedule/schedule-tabs/current/index.tsx similarity index 93% rename from src/app/schedule/_components/current/index.tsx rename to src/components/pages/schedule/schedule-tabs/current/index.tsx index 879f883c..70e0caf4 100644 --- a/src/app/schedule/_components/current/index.tsx +++ b/src/components/pages/schedule/schedule-tabs/current/index.tsx @@ -7,9 +7,9 @@ import { GROUP_LIST_PAGE_SIZE, INTERSECTION_OBSERVER_THRESHOLD } from '@/lib/con import { groupKeys } from '@/lib/query-key/query-key-group'; import { GroupListItemResponse } from '@/types/service/group'; -import { Meetings } from '../meetings/index'; +import { ScheduleList } from '../../schedule-list/index'; -export default function Current() { +export const Current = () => { const queryKey = groupKeys.myGroupsList('current') as ['myGroups', 'current']; const { @@ -47,19 +47,19 @@ export default function Current() { }); return ( - ); -} +}; diff --git a/src/app/schedule/_components/my/index.test.tsx b/src/components/pages/schedule/schedule-tabs/myPost/index.test.tsx similarity index 100% rename from src/app/schedule/_components/my/index.test.tsx rename to src/components/pages/schedule/schedule-tabs/myPost/index.test.tsx diff --git a/src/app/schedule/_components/my/index.tsx b/src/components/pages/schedule/schedule-tabs/myPost/index.tsx similarity index 93% rename from src/app/schedule/_components/my/index.tsx rename to src/components/pages/schedule/schedule-tabs/myPost/index.tsx index 8b9d2254..68db0bc0 100644 --- a/src/app/schedule/_components/my/index.tsx +++ b/src/components/pages/schedule/schedule-tabs/myPost/index.tsx @@ -7,9 +7,9 @@ import { GROUP_LIST_PAGE_SIZE, INTERSECTION_OBSERVER_THRESHOLD } from '@/lib/con import { groupKeys } from '@/lib/query-key/query-key-group'; import { GroupListItemResponse } from '@/types/service/group'; -import { Meetings } from '../meetings/index'; +import { ScheduleList } from '../../schedule-list/index'; -export default function My() { +export const MyPost = () => { const queryKey = groupKeys.myGroupsList('myPost') as ['myGroups', 'myPost']; const { @@ -48,19 +48,19 @@ export default function My() { }); return ( - ); -} +}; diff --git a/src/app/schedule/_components/history/index.test.tsx b/src/components/pages/schedule/schedule-tabs/past/index.test.tsx similarity index 100% rename from src/app/schedule/_components/history/index.test.tsx rename to src/components/pages/schedule/schedule-tabs/past/index.test.tsx diff --git a/src/app/schedule/_components/history/index.tsx b/src/components/pages/schedule/schedule-tabs/past/index.tsx similarity index 92% rename from src/app/schedule/_components/history/index.tsx rename to src/components/pages/schedule/schedule-tabs/past/index.tsx index 4c22664a..e3e9401a 100644 --- a/src/app/schedule/_components/history/index.tsx +++ b/src/components/pages/schedule/schedule-tabs/past/index.tsx @@ -7,9 +7,9 @@ import { GROUP_LIST_PAGE_SIZE, INTERSECTION_OBSERVER_THRESHOLD } from '@/lib/con import { groupKeys } from '@/lib/query-key/query-key-group'; import { GroupListItemResponse } from '@/types/service/group'; -import { Meetings } from '../meetings/index'; +import { ScheduleList } from '../../schedule-list/index'; -export default function History() { +export const Past = () => { const queryKey = groupKeys.myGroupsList('past') as ['myGroups', 'past']; const { @@ -42,19 +42,19 @@ export default function History() { }); return ( - ); -} +}; diff --git a/src/components/pages/schedule/shcedule-skeletons/index.tsx b/src/components/pages/schedule/shcedule-skeletons/index.tsx new file mode 100644 index 00000000..332b5d39 --- /dev/null +++ b/src/components/pages/schedule/shcedule-skeletons/index.tsx @@ -0,0 +1,24 @@ +import { CardSkeleton } from '@/components/shared/card/card-skeleton'; +import { GROUP_LIST_PAGE_SIZE } from '@/lib/constants/group-list'; + +interface Props { + tab: 'current' | 'myPost' | 'past'; +} + +export const ScheduleSkeleton = ({ tab }: Props) => { + const BUTTON_OPTIONS = { + current: true, + myPost: true, + past: false, + }; + + return ( +
+
+ {Array.from({ length: GROUP_LIST_PAGE_SIZE }).map((_, i) => ( + + ))} +
+
+ ); +}; diff --git a/src/components/shared/card/card-skeleton/index.tsx b/src/components/shared/card/card-skeleton/index.tsx index fde7eaa3..aa411606 100644 --- a/src/components/shared/card/card-skeleton/index.tsx +++ b/src/components/shared/card/card-skeleton/index.tsx @@ -1,8 +1,8 @@ interface CardSkeletonProps { - showButtons?: boolean; + showButtons: boolean; } -export const CardSkeleton = ({ showButtons = false }: CardSkeletonProps = {}) => { +export const CardSkeleton = ({ showButtons }: CardSkeletonProps) => { return (
diff --git a/src/components/shared/card/card-thumbnail/index.tsx b/src/components/shared/card/card-thumbnail/index.tsx index aa3e38c4..8ff65cd9 100644 --- a/src/components/shared/card/card-thumbnail/index.tsx +++ b/src/components/shared/card/card-thumbnail/index.tsx @@ -19,12 +19,9 @@ export const CardThumbnail = ({ thumbnail, isPending, isFinished }: CardThumbnai
)} {isFinished && ( - <> -
-
- 모임 마감 -
- +
+ 모임 마감 +
)}
); diff --git a/src/hooks/use-group/use-group-infinite-list/index.ts b/src/hooks/use-group/use-group-infinite-list/index.ts index 8339320d..abb9d92e 100644 --- a/src/hooks/use-group/use-group-infinite-list/index.ts +++ b/src/hooks/use-group/use-group-infinite-list/index.ts @@ -1,6 +1,6 @@ import { useMemo } from 'react'; -import { InfiniteData, QueryObserverResult, useInfiniteQuery } from '@tanstack/react-query'; +import { InfiniteData, QueryObserverResult, useSuspenseInfiniteQuery } from '@tanstack/react-query'; const STALE_TIME = 3 * 1000; // 3초 const DEFAULT_ERROR_MESSAGE = '데이터를 불러오는데 실패했습니다.'; @@ -60,7 +60,7 @@ export function useInfiniteScroll< pageSize = 10, staleTime = STALE_TIME, errorMessage = DEFAULT_ERROR_MESSAGE, - enabled = true, + // enabled = true, completedMessage = '모든 데이터를 불러왔습니다.', }: UseInfiniteScrollParams): UseInfiniteScrollReturn { type InfiniteScrollData = InfiniteData, number | undefined>; @@ -74,7 +74,7 @@ export function useInfiniteScroll< isFetching, isLoading, refetch, - } = useInfiniteQuery< + } = useSuspenseInfiniteQuery< InfiniteScrollResponse, Error, InfiniteScrollData, @@ -82,7 +82,7 @@ export function useInfiniteScroll< number | undefined >({ queryKey, - enabled, + // enabled, queryFn: async ({ pageParam }) => { const response = await queryFn({ cursor: pageParam,