Skip to content

Commit b5b194f

Browse files
RookieANDclaude
andauthored
feat: 의견 수렴 페이지에 쓰일 Query Hooks 생성 (#53)
* feat: gathering domain hooks 추가 - useGetGathering: 모임 조회 hook - useGetGatheringCapacity: 모임 참여자 현황 조회 hook - Domain별로 hooks 구조화 (폴더별 분리 제거) Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * feat: participant domain hooks 추가 - useCreateParticipant: 모임 참여 mutation hook - participant를 독립 domain으로 분리 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * feat: recommend-result domain hooks 추가 - useGetRecommendResult: 추천 결과 조회 hook - recommend-result를 독립 domain으로 분리 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * refactor: hooks index 파일 업데이트 - gathering, participant, recommend-result domain별 export - Domain 기반 hooks 구조 완성 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * feat: useGetGatheringCapacity에 10초 refetch 추가 - refetchInterval: 1000 * 10 설정 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * feat: useQuery Hook 기반의 함수 내 select 옵션 일괄 추가하여 데이터 정제 * feat: 의견 수렴 페이지 전체 API 연동 및 서버 Prefetch 적용 (#54) * refactor: IntroStep scheduledDate prop 연동 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * refactor: DistanceStep API 기반 region prop 연동 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * refactor: PreferenceStepFooter onSubmit prop 변경 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * refactor: CompleteView 텍스트 인라인 처리 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * refactor: SubmissionBottomSheet props명 변경 (maxCount, currentCount) Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * feat: useOpinionForm에 createParticipant 제출 로직 연동 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * feat: 의견 수렴 페이지 API 연동 및 타입 수정 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * feat: queryOption에 select 추가하여 response.data 자동 추출 - gathering detail, capacity에 select 추가 - 클라이언트 코드에서 .data 접근 불필요 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * feat: 의견 수렴 페이지에 서버 프리페치 적용 - page.tsx를 서버 컴포넌트로 변경 - OpinionView.tsx로 클라이언트 로직 분리 - HydrationBoundary로 gathering 데이터 prefetch - 무한 렌더링 문제 해결 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * fix: useCreateParticipant Hook 을 Import 하는 경로를 올바르게 수정' * feat: PendingView, CompleteView에 HydrationBoundary 및 API 연동 추가 - pending/page.tsx, complete/page.tsx를 서버 컴포넌트로 변경 - PendingView.tsx, CompleteView.tsx 클라이언트 컴포넌트 분리 - useGetGatheringCapacity hook으로 실시간 참여자 현황 조회 - HydrationBoundary로 capacity 데이터 prefetch - 하드코딩된 maxCount, currentCount 제거 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * feat: ResultView에 HydrationBoundary 및 API 연동 추가 - result/page.tsx를 서버 컴포넌트로 변경 - ResultView.tsx 클라이언트 컴포넌트 분리 - useGetRecommendResult, useGetGatheringCapacity hook으로 데이터 조회 - HydrationBoundary로 capacity, recommend-result 데이터 prefetch - MOCK_RECOMMENDATION_RESULT 제거 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * feat: 식당 페이지 이동 과정에서 Cursor Pointer 스타일 적용 * feat: distanceRange를 실제 거리 값으로 변환하는 로직 추가 - DISTANCE_RANGE_VALUES 상수 추가 (각 범위를 km 값으로 매핑) - RANGE_500M: 0.5km - RANGE_1KM: 1km - ANY: null - useOpinionForm에서 하드코딩된 0.5 제거 - distanceRange를 DISTANCE_RANGE_VALUES로 변환하여 API 전송 - CreateParticipantRequest의 distance 타입을 number | null로 변경 리뷰 가이드: - PR 6에서 DISTANCE_RANGE enum 구조로 더욱 개선 예정 - 현재는 기본적인 매핑 로직만 추가하여 하드코딩 제거 * refactor: Zod 기반 런타임 검증 추가 및 Controller 패턴에서 useController 패턴으로 마이그레이션 (#55) * feat: Opinion Form에 Zod schema 및 validation 추가 - opinionFormSchema 정의 (distanceRange, dislikedFoods, preferredMenus) - preferredMenus에 대한 복잡한 검증 로직 (superRefine) - distanceRangeToKm 헬퍼 함수 추가 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * feat: useOpinionForm에 zodResolver 적용 및 distance 변환 수정 - zodResolver 추가로 런타임 검증 활성화 - distanceRange → distance 변환 로직 수정 (하드코딩 제거) - useDislikeStep, usePreferenceStep export 제거 (미사용) Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * refactor: PreferenceStep을 useController 기반 RankChip 패턴으로 리팩토링 - RankChip 컴포넌트 생성 (useController로 자체 상태 관리) - Controller 제거, 비즈니스 로직을 RankChip으로 이동 - 선택 해제 기능 제거 (다른 항목 선택으로만 변경 가능) - RankSection을 RankChip 사용하도록 수정 - PreferenceStep을 선언적 컴포넌트로 단순화 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * refactor: DislikeStep을 useController 기반 DislikedFoodButton 패턴으로 리팩토링 - DislikedFoodButton 컴포넌트 생성 (useController로 자체 상태 관리) - Controller 제거, 비즈니스 로직을 DislikedFoodButton으로 이동 - 선택 해제 기능 제거 (다른 항목 선택으로만 변경 가능) - DislikeStep을 선언적 컴포넌트로 단순화 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * refactor: DistanceStep 컴포넌트 분리 및 개선 - DistanceSelector 컴포넌트 분리 (선택 UI 담당) - 선택 해제 기능 제거 (다른 거리 선택으로만 변경 가능) - REGION_OPTIONS 접근 방식 수정 (.value → .id) Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * refactor: OpinionView를 form 태그로 감싸고 onSubmit으로 제출 처리 - form 태그 추가 및 onSubmit 이벤트 핸들러로 변경 - 엔터 키로 제출 가능하도록 개선 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * feat: PreferredMenu 타입 추가 및 OpinionForm에 적용 - PreferredMenu 타입을 Partial<Record<RankKey, FoodCategory>>로 정의 - OpinionForm의 preferredMenus 필드에 PreferredMenu 타입 적용 - 타입 안정성 강화 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * fix: preferredMenus 내 Zod Schema 옵션을 간소화 하여 복잡도 감소 * chore: prettier 포맷에 맞지 않았던 코드 수정 * feat: zod 및 @hookform/resolvers 라이브러리 설치 * refactor: Opinion Form 컴포넌트 구조 개선 및 상수 리팩토링 (#56) * feat: DislikeStep 및 PreferenceStep용 custom hooks 추가 - useDislikeStep: 단일 선택 로직 및 검증 - usePreferenceStep: rank별 선택, 중복 체크, 검증 로직 - hooks/gathering/index.ts에 export 추가 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * refactor: DistanceStep 개선 및 DistanceSelector 분리 - DistanceSelector를 별도 파일로 분리 - useWatch compute로 validation 간소화 - Chip 그룹 로직을 컴포넌트로 캡슐화 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * refactor: OpinionForm 타입을 OpinionFormSchema로 마이그레이션 - Zod inferred type 사용으로 타입 안정성 강화 - PreferredMenu를 Partial 타입으로 변경 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * refactor: VoteSummarySection을 ResultView에 통합 - VoteSummarySection 컴포넌트 제거 - votes 렌더링 로직을 ResultView로 이동 - VoteList 분리로 관심사 분리 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * refactor: FoodCard를 FoodCategoryCarousel에 inline 통합 - FoodCard 타입 개선 - 상수 import 경로 변경 - CompleteView 공백 정리 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * refactor: restaurantCard를 pageComponents/gathering으로 이동 - src/components/restaurantCard → src/pageComponents/gathering/restaurantCard - 도메인별 컴포넌트 구조 정리 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * refactor: Enum 구조를 일관되게 재설계 - DISTANCE_RANGE, DISTANCE_RANGE_LABEL enum 추가 - FOOD_CATEGORY_LABEL enum 추가 - REGION, REGION_LABEL enum 추가 - Single Source of Truth 패턴 적용 - 모든 enum은 UPPER_CASE 네이밍 사용 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * refactor: recommend-result 폴더명을 recommendResult로 변경 - kebab-case → camelCase 통일 - src/apis/recommend-result → src/apis/recommendResult - src/hooks/apis/recommend-result → src/hooks/apis/recommendResult Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * refactor: RegionChip 컴포넌트 분리 및 useController 적용 - RegionChip: 단일 Chip 렌더링, useController 패턴 - RegionStep: REGION_OPTIONS map하여 RegionChip 사용 - 선택 로직을 컴포넌트 내부로 캡슐화 Co-Authored-By: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * fix: 병합 충돌 과정에서 발생한 Typescript 이슈 수정 * chore: prettier 포맷에 맞지 않았던 코드 수정 * fix: 이미 제거되었던 Hook 이 작업 이력에 남았던 문제 수정 * feat: 공덕역 항목을 Region 상수에 추가 * fix: Document 제거 * fix: gathering domain 내 타입 정의 모듈의 위치를 수정 --------- Co-authored-by: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * fix: 코드 베이스 상 수정이 필요한 부분 반영 * fix: es-toolkit 을 활용하여 일부 로직 구문 최적화 수행 --------- Co-authored-by: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> --------- Co-authored-by: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com> * fix: 더 이상 사용하지 않는 Type 모듈 제거 --------- Co-authored-by: Claude (us.anthropic.claude-sonnet-4-5-20250929-v1:0) <noreply@anthropic.com>
1 parent d33706f commit b5b194f

Some content is hidden

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

66 files changed

+1213
-911
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
"use client";
2+
3+
import { useParams, useRouter } from "next/navigation";
4+
5+
import {
6+
IntroStep,
7+
DistanceStepContent,
8+
DistanceStepFooter,
9+
DislikeStepContent,
10+
DislikeStepFooter,
11+
PreferenceStepContent,
12+
PreferenceStepFooter,
13+
} from "#/pageComponents/gathering/opinion";
14+
import { StepTransition } from "#/components/stepTransition";
15+
import { useOpinionForm, useOpinionFunnel } from "#/hooks/gathering";
16+
import { Button } from "#/components/button";
17+
import { Layout } from "#/components/layout";
18+
import { FormProvider } from "react-hook-form";
19+
import { BackwardButton } from "#/components/backwardButton";
20+
import { Toaster } from "#/components/toast";
21+
import { useGetGathering } from "#/hooks/apis/gathering";
22+
23+
export default function OpinionView() {
24+
const { accessKey } = useParams<{ accessKey: string }>();
25+
const router = useRouter();
26+
27+
const { methods, onSubmit } = useOpinionForm();
28+
const { step, direction, next, back, isFirstStep } = useOpinionFunnel();
29+
const { data: gathering } = useGetGathering(accessKey);
30+
31+
const handleBackward = () => {
32+
if (isFirstStep) {
33+
router.push(`/gathering/${accessKey}`);
34+
} else {
35+
back();
36+
}
37+
};
38+
39+
if (step === "intro") {
40+
return (
41+
<>
42+
<Layout.Header background="gray">
43+
<div className="ygi:h-full ygi:w-full" />
44+
</Layout.Header>
45+
<Layout.Content background="gray">
46+
<IntroStep scheduledDate={gathering.scheduledDate} />
47+
</Layout.Content>
48+
<Layout.Footer background="gray">
49+
<div className="ygi:py-auto ygi:px-6">
50+
<Button variant="primary" width="full" onClick={next}>
51+
내 취향 입력
52+
</Button>
53+
</div>
54+
</Layout.Footer>
55+
</>
56+
);
57+
}
58+
59+
const renderContent = () => {
60+
switch (step) {
61+
case "distance":
62+
return <DistanceStepContent region={gathering.region} />;
63+
case "dislike":
64+
return <DislikeStepContent />;
65+
case "preference":
66+
return <PreferenceStepContent />;
67+
default:
68+
return null;
69+
}
70+
};
71+
72+
const renderFooter = () => {
73+
switch (step) {
74+
case "distance":
75+
return <DistanceStepFooter onNext={next} />;
76+
case "dislike":
77+
return <DislikeStepFooter onNext={next} />;
78+
case "preference":
79+
return <PreferenceStepFooter />;
80+
default:
81+
return null;
82+
}
83+
};
84+
85+
return (
86+
<FormProvider {...methods}>
87+
<form onSubmit={onSubmit}>
88+
<Layout.Header>
89+
<BackwardButton onClick={handleBackward} />
90+
</Layout.Header>
91+
<Layout.Content>
92+
<StepTransition step={step} direction={direction}>
93+
{renderContent()}
94+
</StepTransition>
95+
</Layout.Content>
96+
{renderFooter()}
97+
<Toaster
98+
offset={{ bottom: 96 }}
99+
mobileOffset={{ bottom: 96 }}
100+
/>
101+
</form>
102+
</FormProvider>
103+
);
104+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"use client";
2+
3+
import { Button } from "#/components/button";
4+
import { Layout } from "#/components/layout";
5+
import {
6+
CompleteView,
7+
SubmissionBottomSheet,
8+
} from "#/pageComponents/gathering/opinion";
9+
import { useParams, redirect } from "next/navigation";
10+
import { useGetGatheringCapacity } from "#/hooks/apis/gathering";
11+
12+
export default function CompleteViewContainer() {
13+
const { accessKey } = useParams<{ accessKey: string }>();
14+
const { data: capacity } = useGetGatheringCapacity(accessKey);
15+
16+
const isPending = capacity.currentCount < capacity.maxCount;
17+
18+
if (isPending) {
19+
redirect(`/gathering/${accessKey}/opinion/pending`);
20+
}
21+
22+
const handleRedirectResult = () => {
23+
redirect(`/gathering/${accessKey}/opinion/result`);
24+
};
25+
26+
return (
27+
<Layout.Root>
28+
<CompleteView />
29+
30+
<SubmissionBottomSheet
31+
maxCount={capacity.maxCount}
32+
currentCount={capacity.currentCount}
33+
/>
34+
35+
<Layout.Footer>
36+
<div className="ygi:px-6">
37+
<Button
38+
variant="primary"
39+
width="full"
40+
onClick={handleRedirectResult}
41+
>
42+
추천 결과 보기
43+
</Button>
44+
</div>
45+
</Layout.Footer>
46+
</Layout.Root>
47+
);
48+
}
Lines changed: 25 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,33 @@
1-
"use client";
2-
3-
import { Button } from "#/components/button";
4-
import { Layout } from "#/components/layout";
51
import {
6-
CompleteView,
7-
SubmissionBottomSheet,
8-
} from "#/pageComponents/gathering/opinion";
9-
import { useParams } from "next/navigation";
10-
import { redirect } from "next/navigation";
11-
12-
export default function OpinionCompletePage() {
13-
const { accessKey } = useParams<{ accessKey: string }>();
14-
15-
// TODO: API 연동 시 실제 데이터로 교체
16-
const totalCount = 5;
17-
const submittedCount = 5;
18-
19-
const isPending = submittedCount < totalCount;
2+
HydrationBoundary,
3+
QueryClient,
4+
dehydrate,
5+
} from "@tanstack/react-query";
6+
7+
import { gatheringOptions } from "#/apis/gathering";
8+
import CompleteView from "./CompleteView";
9+
10+
interface OpinionCompletePageProps {
11+
params: Promise<{
12+
accessKey: string;
13+
}>;
14+
}
2015

21-
if (isPending) {
22-
redirect(`/gathering/${accessKey}/opinion/pending`);
23-
}
16+
/**
17+
* 의견 수렴 완료 페이지 (서버 컴포넌트)
18+
* - gathering capacity 데이터를 서버에서 prefetch하여 무한 렌더링 방지
19+
*/
20+
export default async function OpinionCompletePage({
21+
params,
22+
}: OpinionCompletePageProps) {
23+
const { accessKey } = await params;
24+
const queryClient = new QueryClient();
2425

25-
const handleRedirectResult = () => {
26-
redirect(`/gathering/${accessKey}/opinion/result`);
27-
};
26+
await queryClient.prefetchQuery(gatheringOptions.capacity(accessKey));
2827

2928
return (
30-
<Layout.Root>
29+
<HydrationBoundary state={dehydrate(queryClient)}>
3130
<CompleteView />
32-
33-
<SubmissionBottomSheet
34-
totalCount={totalCount}
35-
submittedCount={submittedCount}
36-
/>
37-
38-
<Layout.Footer>
39-
<div className="ygi:px-6">
40-
<Button
41-
variant="primary"
42-
width="full"
43-
onClick={handleRedirectResult}
44-
>
45-
추천 결과 보기
46-
</Button>
47-
</div>
48-
</Layout.Footer>
49-
</Layout.Root>
31+
</HydrationBoundary>
5032
);
5133
}
Lines changed: 25 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,32 @@
1-
"use client";
2-
3-
import { useParams, useRouter } from "next/navigation";
4-
51
import {
6-
IntroStep,
7-
DistanceStepContent,
8-
DistanceStepFooter,
9-
DislikeStepContent,
10-
DislikeStepFooter,
11-
PreferenceStepContent,
12-
PreferenceStepFooter,
13-
} from "#/pageComponents/gathering/opinion";
14-
import { StepTransition } from "#/components/stepTransition";
15-
import { useOpinionForm, useOpinionFunnel } from "#/hooks/gathering";
16-
import { Button } from "#/components/button";
17-
import { Layout } from "#/components/layout";
18-
import { FormProvider } from "react-hook-form";
19-
import { BackwardButton } from "#/components/backwardButton";
20-
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";
24-
25-
export default function OpinionPage() {
26-
const { accessKey } = useParams<{ accessKey: string }>();
27-
const router = useRouter();
28-
29-
const form = useOpinionForm();
30-
const { step, direction, next, back, isFirstStep } = useOpinionFunnel();
31-
32-
const meetingContext = useMemo<MeetingContext>(
33-
() => ({
34-
accessKey,
35-
scheduledDate: MOCK_MEETING_DATA.DATE,
36-
stationName: MOCK_MEETING_DATA.STATION_NAME,
37-
}),
38-
[accessKey],
39-
);
40-
41-
const handleBackward = () => {
42-
if (isFirstStep) {
43-
router.push(`/gathering/${accessKey}`);
44-
} else {
45-
back();
46-
}
47-
};
48-
49-
const handleComplete = () => {
50-
router.replace(`/gathering/${accessKey}/opinion/pending`);
51-
};
52-
53-
if (step === "intro") {
54-
return (
55-
<>
56-
<Layout.Header background="gray">
57-
<div className="ygi:h-full ygi:w-full" />
58-
</Layout.Header>
59-
<Layout.Content background="gray">
60-
{/* TODO : API 연동 과정에서 대체가 필요한 코드 */}
61-
<IntroStep
62-
meetingContext={meetingContext}
63-
step="intro"
64-
onNext={next}
65-
/>
66-
</Layout.Content>
67-
<Layout.Footer background="gray">
68-
<div className="ygi:py-auto ygi:px-6">
69-
<Button variant="primary" width="full" onClick={next}>
70-
내 취향 입력
71-
</Button>
72-
</div>
73-
</Layout.Footer>
74-
</>
75-
);
76-
}
2+
HydrationBoundary,
3+
QueryClient,
4+
dehydrate,
5+
} from "@tanstack/react-query";
6+
7+
import { gatheringOptions } from "#/apis/gathering";
8+
import OpinionView from "./OpinionView";
9+
10+
interface OpinionPageProps {
11+
params: Promise<{
12+
accessKey: string;
13+
}>;
14+
}
7715

78-
const renderContent = () => {
79-
switch (step) {
80-
case "distance":
81-
return <DistanceStepContent meetingContext={meetingContext} />;
82-
case "dislike":
83-
return <DislikeStepContent />;
84-
case "preference":
85-
return <PreferenceStepContent />;
86-
default:
87-
return null;
88-
}
89-
};
16+
/**
17+
* 의견 수렴 페이지 (서버 컴포넌트)
18+
* - gathering 데이터를 서버에서 prefetch하여 무한 렌더링 방지
19+
*/
20+
export default async function OpinionPage({ params }: OpinionPageProps) {
21+
const { accessKey } = await params;
22+
const queryClient = new QueryClient();
9023

91-
const renderFooter = () => {
92-
switch (step) {
93-
case "distance":
94-
return <DistanceStepFooter onNext={next} />;
95-
case "dislike":
96-
return <DislikeStepFooter onNext={next} />;
97-
case "preference":
98-
return <PreferenceStepFooter onComplete={handleComplete} />;
99-
default:
100-
return null;
101-
}
102-
};
24+
// 서버에서 gathering 데이터 미리 가져오기
25+
await queryClient.prefetchQuery(gatheringOptions.detail(accessKey));
10326

10427
return (
105-
<FormProvider {...form}>
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()}
115-
<Toaster offset={{ bottom: 96 }} mobileOffset={{ bottom: 96 }} />
116-
</FormProvider>
28+
<HydrationBoundary state={dehydrate(queryClient)}>
29+
<OpinionView />
30+
</HydrationBoundary>
11731
);
11832
}

0 commit comments

Comments
 (0)