From 4b28d498907c281e8608e9227302a2c77c650526 Mon Sep 17 00:00:00 2001 From: 9g-g9 Date: Sun, 2 Aug 2026 22:24:30 +0900 Subject: [PATCH 01/18] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1,=20=EC=88=98=EC=A0=95=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=B6=94=EA=B0=80(=EC=9E=84=EC=8B=9C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(protected)/profile/edit/page.tsx | 14 ++ .../(customer)/(protected)/profile/page.tsx | 13 +- .../(protected)/mover/profile/edit/page.tsx | 14 ++ .../(protected)/mover/profile/page.tsx | 13 +- .../profile/CustomerProfileForm.tsx | 152 ++++++++++++++ src/components/profile/MoverProfileForm.tsx | 195 ++++++++++++++++++ src/components/profile/ProfileChipGroup.tsx | 89 ++++++++ src/components/profile/ProfileFieldHeader.tsx | 59 ++++++ .../profile/ProfileImageUploader.tsx | 87 ++++++++ src/components/profile/ProfilePageHeader.tsx | 35 ++++ .../profile/ProfileSelectableChip.tsx | 38 ++++ src/icons/gallery.svg | 6 + src/icons/index.ts | 1 + src/lib/constants/appRoutes.ts | 4 +- src/lib/schemas/customerProfileSchema.ts | 35 ++++ src/lib/schemas/moverProfileSchema.ts | 34 +++ src/types/profileFormMode.ts | 1 + 17 files changed, 767 insertions(+), 23 deletions(-) create mode 100644 src/app/(customer)/(protected)/profile/edit/page.tsx create mode 100644 src/app/(mover)/(protected)/mover/profile/edit/page.tsx create mode 100644 src/components/profile/CustomerProfileForm.tsx create mode 100644 src/components/profile/MoverProfileForm.tsx create mode 100644 src/components/profile/ProfileChipGroup.tsx create mode 100644 src/components/profile/ProfileFieldHeader.tsx create mode 100644 src/components/profile/ProfileImageUploader.tsx create mode 100644 src/components/profile/ProfilePageHeader.tsx create mode 100644 src/components/profile/ProfileSelectableChip.tsx create mode 100644 src/icons/gallery.svg create mode 100644 src/lib/schemas/customerProfileSchema.ts create mode 100644 src/lib/schemas/moverProfileSchema.ts create mode 100644 src/types/profileFormMode.ts diff --git a/src/app/(customer)/(protected)/profile/edit/page.tsx b/src/app/(customer)/(protected)/profile/edit/page.tsx new file mode 100644 index 00000000..2b71c008 --- /dev/null +++ b/src/app/(customer)/(protected)/profile/edit/page.tsx @@ -0,0 +1,14 @@ +import type { Metadata } from "next"; + +import CustomerProfileForm from "@/components/profile/CustomerProfileForm"; + +export const metadata: Metadata = { + title: "프로필 수정 | 무빙", + description: "무빙 고객 프로필 수정", +}; + +const CustomerProfileEditPage = () => { + return ; +}; + +export default CustomerProfileEditPage; diff --git a/src/app/(customer)/(protected)/profile/page.tsx b/src/app/(customer)/(protected)/profile/page.tsx index f621275c..949d2d2e 100644 --- a/src/app/(customer)/(protected)/profile/page.tsx +++ b/src/app/(customer)/(protected)/profile/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from "next"; -import { Text } from "@/components/common/Text"; +import CustomerProfileForm from "@/components/profile/CustomerProfileForm"; export const metadata: Metadata = { title: "프로필 등록 | 무빙", @@ -8,16 +8,7 @@ export const metadata: Metadata = { }; const CustomerProfilePage = () => { - return ( -
- - 고객 프로필 등록 - - - 프로필 등록 UI는 추후 연결됩니다. - -
- ); + return ; }; export default CustomerProfilePage; diff --git a/src/app/(mover)/(protected)/mover/profile/edit/page.tsx b/src/app/(mover)/(protected)/mover/profile/edit/page.tsx new file mode 100644 index 00000000..be56b8e1 --- /dev/null +++ b/src/app/(mover)/(protected)/mover/profile/edit/page.tsx @@ -0,0 +1,14 @@ +import type { Metadata } from "next"; + +import MoverProfileForm from "@/components/profile/MoverProfileForm"; + +export const metadata: Metadata = { + title: "기사 프로필 수정 | 무빙", + description: "무빙 기사님 프로필 수정", +}; + +const MoverProfileEditPage = () => { + return ; +}; + +export default MoverProfileEditPage; diff --git a/src/app/(mover)/(protected)/mover/profile/page.tsx b/src/app/(mover)/(protected)/mover/profile/page.tsx index 57c6cec9..a37b68a8 100644 --- a/src/app/(mover)/(protected)/mover/profile/page.tsx +++ b/src/app/(mover)/(protected)/mover/profile/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from "next"; -import { Text } from "@/components/common/Text"; +import MoverProfileForm from "@/components/profile/MoverProfileForm"; export const metadata: Metadata = { title: "기사 프로필 등록 | 무빙", @@ -8,16 +8,7 @@ export const metadata: Metadata = { }; const MoverProfilePage = () => { - return ( -
- - 기사님 프로필 등록 - - - 프로필 등록 UI는 추후 연결됩니다. - -
- ); + return ; }; export default MoverProfilePage; diff --git a/src/components/profile/CustomerProfileForm.tsx b/src/components/profile/CustomerProfileForm.tsx new file mode 100644 index 00000000..e07ec34e --- /dev/null +++ b/src/components/profile/CustomerProfileForm.tsx @@ -0,0 +1,152 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { Controller, useForm } from "react-hook-form"; + +import Button from "@/components/common/Button/Button"; +import ProfileChipGroup from "@/components/profile/ProfileChipGroup"; +import ProfileFieldHeader from "@/components/profile/ProfileFieldHeader"; +import ProfileImageUploader from "@/components/profile/ProfileImageUploader"; +import ProfilePageHeader from "@/components/profile/ProfilePageHeader"; +import { MOVE_TYPE_OPTIONS } from "@/lib/constants/moveType"; +import { REGION_OPTIONS, type RegionId } from "@/lib/constants/region"; +import { + customerProfileSchema, + type CustomerProfileFormValues, +} from "@/lib/schemas/customerProfileSchema"; +import type { ProfileFormMode } from "@/types/profileFormMode"; +import type { MoveType } from "@/types/move"; + +interface CustomerProfileFormProps { + mode?: ProfileFormMode; + defaultValues?: Partial; + initialImageUrl?: string | null; +} + +const COPY: Record = { + create: { + title: "프로필 등록", + description: "추가 정보를 입력하여 회원가입을 완료해주세요.", + submitLabel: "시작하기", + }, + edit: { + title: "프로필 수정", + description: "프로필 정보를 수정할 수 있어요.", + submitLabel: "수정하기", + }, +}; + +const CustomerProfileForm = ({ + mode = "create", + defaultValues, + initialImageUrl = null, +}: CustomerProfileFormProps) => { + const copy = COPY[mode]; + + const { + control, + handleSubmit, + formState: { errors, isValid, isSubmitting }, + } = useForm({ + resolver: zodResolver(customerProfileSchema), + mode: "onChange", + defaultValues: { + imageFile: null, + serviceTypes: [], + regionId: null, + ...defaultValues, + }, + }); + + const onSubmit = handleSubmit((values) => { + // Phase A: API 연동 전 — 제출 값 확인용 + console.log("[CustomerProfileForm]", mode, values); + }); + + return ( +
+ + +
+
+
+ + ( + + )} + /> +
+
+ +
+
+ + ( + + selectionMode="multiple" + options={MOVE_TYPE_OPTIONS} + value={field.value} + onChange={field.onChange} + error={errors.serviceTypes?.message} + /> + )} + /> +
+
+ +
+ + ( + + selectionMode="single" + options={REGION_OPTIONS} + value={field.value ?? null} + onChange={field.onChange} + error={errors.regionId?.message} + className="max-w-[277px] gap-x-8 gap-y-12 md:max-w-none" + /> + )} + /> +
+
+ + + + ); +}; + +export default CustomerProfileForm; diff --git a/src/components/profile/MoverProfileForm.tsx b/src/components/profile/MoverProfileForm.tsx new file mode 100644 index 00000000..5ca5bb55 --- /dev/null +++ b/src/components/profile/MoverProfileForm.tsx @@ -0,0 +1,195 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { Controller, useForm } from "react-hook-form"; + +import Button from "@/components/common/Button/Button"; +import Input from "@/components/common/Input/Input"; +import Textarea from "@/components/common/Input/Textarea"; +import ProfileChipGroup from "@/components/profile/ProfileChipGroup"; +import type { ProfileFormMode } from "@/types/profileFormMode"; +import ProfileFieldHeader from "@/components/profile/ProfileFieldHeader"; +import ProfileImageUploader from "@/components/profile/ProfileImageUploader"; +import ProfilePageHeader from "@/components/profile/ProfilePageHeader"; +import { MOVE_TYPE_OPTIONS } from "@/lib/constants/moveType"; +import { REGION_OPTIONS, type RegionId } from "@/lib/constants/region"; +import { moverProfileSchema, type MoverProfileFormValues } from "@/lib/schemas/moverProfileSchema"; +import type { MoveType } from "@/types/move"; + +interface MoverProfileFormProps { + mode?: ProfileFormMode; + defaultValues?: Partial; + initialImageUrl?: string | null; +} + +const COPY: Record = { + create: { + title: "기사님 프로필 등록", + description: "추가 정보를 입력하여 회원가입을 완료해주세요.", + submitLabel: "시작하기", + }, + edit: { + title: "기사님 프로필 수정", + description: "프로필 정보를 수정할 수 있어요.", + submitLabel: "수정하기", + }, +}; + +const MoverProfileForm = ({ + mode = "create", + defaultValues, + initialImageUrl = null, +}: MoverProfileFormProps) => { + const copy = COPY[mode]; + + const { + register, + control, + handleSubmit, + formState: { errors, isValid, isSubmitting }, + } = useForm({ + resolver: zodResolver(moverProfileSchema), + mode: "onChange", + defaultValues: { + imageFile: null, + nickname: "", + career: "", + shortIntro: "", + description: "", + serviceTypes: [], + regionIds: [], + ...defaultValues, + }, + }); + + const onSubmit = handleSubmit((values) => { + // Phase A: API 연동 전 — 제출 값 확인용 + console.log("[MoverProfileForm]", mode, values); + }); + + return ( +
+ + +
+
+
+ + ( + + )} + /> +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+
+ +