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,