diff --git a/src/_apis/crew/crew-list.ts b/src/_apis/crew/crew-list.ts
index f2719143..591912dc 100644
--- a/src/_apis/crew/crew-list.ts
+++ b/src/_apis/crew/crew-list.ts
@@ -5,16 +5,24 @@ export async function getCrewList(condition: ConditionTypes, pageable: PageableT
const { keyword, mainLocation, mainCategory, subCategory, sortType } = condition;
const { page, size, sort = ['string'] } = pageable;
- const response: { data: MainCrewListResponse } = await fetchApi(
- `/api/crews/search?keyword=${keyword}&mainLocation=${mainLocation}&mainCategory=${mainCategory}&subCategory=${subCategory}&sortType=${sortType}&page=${page}&size=${size}&sort=${sort}`,
- {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
+ try {
+ const response: { data: MainCrewListResponse } = await fetchApi(
+ `/api/crews/search?keyword=${keyword}&mainLocation=${mainLocation}&mainCategory=${mainCategory}&subCategory=${subCategory}&sortType=${sortType}&page=${page}&size=${size}&sort=${sort}`,
+ {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ credentials: 'include', // 인증 정보를 요청에 포함
},
- credentials: 'include', // 인증 정보를 요청에 포함
- },
- );
-
- return response?.data;
+ );
+ if (!response.data) {
+ throw new Error('Failed to get crew list: No data received');
+ }
+ return response.data;
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error(`크루 목록 조회 실패`);
+ return null;
+ }
}
diff --git a/src/_apis/crew/my-crew-hosted-list.ts b/src/_apis/crew/my-crew-hosted-list.ts
new file mode 100644
index 00000000..e55d990a
--- /dev/null
+++ b/src/_apis/crew/my-crew-hosted-list.ts
@@ -0,0 +1,27 @@
+import { fetchApi } from '@/src/utils/api';
+import { MyCrewListResponse, PageableTypes } from '@/src/types/crew-card';
+
+export async function getMyCrewHostedList(pageable: PageableTypes) {
+ const { page, size, sort = ['string'] } = pageable;
+
+ try {
+ const response: { data: MyCrewListResponse } = await fetchApi(
+ `/api/crews/hosted?page=${page}&size=${size}&sort=${sort}`,
+ {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ credentials: 'include', // 인증 정보를 요청에 포함
+ },
+ );
+ if (!response.data) {
+ throw new Error('Failed to get my crew hosted list');
+ }
+ return response.data;
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error(`내가 개설한 크루 목록 조회 실패`);
+ return null;
+ }
+}
diff --git a/src/_apis/crew/my-crew-joined-list.ts b/src/_apis/crew/my-crew-joined-list.ts
new file mode 100644
index 00000000..7679baea
--- /dev/null
+++ b/src/_apis/crew/my-crew-joined-list.ts
@@ -0,0 +1,27 @@
+import { fetchApi } from '@/src/utils/api';
+import { MyCrewListResponse, PageableTypes } from '@/src/types/crew-card';
+
+export async function getMyCrewJoinedList(pageable: PageableTypes) {
+ const { page, size, sort = ['string'] } = pageable;
+
+ try {
+ const response: { data: MyCrewListResponse } = await fetchApi(
+ `/api/crews/joined?page=${page}&size=${size}&sort=${sort}`,
+ {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ credentials: 'include', // 인증 정보를 요청에 포함
+ },
+ );
+ if (!response.data) {
+ throw new Error('Failed to get my crew joined list');
+ }
+ return response.data;
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error(`내가 가입한 크루 목록 조회 실패`);
+ return null;
+ }
+}
diff --git a/src/_queries/crew/crew-list-queries.tsx b/src/_queries/crew/crew-list-queries.tsx
index 6d2ce1f2..056b8dbd 100644
--- a/src/_queries/crew/crew-list-queries.tsx
+++ b/src/_queries/crew/crew-list-queries.tsx
@@ -1,7 +1,14 @@
+import { getCrewList } from '@/src/_apis/crew/crew-list';
import { ConditionTypes, MainCrewListResponse, PageableTypes } from '@/src/types/crew-card';
-import { getCrewList } from '../../_apis/crew/crew-list';
-export function useGetCrewListQuery(condition: ConditionTypes) {
+export function useGetCrewListQuery({
+ condition,
+ pageable,
+}: {
+ condition: ConditionTypes;
+ pageable: PageableTypes;
+}) {
+ const { size, sort = ['string'] } = pageable;
return {
queryKey: [
condition.keyword,
@@ -11,14 +18,16 @@ export function useGetCrewListQuery(condition: ConditionTypes) {
condition.sortType,
],
queryFn: ({ pageParam = 0 }) =>
- getCrewList(condition, { page: pageParam, size: 6, sort: [condition.sortType] }).then(
- (response) => {
- if (response === undefined) {
- throw new Error('Response is null');
- }
- return response;
- },
- ),
+ getCrewList(condition, {
+ page: pageParam,
+ size,
+ sort,
+ }).then((response) => {
+ if (response === undefined || response === null) {
+ throw new Error('Response is undefined');
+ }
+ return response;
+ }),
getNextPageParam: (lastPage: MainCrewListResponse, allPages: MainCrewListResponse[]) =>
lastPage.hasNext ? allPages.length : undefined,
};
diff --git a/src/_queries/crew/my-crew-hosted-list-query.ts b/src/_queries/crew/my-crew-hosted-list-query.ts
new file mode 100644
index 00000000..24ee37a6
--- /dev/null
+++ b/src/_queries/crew/my-crew-hosted-list-query.ts
@@ -0,0 +1,18 @@
+import { getMyCrewHostedList } from '@/src/_apis/crew/my-crew-hosted-list';
+import { MyCrewListResponse, PageableTypes } from '@/src/types/crew-card';
+
+export function useGetMyCrewHostedQuery({ pageable }: { pageable: PageableTypes }) {
+ const { size, sort = ['string'] } = pageable;
+ return {
+ queryKey: ['myCrewHosted'],
+ queryFn: ({ pageParam = 0 }) =>
+ getMyCrewHostedList({ page: pageParam, size, sort }).then((response) => {
+ if (response === undefined || response === null) {
+ throw new Error('크루 목록을 불러오는데 실패했습니다.');
+ }
+ return response;
+ }),
+ getNextPageParam: (lastPage: MyCrewListResponse, allPages: MyCrewListResponse[]) =>
+ lastPage.hasNext ? allPages.length : undefined,
+ };
+}
diff --git a/src/_queries/crew/my-crew-joined-list-query.ts b/src/_queries/crew/my-crew-joined-list-query.ts
new file mode 100644
index 00000000..0daced74
--- /dev/null
+++ b/src/_queries/crew/my-crew-joined-list-query.ts
@@ -0,0 +1,18 @@
+import { getMyCrewJoinedList } from '@/src/_apis/crew/my-crew-joined-list';
+import { MyCrewListResponse, PageableTypes } from '@/src/types/crew-card';
+
+export function useGetMyCrewJoinedQuery({ pageable }: { pageable: PageableTypes }) {
+ const { size, sort = ['string'] } = pageable;
+ return {
+ queryKey: ['myCrewJoined'],
+ queryFn: ({ pageParam = 0 }) =>
+ getMyCrewJoinedList({ page: pageParam, size, sort }).then((response) => {
+ if (response === undefined || response === null) {
+ throw new Error('크루 목록을 불러오는데 실패했습니다.');
+ }
+ return response;
+ }),
+ getNextPageParam: (lastPage: MyCrewListResponse, allPages: MyCrewListResponse[]) =>
+ lastPage.hasNext ? allPages.length : undefined,
+ };
+}
diff --git a/src/app/(crew)/my-crew/hosted/page.tsx b/src/app/(crew)/my-crew/hosted/page.tsx
new file mode 100644
index 00000000..69930ceb
--- /dev/null
+++ b/src/app/(crew)/my-crew/hosted/page.tsx
@@ -0,0 +1,31 @@
+'use client';
+
+import { Loader } from '@mantine/core';
+import { useGetMyCrewHostedQuery } from '@/src/_queries/crew/my-crew-hosted-list-query';
+import { useInfiniteScroll } from '@/src/hooks/use-infinite-scroll';
+import CrewCardList from '@/src/components/common/crew-list/crew-card-list';
+
+export default function MyCrewHostedPage() {
+ const { data, status, ref, isFetchingNextPage } = useInfiniteScroll(
+ useGetMyCrewHostedQuery({
+ pageable: { page: 0, size: 6, sort: ['createdAt,desc'] },
+ }),
+ );
+ return (
+
+
+ {status === 'pending' ? (
+
+
+
+ ) : (
+
+ )}
+ {status === 'error' &&
에러가 발생했습니다.
}
+
+ );
+}
diff --git a/src/app/(crew)/my-crew/joined/page.tsx b/src/app/(crew)/my-crew/joined/page.tsx
new file mode 100644
index 00000000..0505ff9e
--- /dev/null
+++ b/src/app/(crew)/my-crew/joined/page.tsx
@@ -0,0 +1,31 @@
+'use client';
+
+import { Loader } from '@mantine/core';
+import { useGetMyCrewJoinedQuery } from '@/src/_queries/crew/my-crew-joined-list-query';
+import { useInfiniteScroll } from '@/src/hooks/use-infinite-scroll';
+import CrewCardList from '@/src/components/common/crew-list/crew-card-list';
+
+export default function MyCrewJoinedPage() {
+ const { data, status, ref, isFetchingNextPage } = useInfiniteScroll(
+ useGetMyCrewJoinedQuery({
+ pageable: { page: 0, size: 6, sort: ['createdAt,desc'] },
+ }),
+ );
+ return (
+
+
+ {status === 'pending' ? (
+
+
+
+ ) : (
+
+ )}
+ {status === 'error' &&
에러가 발생했습니다.
}
+
+ );
+}
diff --git a/src/app/(crew)/my-crew/layout.tsx b/src/app/(crew)/my-crew/layout.tsx
new file mode 100644
index 00000000..0a3f593c
--- /dev/null
+++ b/src/app/(crew)/my-crew/layout.tsx
@@ -0,0 +1,41 @@
+'use client';
+
+import { ReactNode, useEffect, useState } from 'react';
+import { usePathname, useRouter } from 'next/navigation';
+import Tabs from '@/src/components/common/tab';
+
+export default function MyCrewLayout({ children }: { children: ReactNode }) {
+ const router = useRouter();
+ const currentPath = usePathname();
+ const myCrewTabs = [
+ { label: '내가 참여한 크루', id: 'joined-crew', route: '/my-crew/joined' },
+ { label: '내가 만든 크루', id: 'hosted-crew', route: '/my-crew/hosted' },
+ ];
+ const [currentTab, setCurrentTab] = useState(myCrewTabs[0].id);
+
+ const handleTabClick = (id: string) => {
+ const targetRoute = myCrewTabs.find((tab) => tab.id === id)?.route;
+ if (targetRoute) router.push(targetRoute);
+ };
+
+ useEffect(() => {
+ const activeTabId = myCrewTabs.find((tab) => tab.route === currentPath)?.id;
+ if (activeTabId) setCurrentTab(activeTabId);
+ }, [currentPath]);
+
+ return (
+
+
+ {
+ handleTabClick(id);
+ }}
+ />
+
+
{children}
+
+ );
+}
diff --git a/src/app/(crew)/my-crew/page.tsx b/src/app/(crew)/my-crew/page.tsx
index 976094b1..9322cc4d 100644
--- a/src/app/(crew)/my-crew/page.tsx
+++ b/src/app/(crew)/my-crew/page.tsx
@@ -1,40 +1,7 @@
'use client';
-import { useState } from 'react';
-import Tabs from '@/src/components/common/tab';
+import { redirect } from 'next/navigation';
export default function MyCrewPage() {
- const myPageTabs = [
- { label: '내가 참여한 크루', id: 'joined-crew' },
- { label: '내가 만든 크루', id: 'made-crew' },
- ];
- const [currentTab, setCurrentTab] = useState(myPageTabs[0].id);
-
- // TODO: fetchCrewData 함수를 사용하여 데이터를 불러오기 : 파라미터 수정 필요
- // TODO: 리스트와는 다른 데이터를 사용해야해서 우선 주석처리 했습니다.
- // const { data, ref, isFetchingNextPage } =
- // useInfiniteScroll(useGetCrewListQuery());
-
- return (
-
-
- {
- setCurrentTab(id);
- }}
- />
-
-
- {/* */}
-
-
- );
+ redirect('/my-crew/joined');
}
diff --git a/src/app/(crew)/page.tsx b/src/app/(crew)/page.tsx
index 5a4e86be..430424c6 100644
--- a/src/app/(crew)/page.tsx
+++ b/src/app/(crew)/page.tsx
@@ -2,7 +2,7 @@
import { useRef, useState } from 'react';
import Image from 'next/image';
-import { Divider, TextInput } from '@mantine/core';
+import { Divider, Loader, TextInput } from '@mantine/core';
import { useGetCrewListQuery } from '@/src/_queries/crew/crew-list-queries';
import regionData from '@/src/data/region.json';
import { useInfiniteScroll } from '@/src/hooks/use-infinite-scroll';
@@ -10,7 +10,6 @@ import CategoryContainer from '@/src/app/_components/category/category-container
import HeroCrew from '@/src/app/_components/hero/hero-crew';
import CrewCardList from '@/src/components/common/crew-list/crew-card-list';
import DropDown from '@/src/components/common/input/drop-down';
-import { MainCrewListResponse } from '@/src/types/crew-card';
import IcoSearch from '@/public/assets/icons/ic-search.svg';
export default function HomePage() {
@@ -35,13 +34,16 @@ export default function HomePage() {
}
};
- const { data, ref, isFetchingNextPage } = useInfiniteScroll(
+ const { data, status, isFetchingNextPage, ref } = useInfiniteScroll(
useGetCrewListQuery({
- keyword: search,
- mainLocation: handleRegionChange(region),
- mainCategory,
- subCategory,
- sortType: sort === 'latest' ? 'LATEST' : 'POPULAR',
+ condition: {
+ keyword: search,
+ mainLocation: handleRegionChange(region),
+ mainCategory,
+ subCategory,
+ sortType: sort === 'latest' ? 'LATEST' : 'POPULAR',
+ },
+ pageable: { page: 0, size: 6, sort: ['createdAt,desc'] },
}),
);
@@ -124,6 +126,14 @@ export default function HomePage() {
{data &&
}
+ {status === 'pending' ? (
+
+
+
+ ) : (
+
+ )}
+ {status === 'error' &&
에러가 발생했습니다.
}
);
diff --git a/src/app/_components/hero/hero-crew.tsx b/src/app/_components/hero/hero-crew.tsx
index 893aa3b6..38a24889 100644
--- a/src/app/_components/hero/hero-crew.tsx
+++ b/src/app/_components/hero/hero-crew.tsx
@@ -1,7 +1,6 @@
import Image from 'next/image';
import Link from 'next/link';
import { useAuthStore } from '@/src/store/use-auth-store';
-import Button from '@/src/components/common/input/button';
import ImgHeroCrew from '@/public/assets/icons/ic-dumbbell.svg';
export default function HeroCrew() {
diff --git a/src/components/common/crew-list/crew-card-list.stories.tsx b/src/components/common/crew-list/crew-card-list.stories.tsx
index 4e8952f6..672875e0 100644
--- a/src/components/common/crew-list/crew-card-list.stories.tsx
+++ b/src/components/common/crew-list/crew-card-list.stories.tsx
@@ -1,10 +1,7 @@
-import { useEffect, useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
-import { InfiniteData } from '@tanstack/react-query';
import { useGetCrewListQuery } from '@/src/_queries/crew/crew-list-queries';
import { useInfiniteScroll } from '@/src/hooks/use-infinite-scroll';
import ClientProvider from '@/src/components/client-provider';
-import { MainCrewListResponse } from '@/src/types/crew-card';
import CrewCardList from './crew-card-list';
const meta: Meta = {
@@ -32,11 +29,14 @@ type Story = StoryObj;
function RenderCrewCardList() {
const { data, ref, isFetchingNextPage } = useInfiniteScroll(
useGetCrewListQuery({
- keyword: '',
- mainLocation: '',
- mainCategory: '',
- subCategory: '',
- sortType: 'LATEST',
+ condition: {
+ keyword: '',
+ mainLocation: '',
+ mainCategory: '',
+ subCategory: '',
+ sortType: 'LATEST',
+ },
+ pageable: { page: 0, size: 6, sort: ['createdAt,desc'] },
}),
);
diff --git a/src/components/common/crew-list/crew-card-list.tsx b/src/components/common/crew-list/crew-card-list.tsx
index e5ca247d..8fb36641 100644
--- a/src/components/common/crew-list/crew-card-list.tsx
+++ b/src/components/common/crew-list/crew-card-list.tsx
@@ -10,38 +10,22 @@ import {
import CrewCard from './crew-card';
// CrewCardListProps 타입을 구분하여 정의
-interface MainCrewCardListProps {
- data: InfiniteData;
+interface CrewCardListProps {
+ data: InfiniteData;
isFetchingNextPage: boolean;
- inWhere?: undefined;
+ inWhere?: 'my-crew' | 'main-crew';
}
-interface MyCrewCardListProps {
- data: InfiniteData;
- isFetchingNextPage: boolean;
- inWhere: 'my-crew';
-}
-
-// 유니온 타입으로 정의
-type CrewCardListProps = MainCrewCardListProps | MyCrewCardListProps;
-
-function CrewCardList(
- { data, isFetchingNextPage, inWhere }: CrewCardListProps,
- ref: React.Ref,
-) {
+function CrewCardList({ data, isFetchingNextPage, inWhere }: CrewCardListProps) {
const crewDataList =
- (inWhere === 'my-crew'
- ? data?.pages.flatMap((page) => page?.content)
- : data?.pages?.flatMap((page) => page?.content)) ?? [];
+ inWhere === 'my-crew'
+ ? data.pages.flatMap((page) => page.content as MyCrewList[])
+ : data.pages.flatMap((page) => page.content as MainCrewList[]);
+
const gridColsStyle = inWhere === 'my-crew' ? '' : 'lg:grid-cols-2';
- if (data?.pages[0] === undefined)
- // 초기 로딩시 데이터 없을때
- return (
-
-
-
- );
+ // 초기 로딩시 데이터 없을때
+ if (!data.pages.length) return null;
if (!crewDataList.length)
return (
@@ -85,12 +69,10 @@ function CrewCardList(
))}
- {isFetchingNextPage ? (
+ {isFetchingNextPage && (
- ) : (
-
)}
>
);
diff --git a/src/components/common/crew-list/crew-card.tsx b/src/components/common/crew-list/crew-card.tsx
index 37552551..2daa2187 100644
--- a/src/components/common/crew-list/crew-card.tsx
+++ b/src/components/common/crew-list/crew-card.tsx
@@ -10,7 +10,7 @@ import IcoUser from '@/public/assets/icons/ic-user.svg';
import Profiles from './profiles';
interface CrewCardProps extends MainCrewList {
- inWhere?: 'my-crew';
+ inWhere?: 'my-crew' | 'main-crew';
}
export default function CrewCard({
@@ -27,9 +27,7 @@ export default function CrewCard({
inWhere,
}: CrewCardProps) {
const [prefetched, setPrefetched] = useState(new Set());
- // NOTE: api연결 후 되돌리기
- // const CREWPAGE = `/crew/detail/${id}`;
- const CREWPAGE = `/crew/detail/1`;
+ const CREWPAGE = `/crew/detail/${id}`;
const router = useRouter();
const handleCardClick = () => {
diff --git a/src/components/common/input/pop-over-calendar/index.tsx b/src/components/common/input/pop-over-calendar/index.tsx
index 5f76e8c7..648314cf 100644
--- a/src/components/common/input/pop-over-calendar/index.tsx
+++ b/src/components/common/input/pop-over-calendar/index.tsx
@@ -36,9 +36,14 @@ export default function PopOverCalendar({ value, onChange }: PopOverProps) {