Skip to content

Commit 4c34c8f

Browse files
youngminssclaudeRookieAND
authored
refactor: 모임 생성 폼 리팩토링 (PR 리뷰 대응) (#49)
* refactor: 모임 생성 폼 리팩토링 (PR 리뷰 대응) - mutationOptions 함수 사용하여 queryOptions/mutationOptions 파일 분리 - Step 컴포넌트에 useController 적용하여 setValue + useWatch 제거 - 커스텀 validation 훅 제거하고 field.value로 직접 유효성 검사 - form submit을 page.tsx로 위임하여 react-hook-form에 제출 액션 위임 - Chip 컴포넌트에 type="button" 기본값 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: 코드 포맷팅 * fix: queryOptions 모듈 분기 처리 과정에서 capacity 가 누락되었던 이슈 수정 --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: RookieAND <gwangin1999@naver.com>
1 parent ee3cde5 commit 4c34c8f

File tree

17 files changed

+187
-160
lines changed

17 files changed

+187
-160
lines changed

app/gathering/[accessKey]/opinion/complete/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
dehydrate,
55
} from "@tanstack/react-query";
66

7-
import { gatheringOptions } from "#/apis/gathering";
7+
import { gatheringQueryOptions } from "#/apis/gathering";
88
import CompleteView from "./CompleteView";
99

1010
interface OpinionCompletePageProps {
@@ -23,7 +23,7 @@ export default async function OpinionCompletePage({
2323
const { accessKey } = await params;
2424
const queryClient = new QueryClient();
2525

26-
await queryClient.prefetchQuery(gatheringOptions.capacity(accessKey));
26+
await queryClient.prefetchQuery(gatheringQueryOptions.capacity(accessKey));
2727

2828
return (
2929
<HydrationBoundary state={dehydrate(queryClient)}>

app/gathering/[accessKey]/opinion/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
dehydrate,
55
} from "@tanstack/react-query";
66

7-
import { gatheringOptions } from "#/apis/gathering";
7+
import { gatheringQueryOptions } from "#/apis/gathering";
88
import OpinionView from "./OpinionView";
99

1010
interface OpinionPageProps {
@@ -22,7 +22,7 @@ export default async function OpinionPage({ params }: OpinionPageProps) {
2222
const queryClient = new QueryClient();
2323

2424
// 서버에서 gathering 데이터 미리 가져오기
25-
await queryClient.prefetchQuery(gatheringOptions.detail(accessKey));
25+
await queryClient.prefetchQuery(gatheringQueryOptions.detail(accessKey));
2626

2727
return (
2828
<HydrationBoundary state={dehydrate(queryClient)}>

app/gathering/[accessKey]/opinion/pending/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
dehydrate,
55
} from "@tanstack/react-query";
66

7-
import { gatheringOptions } from "#/apis/gathering";
7+
import { gatheringQueryOptions } from "#/apis/gathering";
88
import PendingView from "./PendingView";
99

1010
interface OpinionPendingPageProps {
@@ -23,7 +23,7 @@ export default async function OpinionPendingPage({
2323
const { accessKey } = await params;
2424
const queryClient = new QueryClient();
2525

26-
await queryClient.prefetchQuery(gatheringOptions.capacity(accessKey));
26+
await queryClient.prefetchQuery(gatheringQueryOptions.capacity(accessKey));
2727

2828
return (
2929
<HydrationBoundary state={dehydrate(queryClient)}>

app/gathering/[accessKey]/opinion/result/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
dehydrate,
55
} from "@tanstack/react-query";
66

7-
import { gatheringOptions } from "#/apis/gathering";
7+
import { gatheringQueryOptions } from "#/apis/gathering";
88
import { recommendResultOptions } from "#/apis/recommendResult";
99
import ResultView from "./ResultView";
1010

@@ -25,7 +25,7 @@ export default async function OpinionResultPage({
2525
const queryClient = new QueryClient();
2626

2727
await Promise.all([
28-
queryClient.prefetchQuery(gatheringOptions.capacity(accessKey)),
28+
queryClient.prefetchQuery(gatheringQueryOptions.capacity(accessKey)),
2929
queryClient.prefetchQuery(recommendResultOptions.detail(accessKey)),
3030
]);
3131

app/gathering/create/page.tsx

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,20 @@ import {
1818
useCreateMeetingForm,
1919
useCreateMeetingFunnel,
2020
} from "#/hooks/gathering";
21+
import { useCreateGathering } from "#/hooks/apis/gathering";
2122
import { Toaster } from "#/components/toast";
23+
import { isApiError } from "#/utils/api";
24+
import { toast } from "#/utils/toast";
25+
import type { CreateMeetingForm } from "#/types/gathering";
2226

2327
export default function GatheringCreatePage() {
2428
const router = useRouter();
2529
const form = useCreateMeetingForm();
2630
const { step, direction, next, back, isFirstStep } =
2731
useCreateMeetingFunnel();
2832

33+
const { mutate: createGathering, isPending } = useCreateGathering();
34+
2935
const handleBackward = () => {
3036
if (isFirstStep) {
3137
router.push("/");
@@ -38,6 +44,36 @@ export default function GatheringCreatePage() {
3844
router.push(`/gathering/create/complete/${accessKey}`);
3945
};
4046

47+
const onSubmit = (formData: CreateMeetingForm) => {
48+
if (
49+
!formData.peopleCount ||
50+
!formData.region ||
51+
!formData.scheduledDate ||
52+
!formData.timeSlot
53+
) {
54+
return;
55+
}
56+
57+
createGathering(
58+
{
59+
peopleCount: formData.peopleCount,
60+
region: formData.region,
61+
scheduledDate: formData.scheduledDate.replace(/\./g, "-"),
62+
timeSlot: formData.timeSlot,
63+
},
64+
{
65+
onSuccess: (response) => {
66+
handleComplete(response.data.accessKey);
67+
},
68+
onError: (error) => {
69+
if (isApiError(error)) {
70+
toast.warning(error.message);
71+
}
72+
},
73+
},
74+
);
75+
};
76+
4177
const renderContent = () => {
4278
switch (step) {
4379
case "people":
@@ -58,7 +94,7 @@ export default function GatheringCreatePage() {
5894
case "date":
5995
return <DateStepFooter onNext={next} />;
6096
case "region":
61-
return <RegionStepFooter onComplete={handleComplete} />;
97+
return <RegionStepFooter isPending={isPending} />;
6298
default:
6399
return null;
64100
}
@@ -67,19 +103,21 @@ export default function GatheringCreatePage() {
67103
return (
68104
<FormProvider {...form}>
69105
<Layout.Root>
70-
<Layout.Header>
71-
{step !== "people" && (
72-
<BackwardButton onClick={handleBackward} />
73-
)}
74-
</Layout.Header>
106+
<form onSubmit={form.handleSubmit(onSubmit)}>
107+
<Layout.Header>
108+
{step !== "people" && (
109+
<BackwardButton onClick={handleBackward} />
110+
)}
111+
</Layout.Header>
75112

76-
<Layout.Content>
77-
<StepTransition step={step} direction={direction}>
78-
{renderContent()}
79-
</StepTransition>
80-
</Layout.Content>
113+
<Layout.Content>
114+
<StepTransition step={step} direction={direction}>
115+
{renderContent()}
116+
</StepTransition>
117+
</Layout.Content>
81118

82-
{renderFooter()}
119+
{renderFooter()}
120+
</form>
83121

84122
<Toaster
85123
offset={{ bottom: 96 }}

src/apis/gathering/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ export type {
66
GatheringCapacityResponse,
77
} from "./type";
88

9-
// Query Key & Option
9+
// Query Key & Options
1010
export { gatheringKeys } from "./queryKey";
11-
export { gatheringOptions } from "./queryOption";
11+
export { gatheringQueryOptions } from "./queryOptions";
12+
export { gatheringMutationOptions } from "./mutationOptions";
1213

1314
// API
1415
export { createGathering, getGathering, getGatheringCapacity } from "./api";
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { mutationOptions } from "@tanstack/react-query";
2+
3+
import { gatheringKeys } from "./queryKey";
4+
import { createGathering } from "./api";
5+
6+
/**
7+
* 모임 관련 Mutation Options Factory
8+
*/
9+
export const gatheringMutationOptions = {
10+
create: () =>
11+
mutationOptions({
12+
mutationKey: gatheringKeys.create(),
13+
mutationFn: createGathering,
14+
}),
15+
};

src/apis/gathering/queryOption.ts

Lines changed: 0 additions & 30 deletions
This file was deleted.

src/apis/gathering/queryOptions.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { queryOptions } from "@tanstack/react-query";
2+
3+
import { gatheringKeys } from "./queryKey";
4+
import { getGathering, getGatheringCapacity } from "./api";
5+
6+
/**
7+
* 모임 관련 Query Options Factory
8+
*/
9+
export const gatheringQueryOptions = {
10+
detail: (accessKey: string) =>
11+
queryOptions({
12+
queryKey: gatheringKeys.detail(accessKey),
13+
queryFn: () => getGathering(accessKey),
14+
}),
15+
capacity: (accessKey: string) =>
16+
queryOptions({
17+
queryKey: gatheringKeys.capacity(accessKey),
18+
queryFn: () => getGatheringCapacity(accessKey),
19+
}),
20+
};
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { useMutation } from "@tanstack/react-query";
22

3-
import { gatheringOptions } from "#/apis/gathering";
3+
import { gatheringMutationOptions } from "#/apis/gathering";
44

55
/**
66
* 모임 생성 mutation hook
77
*/
88
export const useCreateGathering = () => {
9-
return useMutation(gatheringOptions.create());
9+
return useMutation(gatheringMutationOptions.create());
1010
};

0 commit comments

Comments
 (0)