Skip to content

Commit f7bf794

Browse files
youngminssclaudeRookieAND
authored
feat: api client 모듈 생성 (#37)
* feat: ky 기반 공용 API 클라이언트 구현 - ApiResponse, ErrorResponse 타입 정의 - ApiError 커스텀 에러 클래스 및 isApiError 타입 가드 - createApiClient 팩토리 함수 (GET, POST, PUT, PATCH, DELETE) - 기본 apiClient 인스턴스 (NEXT_PUBLIC_API_URL 환경변수 사용) - 불필요한 utils/index.ts barrel export 제거 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: API 클라이언트 baseUrl에 공통 경로 추가 - /api/v1 공통 경로를 baseUrl에 포함 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * style: API 클라이언트 코드 포맷 정리 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: API 클라이언트 타입을 KyOptions 기반으로 개선 - ApiClientConfig: KyOptions indexed access type 활용 - ApiRequestOptions: Pick<KyOptions, ...>으로 변경 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: 모임장 모임 생성 api 연동 (Tanstack-Query 기반 요청 제외) (#38) * style: API 클라이언트 코드 포맷 정리 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: 모임 생성 API 정의 - CreateGatheringRequest, CreateGatheringResponse 타입 - createGathering API 함수 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: 모임 생성 완료 페이지 동적 라우트 변경 - /gathering/create/complete → /gathering/create/complete/[accessKey] - useParams로 accessKey 파라미터 수신 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: 모임 생성 API 연동 - RegionStep에서 createGathering API 호출 - 로딩 중 Spinner 표시 - API 에러 시 toast 알림 - 성공 시 accessKey로 완료 페이지 라우팅 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: 루트 레이아웃에서 전역 Toaster 제거 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: 모임 생성 완료 페이지 기능 연결 - handlePreferenceInput: opinion 페이지로 라우팅 구현 - handleShare: opinion 페이지 URL로 공유 기능 연결 - 미사용 Toaster import 제거 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: participant 도메인 내 신규 모임 참여 API 함수 추가 (#39) * feat: participant API 추가 - POST /api/v1/participants 모임 참여 API - CreateParticipantRequest, CreateParticipantResponse 타입 정의 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: prettier 양식에 어긋난 코드를 수정 * feat: recommend-result 도메인 내 추천 결과 조회 API 함수 추가 (#40) * feat: recommend-result API 추가 - GET /api/v1/recommend-results/{accessKey} 추천 결과 조회 API - GetRecommendResultResponse 타입 정의 (기존 RecommendationResult 재사용) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: prettier 양식에 어긋난 코드를 수정 * feat: 모임 단건 조회 API 추가 (#41) * chore: prettier 양식에 어긋난 코드를 수정 * feat: 모임 단건 조회 API 추가 - GET /api/v1/gatherings/{accessKey} 모임 단건 조회 API - GetGatheringResponse 타입 정의 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: GetGatheringResponse 타입 NonNullable 적용 - timeSlot, region 필드에 NonNullable 적용 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> * feat: 모임 참여 현황 조회 API 함수 추가 (#46) * feat: 모임 참여자 현황 조회 API 추가 - GatheringCapacityResponse 타입 정의 추가 - getGatheringCapacity API 함수 구현 - GET /api/v1/gatherings/{accessKey}/capacity 엔드포인트 연동 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * chore: prettier 포맷에 맞지 않았던 코드 수정 * chore: 병합 과정에서 충돌이 발생했던 코드 수정 --------- Co-authored-by: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * feat: 모임장 모임 생성 api Tanstack-Query 기반 요청 적용 (#42) * feat: QueryProvider 설정 추가 - QueryClientProvider 래퍼 컴포넌트 생성 - 기본 staleTime 60초, retry 설정 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: gathering Query Factory Pattern 파일 추가 - gatheringKeys: mutation key factory - gatheringOptions: mutation options factory - index.ts에 export 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: useCreateGathering mutation hook 추가 - useMutation 기반 모임 생성 hook - gatheringOptions.create() 활용 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: RegionStep에 useMutation 적용 - useCreateGathering hook으로 API 호출 변경 - useState(isSubmitting) → isPending으로 대체 - gathering layout에 QueryProvider 적용 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: QueryProvider 포맷팅 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: queryOption 포맷팅 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: participant 도메인 내 신규 모임 참여 API 함수 추가 (#39) * feat: participant API 추가 - POST /api/v1/participants 모임 참여 API - CreateParticipantRequest, CreateParticipantResponse 타입 정의 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: prettier 양식에 어긋난 코드를 수정 * feat: recommend-result 도메인 내 추천 결과 조회 API 함수 추가 (#40) * feat: recommend-result API 추가 - GET /api/v1/recommend-results/{accessKey} 추천 결과 조회 API - GetRecommendResultResponse 타입 정의 (기존 RecommendationResult 재사용) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: prettier 양식에 어긋난 코드를 수정 * feat: 모임 단건 조회 API 추가 (#41) * chore: prettier 양식에 어긋난 코드를 수정 * feat: 모임 단건 조회 API 추가 - GET /api/v1/gatherings/{accessKey} 모임 단건 조회 API - GetGatheringResponse 타입 정의 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: GetGatheringResponse 타입 NonNullable 적용 - timeSlot, region 필드에 NonNullable 적용 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> * feat: opengraph, favicon 적용 (#43) * feat: 페이지별 OpenGraph 메타데이터 적용 - 루트 layout에 기본 OG 이미지 활성화 - 모임 생성 완료 페이지 OG 메타데이터 추가 - 의견 수합 페이지들 OG 메타데이터 추가 - 결과 페이지 OG 메타데이터 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: favicon 및 앱 아이콘 적용 - favicon.ico 교체 - icon.png (32x32) 추가 - apple-icon.png (180x180) 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: 모임 생성 퍼널 전환을 모임원 의견 수합 퍼널 전환과 동일하게 적용 (#44) * refactor: StepTransition 컴포넌트를 공용 컴포넌트로 마이그레이션 - src/components/stepTransition으로 이동 - opinion 전용 타입(OpinionStep)을 제네릭(string)으로 변경 - intro step 체크 로직 제거 (페이지 레벨에서 처리) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: useCreateMeetingFunnel에 direction 상태 추가 - step 전환 방향(forward/backward) 추적 - StepTransition 애니메이션 지원을 위한 준비 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: create Step 컴포넌트를 Content/Footer로 분리 - PeopleStep → PeopleStepContent, PeopleStepFooter - DateStep → DateStepContent, DateStepFooter - RegionStep → RegionStepContent, RegionStepFooter - Content만 StepTransition으로 감싸기 위한 구조 변경 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: 모임 생성 페이지에 StepTransition 적용 - renderContent/renderFooter 패턴 적용 - Content 영역만 전환 애니메이션 적용 - Footer는 고정 유지 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> * fix: gatheringId 기반의 Path URL 을 accessKey 기반으로 수정 (#45) * feat: participant 도메인 내 신규 모임 참여 API 함수 추가 (#39) * feat: participant API 추가 - POST /api/v1/participants 모임 참여 API - CreateParticipantRequest, CreateParticipantResponse 타입 정의 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: prettier 양식에 어긋난 코드를 수정 * feat: recommend-result 도메인 내 추천 결과 조회 API 함수 추가 (#40) * feat: recommend-result API 추가 - GET /api/v1/recommend-results/{accessKey} 추천 결과 조회 API - GetRecommendResultResponse 타입 정의 (기존 RecommendationResult 재사용) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: prettier 양식에 어긋난 코드를 수정 * feat: 모임 단건 조회 API 추가 (#41) * chore: prettier 양식에 어긋난 코드를 수정 * feat: 모임 단건 조회 API 추가 - GET /api/v1/gatherings/{accessKey} 모임 단건 조회 API - GetGatheringResponse 타입 정의 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: GetGatheringResponse 타입 NonNullable 적용 - timeSlot, region 필드에 NonNullable 적용 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> * chore: prettier 포맷에 맞지 않았던 코드 수정 * refactor: change route params from gatheringId to accessKey - Rename route directory: [gatheringId] → [accessKey] - Update ShareButton to use accessKey from params - Add gathering detail query with accessKey parameter Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * fix: 타입스크립트 에러를 발생시키는 코드 제거 * fix: 이전 작업에서 혼용되었던 변경 이력을 초기화 하여 반영 * chore: 코드 포맷팅 --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: 위영민 <youngminieo1005@gmail.com> * chore: 코드 포맷팅 --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Gwangin Baik <gwangin1999@naver.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Gwangin Baik <gwangin1999@naver.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Gwangin Baik <gwangin1999@naver.com>
1 parent b415c73 commit f7bf794

File tree

50 files changed

+870
-152
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+870
-152
lines changed

app/apple-icon.png

7.08 KB
Loading

app/favicon.ico

-24.2 KB
Binary file not shown.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type { Metadata } from "next";
2+
import type { ReactNode } from "react";
3+
4+
export const metadata: Metadata = {
5+
title: "요기잇",
6+
description: "다인원을 위한 맛집 추천 서비스",
7+
openGraph: {
8+
title: "함께 갈 맛집, 같이 정해요!",
9+
description: "[요기잇] 다인원을 위한 맛집 서비스",
10+
type: "website",
11+
images: [
12+
{
13+
url: "https://yogieat-statics.s3.ap-southeast-2.amazonaws.com/images/opengraph/opinion-complete-og-image.png",
14+
width: 1200,
15+
height: 630,
16+
alt: "요기잇 - 의견 수합 완료",
17+
},
18+
],
19+
},
20+
};
21+
22+
interface LayoutProps {
23+
children: ReactNode;
24+
}
25+
26+
export default function Layout({ children }: LayoutProps) {
27+
return children;
28+
}

app/gathering/[gatheringId]/opinion/complete/page.tsx renamed to app/gathering/[accessKey]/opinion/complete/page.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import { useParams } from "next/navigation";
1010
import { redirect } from "next/navigation";
1111

1212
export default function OpinionCompletePage() {
13-
const params = useParams();
14-
const gatheringId = params.gatheringId as string;
13+
const { accessKey } = useParams<{ accessKey: string }>();
1514

1615
// TODO: API 연동 시 실제 데이터로 교체
1716
const totalCount = 5;
@@ -20,11 +19,11 @@ export default function OpinionCompletePage() {
2019
const isPending = submittedCount < totalCount;
2120

2221
if (isPending) {
23-
redirect(`/gathering/${gatheringId}/opinion/pending`);
22+
redirect(`/gathering/${accessKey}/opinion/pending`);
2423
}
2524

2625
const handleRedirectResult = () => {
27-
redirect(`/gathering/${gatheringId}/opinion/result`);
26+
redirect(`/gathering/${accessKey}/opinion/result`);
2827
};
2928

3029
return (
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"use client";
2+
3+
import { redirect } from "next/navigation";
4+
5+
export default function Error() {
6+
return redirect(`/`);
7+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import type { ReactNode } from "react";
2+
import type { Metadata } from "next";
3+
4+
import { Layout } from "#/components/layout";
5+
6+
interface OpinionLayoutProps {
7+
children: ReactNode;
8+
}
9+
10+
export const metadata: Metadata = {
11+
title: "요기잇",
12+
description: "다인원을 위한 맛집 추천 서비스",
13+
openGraph: {
14+
title: "함께 갈 맛집, 같이 정해요!",
15+
description: "[요기잇] 다인원을 위한 맛집 서비스",
16+
type: "website",
17+
images: [
18+
{
19+
url: "https://yogieat-statics.s3.ap-southeast-2.amazonaws.com/images/opengraph/opinion-landing-og-image.png",
20+
width: 1200,
21+
height: 630,
22+
alt: "요기잇 - 의견 수합",
23+
},
24+
],
25+
},
26+
};
27+
28+
export default function layout({ children }: OpinionLayoutProps) {
29+
return <Layout.Root>{children}</Layout.Root>;
30+
}

app/gathering/[gatheringId]/opinion/page.tsx renamed to app/gathering/[accessKey]/opinion/page.tsx

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,66 @@
11
"use client";
22

33
import { useParams, useRouter } from "next/navigation";
4-
import { useMemo } from "react";
54

65
import {
76
IntroStep,
87
DistanceStepContent,
98
DistanceStepFooter,
109
DislikeStepContent,
1110
DislikeStepFooter,
12-
StepTransition,
1311
PreferenceStepContent,
1412
PreferenceStepFooter,
1513
} from "#/pageComponents/gathering/opinion";
14+
import { StepTransition } from "#/components/stepTransition";
1615
import { useOpinionForm, useOpinionFunnel } from "#/hooks/gathering";
1716
import { Button } from "#/components/button";
1817
import { Layout } from "#/components/layout";
19-
import { MOCK_MEETING_DATA } from "#/constants/gathering/opinion/meeting";
20-
import { MeetingContext } from "#/types/gathering";
2118
import { FormProvider } from "react-hook-form";
2219
import { BackwardButton } from "#/components/backwardButton";
2320
import { Toaster } from "#/components/toast";
21+
import { useMemo } from "react";
22+
import { MeetingContext } from "#/types/gathering";
23+
import { MOCK_MEETING_DATA } from "#/constants/gathering/opinion/meeting";
2424

2525
export default function OpinionPage() {
26-
const params = useParams();
26+
const { accessKey } = useParams<{ accessKey: string }>();
2727
const router = useRouter();
28-
const gatheringId = params.gatheringId as string;
2928

3029
const form = useOpinionForm();
3130
const { step, direction, next, back, isFirstStep } = useOpinionFunnel();
3231

3332
const meetingContext = useMemo<MeetingContext>(
3433
() => ({
35-
gatheringId,
34+
accessKey,
3635
scheduledDate: MOCK_MEETING_DATA.DATE,
3736
stationName: MOCK_MEETING_DATA.STATION_NAME,
3837
}),
39-
[gatheringId],
38+
[accessKey],
4039
);
4140

4241
const handleBackward = () => {
4342
if (isFirstStep) {
44-
router.push(`/gathering/${gatheringId}`);
43+
router.push(`/gathering/${accessKey}`);
4544
} else {
4645
back();
4746
}
4847
};
4948

5049
const handleComplete = () => {
51-
router.replace(`/gathering/${gatheringId}/opinion/pending`);
50+
router.replace(`/gathering/${accessKey}/opinion/pending`);
5251
};
5352

5453
if (step === "intro") {
5554
return (
56-
<Layout.Root>
55+
<>
5756
<Layout.Header background="gray">
5857
<div className="ygi:h-full ygi:w-full" />
5958
</Layout.Header>
6059
<Layout.Content background="gray">
60+
{/* TODO : API 연동 과정에서 대체가 필요한 코드 */}
6161
<IntroStep
62-
step="intro"
6362
meetingContext={meetingContext}
63+
step="intro"
6464
onNext={next}
6565
/>
6666
</Layout.Content>
@@ -71,7 +71,7 @@ export default function OpinionPage() {
7171
</Button>
7272
</div>
7373
</Layout.Footer>
74-
</Layout.Root>
74+
</>
7575
);
7676
}
7777

@@ -103,17 +103,15 @@ export default function OpinionPage() {
103103

104104
return (
105105
<FormProvider {...form}>
106-
<Layout.Root>
107-
<Layout.Header>
108-
<BackwardButton onClick={handleBackward} />
109-
</Layout.Header>
110-
<Layout.Content>
111-
<StepTransition step={step} direction={direction}>
112-
{renderContent()}
113-
</StepTransition>
114-
</Layout.Content>
115-
{renderFooter()}
116-
</Layout.Root>
106+
<Layout.Header>
107+
<BackwardButton onClick={handleBackward} />
108+
</Layout.Header>
109+
<Layout.Content>
110+
<StepTransition step={step} direction={direction}>
111+
{renderContent()}
112+
</StepTransition>
113+
</Layout.Content>
114+
{renderFooter()}
117115
<Toaster offset={{ bottom: 96 }} mobileOffset={{ bottom: 96 }} />
118116
</FormProvider>
119117
);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type { Metadata } from "next";
2+
import type { ReactNode } from "react";
3+
4+
export const metadata: Metadata = {
5+
title: "요기잇",
6+
description: "다인원을 위한 맛집 추천 서비스",
7+
openGraph: {
8+
title: "함께 갈 맛집, 같이 정해요!",
9+
description: "[요기잇] 다인원을 위한 맛집 서비스",
10+
type: "website",
11+
images: [
12+
{
13+
url: "https://yogieat-statics.s3.ap-southeast-2.amazonaws.com/images/opengraph/opinion-complete-og-image.png",
14+
width: 1200,
15+
height: 630,
16+
alt: "요기잇 - 의견 수합 대기",
17+
},
18+
],
19+
},
20+
};
21+
22+
interface LayoutProps {
23+
children: ReactNode;
24+
}
25+
26+
export default function Layout({ children }: LayoutProps) {
27+
return children;
28+
}

app/gathering/[gatheringId]/opinion/pending/page.tsx renamed to app/gathering/[accessKey]/opinion/pending/page.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,14 @@ import { useParams } from "next/navigation";
1010
import { redirect } from "next/navigation";
1111

1212
export default function OpinionPendingPage() {
13-
const params = useParams();
14-
const gatheringId = params.gatheringId as string;
13+
const { accessKey } = useParams<{ accessKey: string }>();
1514

16-
// TODO: API 연동 시 실제 데이터로 교체
1715
const totalCount = 5;
1816
const submittedCount = 3;
1917

2018
const isComplete = submittedCount >= totalCount;
2119
if (isComplete) {
22-
redirect(`/gathering/${gatheringId}/opinion/complete`);
20+
redirect(`/gathering/${accessKey}/opinion/complete`);
2321
}
2422

2523
return (
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type { Metadata } from "next";
2+
import type { ReactNode } from "react";
3+
4+
export const metadata: Metadata = {
5+
title: "요기잇",
6+
description: "다인원을 위한 맛집 추천 서비스",
7+
openGraph: {
8+
title: "메뉴 추천이 완료 되었어요",
9+
description: "[요기잇] 추천 결과를 확인해 보세요",
10+
type: "website",
11+
images: [
12+
{
13+
url: "https://yogieat-statics.s3.ap-southeast-2.amazonaws.com/images/opengraph/opinion-result-og-image.png",
14+
width: 1200,
15+
height: 630,
16+
alt: "요기잇 - 추천 결과",
17+
},
18+
],
19+
},
20+
};
21+
22+
interface LayoutProps {
23+
children: ReactNode;
24+
}
25+
26+
export default function Layout({ children }: LayoutProps) {
27+
return children;
28+
}

0 commit comments

Comments
 (0)