Skip to content

Commit 6bec971

Browse files
author
jyn
committed
Merge branch 'dev'
2 parents fad6c7c + b22aa66 commit 6bec971

File tree

24 files changed

+491
-111
lines changed

24 files changed

+491
-111
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"lucide-react": "^0.525.0",
3333
"next": "15.3.5",
3434
"react": "^19.0.0",
35+
"react-daum-postcode": "^2.0.0",
3536
"react-dom": "^19.0.0",
3637
"react-hook-form": "^7.60.0",
3738
"react-kakao-maps-sdk": "^1.2.0",

pnpm-lock.yaml

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/layout.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@ export default function RootLayout({
2626
}>) {
2727
return (
2828
<html lang="ko" className={`${pretendard.variable}`}>
29-
<body className={`${pretendard.className} bg-background`}>
29+
<body
30+
className={`${pretendard.className} bg-background flex min-h-screen flex-col`}
31+
>
3032
<Providers>
3133
<HeaderWrapper />
32-
<main>{children}</main>
34+
<main className="flex-1">{children}</main>
3335
<Footer />
3436
</Providers>
3537
</body>

src/app/my/my-activities/activity-registration/page.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import ActivityRegistrationForm from '@/features/activity-registration/components/activity-registration-form';
2+
import { AuthGuard } from '@/shared/components/auth/AuthGuard';
23

34
const ActivityRegistrationPage = () => {
45
return (
@@ -13,4 +14,10 @@ const ActivityRegistrationPage = () => {
1314
);
1415
};
1516

16-
export default ActivityRegistrationPage;
17+
export default function GuardActivityRegistrationPage() {
18+
return (
19+
<AuthGuard>
20+
<ActivityRegistrationPage />
21+
</AuthGuard>
22+
);
23+
}

src/app/my/my-activities/page.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
1+
'use client';
12
import Image from 'next/image';
23
import Link from 'next/link';
34

45
import { DeleteConfirmModal } from '@/features/my/my-activities/components/delete-confirm-modal';
56
import { MyActivitiesList } from '@/features/my/my-activities/components/my-activities-list';
7+
import { useModalStore } from '@/shared/libs/stores/useModalStore';
68

79
const MyActivityPage = () => {
10+
const { isModalOpen } = useModalStore();
811
return (
912
<>
1013
{/* 페이지 헤더 */}
1114
<div className="mb-[2rem] flex items-start justify-between py-[1rem] md:mb-[2.4rem]">
1215
<header className="flex flex-col gap-[0.4rem]">
1316
<h1 className="text-[1.8rem] font-bold text-gray-950">내 체험관리</h1>
1417
<span className="text-[1.4rem] font-medium text-gray-500">
15-
체험을 등록하거나 수정 및 삭제가 가능합니다.{' '}
18+
체험을 등록하거나 수정 및 삭제가 가능합니다.
1619
</span>
1720
</header>
1821
<Link
@@ -34,7 +37,7 @@ const MyActivityPage = () => {
3437
{/* 내 체험 리스트 */}
3538
<MyActivitiesList />
3639
{/* 삭제 확인 모달 */}
37-
<DeleteConfirmModal />
40+
{isModalOpen && <DeleteConfirmModal />}
3841
</>
3942
);
4043
};

src/features/activities/components/search.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22

33
import { SearchIcon } from 'lucide-react';
44
import { useRouter, useSearchParams } from 'next/navigation';
5-
import React, { useState } from 'react';
5+
import React, { useEffect, useState } from 'react';
66

77
import { searchVariant } from '@/shared/libs/constants/searchVariant';
88

99
interface SearchProps {
1010
placeholder?: string;
11+
setKeyword: (value: React.SetStateAction<string>) => void;
1112
}
1213

1314
const Search: React.FC<SearchProps> = ({
@@ -19,6 +20,10 @@ const Search: React.FC<SearchProps> = ({
1920
const [keyword, setKeyword] = useState(defaultValue);
2021
const [isFocused, setIsFocused] = useState(false);
2122

23+
useEffect(() => {
24+
setKeyword(defaultValue);
25+
}, [defaultValue]);
26+
2227
const handleSearch = () => {
2328
const trimmed = keyword.trim();
2429
if (!trimmed) {
@@ -49,7 +54,7 @@ const Search: React.FC<SearchProps> = ({
4954
placeholder={isFocused ? '' : placeholder}
5055
className={style.input}
5156
/>
52-
<button onClick={handleSearch} className={style.button}>
57+
<button type="button" onClick={handleSearch} className={style.button}>
5358
검색하기
5459
</button>
5560
</div>

src/features/activity-registration/components/activity-registration-form.tsx

Lines changed: 57 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { zodResolver } from '@hookform/resolvers/zod';
44
import { useRouter } from 'next/navigation';
55
import { useForm } from 'react-hook-form';
6+
import { toast } from 'sonner';
67
import z from 'zod';
78

89
import BannerImageUpload from '@/features/activity-registration/components/banner-image-upload';
@@ -15,14 +16,10 @@ import {
1516
} from '@/features/activity-registration/libs/constants/formOption';
1617
import { useRegistrationMutation } from '@/features/activity-registration/libs/hooks/useRegistrationMutation';
1718
import { FormInput } from '@/shared/components/form-input/form-input';
18-
import Modal from '@/shared/components/modal/components';
19-
import { useModalStore } from '@/shared/libs/stores/useModalStore';
20-
import {
21-
ActivityRegistrationFormData,
22-
ActivityRegistrationParams,
23-
} from '@/shared/types/activity';
19+
import { ActivityRegistrationParams } from '@/shared/types/activity';
20+
21+
// 기존 hasDuplicateStartTime 함수를 사용
2422

25-
// 업데이트된 스키마 - schedules 배열로 변경
2623
const registerSchema = z.object({
2724
title: z.string().min(1, { message: '제목을 입력해 주세요.' }),
2825
category: z.string().min(1, { message: '카테고리를 선택해 주세요.' }),
@@ -39,29 +36,33 @@ const registerSchema = z.object({
3936
schedules: z
4037
.array(
4138
z.object({
42-
date: z.string().min(1, { message: '날짜를 선택해 주세요.' }),
39+
date: z.string().min(1, {
40+
message: '날짜를 선택해 주세요.',
41+
}),
4342
startTime: z.string().min(1, { message: '시작 시간을 선택해 주세요.' }),
4443
endTime: z.string().min(1, { message: '종료 시간을 선택해 주세요.' }),
4544
}),
4645
)
47-
.min(FORM_CONSTRAINTS.SCHEDULES.MIN_COUNT, {
48-
message: '최소 하나의 시간대를 등록해 주세요.',
49-
}),
46+
.min(1, { message: '예약 가능한 시간대는 최소 1개 이상 등록해주세요.' }),
5047
bannerImages: z.string().min(1, { message: '배너 이미지를 등록해 주세요.' }),
5148
subImages: z
5249
.array(z.string())
5350
.min(1, { message: '소개 이미지를 등록해 주세요.' }),
5451
});
5552

53+
type FormData = z.infer<typeof registerSchema>;
54+
5655
const ActivityRegistrationForm = () => {
5756
const {
5857
register,
5958
handleSubmit,
6059
watch,
6160
setValue,
61+
trigger,
6262
formState: { errors },
63-
} = useForm<ActivityRegistrationFormData>({
63+
} = useForm<FormData>({
6464
resolver: zodResolver(registerSchema),
65+
mode: 'onChange',
6566
defaultValues: {
6667
schedules: [],
6768
bannerImages: '',
@@ -70,10 +71,11 @@ const ActivityRegistrationForm = () => {
7071
});
7172

7273
const registrationMutation = useRegistrationMutation();
73-
const { openModal, closeModal } = useModalStore();
7474
const router = useRouter();
7575

76-
const onSubmit = (data: ActivityRegistrationFormData) => {
76+
const onSubmit = async (data: FormData) => {
77+
console.log('폼 제출 시작:', data); // 디버깅용
78+
7779
// 모든 스케줄의 시간 유효성 검증
7880
const hasInvalidSchedules = data.schedules.some((schedule) => {
7981
const startIndex = TIME_OPTIONS.findIndex(
@@ -90,30 +92,47 @@ const ActivityRegistrationForm = () => {
9092
return;
9193
}
9294

95+
// 중복 시간 체크는 DateScheduler에서 처리되므로 제거
96+
9397
// API 데이터 변환
9498
const apiData: ActivityRegistrationParams = {
9599
title: data.title,
96100
category: data.category,
97101
description: data.description,
98102
address: data.address,
99-
price: data.price,
103+
price: data.price as number,
100104
bannerImageUrl: data.bannerImages,
101105
schedules: data.schedules,
102106
subImageUrls: data.subImages,
103107
};
104108

109+
console.log('API 데이터:', apiData); // 디버깅용
110+
105111
// TanStack Query mutation 실행
106-
registrationMutation.mutate(apiData, {
107-
onSuccess: () => {
108-
openModal();
109-
},
110-
});
112+
try {
113+
await registrationMutation.mutateAsync(apiData);
114+
console.log('등록 성공!'); // 디버깅용
115+
toast.success('체험이 등록되었습니다!');
116+
router.push('/activities');
117+
} catch (error) {
118+
console.error('등록 실패:', error); // 디버깅용
119+
toast.error('등록 중 오류가 발생했습니다. 다시 시도해주세요.');
120+
}
111121
};
112122

113123
return (
114124
<form
115125
onSubmit={handleSubmit(onSubmit)}
116126
className="mt-[2.4rem] flex flex-col"
127+
onKeyDown={(e) => {
128+
if (e.key === 'Enter' && e.target instanceof HTMLInputElement) {
129+
const target = e.target as HTMLInputElement;
130+
if (target.type === 'button' || target.readOnly) {
131+
e.preventDefault();
132+
e.stopPropagation();
133+
}
134+
}
135+
}}
117136
>
118137
{/* 제목 */}
119138
<FormInput
@@ -166,33 +185,46 @@ const ActivityRegistrationForm = () => {
166185
label="주소"
167186
name="address"
168187
register={register}
188+
setValue={setValue}
189+
watch={watch}
169190
error={errors.address}
170-
inputType="input"
191+
inputType="address"
171192
placeholder="주소를 입력해 주세요"
172193
required
173194
/>
174195

175196
{/* DateScheduler 컴포넌트 사용 */}
176197
<DateScheduler
177198
schedules={watch('schedules') || []}
178-
onChange={(schedules) => setValue('schedules', schedules)}
199+
onChange={async (schedules) => {
200+
setValue('schedules', schedules);
201+
await trigger('schedules');
202+
}}
179203
errors={{ schedules: errors.schedules }}
204+
register={register}
205+
formErrors={errors}
180206
/>
181207

182208
<BannerImageUpload
183209
label="배너 이미지 등록"
184210
name="bannerImages"
185211
error={errors.bannerImages}
186212
required
187-
onChange={(imageUrl: string) => setValue('bannerImages', imageUrl)}
213+
onChange={async (imageUrl: string) => {
214+
setValue('bannerImages', imageUrl);
215+
await trigger('bannerImages');
216+
}}
188217
value={watch('bannerImages') || ''}
189218
/>
190219

191220
<SubImage
192221
label="소개 이미지 등록"
193222
name="subImages"
194223
error={errors.subImages}
195-
onChange={(imageUrls: string[]) => setValue('subImages', imageUrls)}
224+
onChange={async (imageUrls: string[]) => {
225+
setValue('subImages', imageUrls);
226+
await trigger('subImages');
227+
}}
196228
value={watch('subImages') || []}
197229
/>
198230

@@ -201,27 +233,14 @@ const ActivityRegistrationForm = () => {
201233
<button
202234
type="submit"
203235
disabled={registrationMutation.isPending}
204-
className={`h-[4.1rem] w-[16rem] rounded-[1.2rem] text-center text-[1.4rem] font-bold text-white md:h-[4.8rem] md:w-[16rem] md:rounded-[1.6rem] md:text-[1.6rem] lg:h-[5.2rem] lg:w-[18rem] ${
236+
className={`h-[4.1rem] w-[16rem] cursor-pointer rounded-[1.2rem] text-center text-[1.4rem] font-bold text-white md:h-[4.8rem] md:w-[16rem] md:rounded-[1.6rem] md:text-[1.6rem] lg:h-[5.2rem] lg:w-[18rem] ${
205237
registrationMutation.isPending
206238
? 'cursor-not-allowed bg-gray-400'
207239
: 'bg-main hover:bg-blue-500'
208240
}`}
209241
>
210242
{registrationMutation.isPending ? '등록 중...' : '체험 등록하기'}
211243
</button>
212-
<Modal type="confirm">
213-
<Modal.Header>체험 등록이 완료되었습니다.</Modal.Header>
214-
<Modal.Button
215-
color="blue"
216-
ariaLabel="확인 버튼"
217-
onClick={() => {
218-
closeModal();
219-
router.push('/activities');
220-
}}
221-
>
222-
확인
223-
</Modal.Button>
224-
</Modal>
225244
</div>
226245
</form>
227246
);

0 commit comments

Comments
 (0)