From 84d12754434d404fdcd8071a4c53417332ec14dc Mon Sep 17 00:00:00 2001 From: JaeHunKang Date: Sat, 7 Jun 2025 00:47:37 +0900 Subject: [PATCH 1/4] =?UTF-8?q?refact:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=97=B0=EB=9D=BD=EC=B2=98=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=EC=8B=9C=20=EC=9E=90=EB=8F=99=20=ED=95=98=EC=9D=B4=ED=94=88=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=95=88=EB=82=B4=20=EB=AC=B8?= =?UTF-8?q?=EA=B5=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/join/InfoInputPage.tsx | 33 ++++++++++++++++++++++++-------- src/shared/lib/formValidation.ts | 5 +---- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/pages/join/InfoInputPage.tsx b/src/pages/join/InfoInputPage.tsx index 1418fa0f..2fa3d209 100644 --- a/src/pages/join/InfoInputPage.tsx +++ b/src/pages/join/InfoInputPage.tsx @@ -1,12 +1,19 @@ +import { useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; import { useForm, SubmitHandler } from 'react-hook-form'; import Header from '../../../design-system/ui/Header'; import Button from '../../../design-system/ui/Button'; import UnderlineTextField from '../../../design-system/ui/textFields/UnderlineTextField'; -import { useNavigate } from 'react-router-dom'; import { FormData, zodValidation } from '../../shared/lib/formValidation'; import { useUserInfo, useUserUpdate } from '../../features/join/hooks/useUserHook'; import useAuthStore from '../../app/provider/authStore'; -import { useEffect } from 'react'; + +const formatPhoneNumber = (value: string) => { + const numbers = value.replace(/[^\d]/g, '').slice(0, 11); // 11자리까지만 허용 + if (numbers.length <= 3) return numbers; + if (numbers.length <= 7) return `${numbers.slice(0, 3)}-${numbers.slice(3)}`; + return `${numbers.slice(0, 3)}-${numbers.slice(3, 7)}-${numbers.slice(7, 11)}`; +}; const InfoInputPage = () => { const { data, isLoading } = useUserInfo(); @@ -17,6 +24,8 @@ const InfoInputPage = () => { handleSubmit, formState: { errors, isValid }, reset, + setValue, + watch, } = useForm({ mode: 'onChange', defaultValues: { @@ -28,20 +37,27 @@ const InfoInputPage = () => { }); const navigate = useNavigate(); + const phoneValue = watch('phone'); + + const handlePhoneChange = (e: React.ChangeEvent) => { + const formatted = formatPhoneNumber(e.target.value); + setValue('phone', formatted, { shouldValidate: true }); + }; + const onSubmit: SubmitHandler = formData => { const updatedData = { - name: data?.name || "", - email: data?.email || "", + name: data?.name || '', + email: data?.email || '', phoneNumber: formData.phone, }; updateUser(updatedData, { onSuccess: () => { login(); - setName(data?.name || "사용자"); + setName(data?.name || '사용자'); alert('정보가 성공적으로 업데이트되었습니다.'); navigate('/'); }, - onError: (err) => { + onError: err => { alert('정보 업데이트에 실패했습니다. 다시 시도해주세요.'); console.error(err); }, @@ -81,11 +97,12 @@ const InfoInputPage = () => { {/* 연락처 필드 */} {/* 이메일 필드 */} diff --git a/src/shared/lib/formValidation.ts b/src/shared/lib/formValidation.ts index b356962d..100b88f5 100644 --- a/src/shared/lib/formValidation.ts +++ b/src/shared/lib/formValidation.ts @@ -9,10 +9,7 @@ export const formSchema = z.object({ .regex(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/, '올바른 이메일 형식이어야 합니다.'), phone: z .string() - .regex(/^[0-9]{3}-[0-9]{3,4}-[0-9]{4}$/, '연락처는 하이픈(-)을 포함한 형식이어야 합니다.') - .refine(val => val.length === 13, { - message: '연락처는 하이픈(-) 포함 13자 형식(예: 010-1234-5678)이어야 합니다.', - }), + .regex(/^[0-9]{3}-[0-9]{3,4}-[0-9]{4}$/, '연락처는 휴대전화 번호 형식(예: 010-1234-5678)이어야 합니다.') }); export const organizerFormSchema = formSchema.pick({ email: true, phone: true }); export const eventTitleSchema = z.object({ From 1f3c94a8e2e3a388c4f98efd3ab10ee50dba1792 Mon Sep 17 00:00:00 2001 From: JaeHunKang Date: Sat, 7 Jun 2025 01:03:20 +0900 Subject: [PATCH 2/4] =?UTF-8?q?refact:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EA=B8=B0=EB=B3=B8=20=EC=A0=95=EB=B3=B4=20=EC=A3=BC=EC=B5=9C?= =?UTF-8?q?=EC=9E=90=20=EC=97=B0=EB=9D=BD=EC=B2=98=20=EC=9E=90=EB=8F=99=20?= =?UTF-8?q?=ED=95=98=EC=9D=B4=ED=94=88=20=EC=B6=94=EA=B0=80,=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=20=EC=A0=84=EC=86=A1=20=EC=8B=9C=20=ED=95=98=EC=9D=B4?= =?UTF-8?q?=ED=94=88=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/dashboard/ui/EventInfoPage.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/pages/dashboard/ui/EventInfoPage.tsx b/src/pages/dashboard/ui/EventInfoPage.tsx index ffaf4686..fc0af2e9 100644 --- a/src/pages/dashboard/ui/EventInfoPage.tsx +++ b/src/pages/dashboard/ui/EventInfoPage.tsx @@ -1,4 +1,5 @@ import { useEffect, useState } from 'react'; +import { useQueryClient } from '@tanstack/react-query'; import ChoiceChip from '../../../../design-system/ui/ChoiceChip'; import DefaultTextField from '../../../../design-system/ui/textFields/DefaultTextField'; import EventDatePicker from '../../../features/event/ui/DatePicker'; @@ -10,7 +11,6 @@ import { AddressSearch } from '../../../shared/ui/AddressSearch'; import KakaoMap from '../../../shared/ui/KakaoMap'; import { UpdateEventRequest } from '../../../features/dashboard/model/event'; import { useEventDetail } from '../../../entities/event/hook/useEventHook'; -import { useQueryClient } from '@tanstack/react-query'; const EventInfoPage = () => { const queryClient = useQueryClient(); @@ -18,6 +18,13 @@ const EventInfoPage = () => { const { data } = useEventDetail(); const { mutate } = useUpdateEventHook(); + const formatPhoneNumber = (value: string) => { + const numbers = value.replace(/[^\d]/g, '').slice(0, 11); // 11자리까지만 허용 + if (numbers.length <= 3) return numbers; + if (numbers.length <= 7) return `${numbers.slice(0, 3)}-${numbers.slice(3)}`; + return `${numbers.slice(0, 3)}-${numbers.slice(3, 7)}-${numbers.slice(7, 11)}`; + }; + const handleSelect = (option: string) => { setSelectedOption(option); }; @@ -54,7 +61,7 @@ const EventInfoPage = () => { category: data.result.category || 'DEVELOPMENT_STUDY', hashtags: data.result.hashtags || [], organizerEmail: email || data.result.organizerEmail || '', - organizerPhoneNumber: phone || data.result.organizerPhoneNumber || '', + organizerPhoneNumber: phone.replace(/-/g, '') || data.result.organizerPhoneNumber || '', }; mutate(requestData, { @@ -113,10 +120,10 @@ const EventInfoPage = () => { /> setPhone(e.target.value)} + onChange={e => setPhone(formatPhoneNumber(e.target.value))} /> Date: Sat, 7 Jun 2025 01:11:45 +0900 Subject: [PATCH 3/4] =?UTF-8?q?refact:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=A3=BC=EC=B5=9C=EC=9E=90=20=EC=A0=95=EB=B3=B4=20=EC=97=B0?= =?UTF-8?q?=EB=9D=BD=EC=B2=98=20=EC=9E=85=EB=A0=A5=20=EA=B0=84=20=EC=9E=90?= =?UTF-8?q?=EB=8F=99=20=ED=95=98=EC=9D=B4=ED=94=88=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EB=9D=BC=EB=B2=A8=20=EC=9D=B4=EB=A6=84=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../create-event/EventOrganizerInfoPage.tsx | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/pages/event/ui/create-event/EventOrganizerInfoPage.tsx b/src/pages/event/ui/create-event/EventOrganizerInfoPage.tsx index df8faa5c..147aae23 100644 --- a/src/pages/event/ui/create-event/EventOrganizerInfoPage.tsx +++ b/src/pages/event/ui/create-event/EventOrganizerInfoPage.tsx @@ -11,8 +11,16 @@ interface EventOrganizerInfoPageProps { const EventOrganizerInfoPage = ({ onValidationChange }: EventOrganizerInfoPageProps) => { const { eventState, setEventState } = useFunnelState(); + const formatPhoneNumber = (value: string) => { + const numbers = value.replace(/[^\d]/g, '').slice(0, 11); // 11자리까지만 허용 + if (numbers.length <= 3) return numbers; + if (numbers.length <= 7) return `${numbers.slice(0, 3)}-${numbers.slice(3)}`; + return `${numbers.slice(0, 3)}-${numbers.slice(3, 7)}-${numbers.slice(7, 11)}`; + }; + const { register, + setValue, watch, formState: { errors, isValid }, } = useForm({ @@ -27,6 +35,11 @@ const EventOrganizerInfoPage = ({ onValidationChange }: EventOrganizerInfoPagePr const phoneValue = watch('phone'); const emailValue = watch('email'); + const handlePhoneChange = (e: React.ChangeEvent) => { + const formatted = formatPhoneNumber(e.target.value); + setValue('phone', formatted, { shouldValidate: true }); + }; + useEffect(() => { onValidationChange?.(isValid); }, [isValid, onValidationChange]); @@ -50,12 +63,13 @@ const EventOrganizerInfoPage = ({ onValidationChange }: EventOrganizerInfoPagePr {...register('email')} /> ); From 5ba2347093e9bb8bcaadf7bbe11b39311a1d5b5c Mon Sep 17 00:00:00 2001 From: JaeHunKang Date: Sat, 7 Jun 2025 01:31:03 +0900 Subject: [PATCH 4/4] =?UTF-8?q?refact:=20=EC=9E=90=EB=8F=99=20=ED=95=98?= =?UTF-8?q?=EC=9D=B4=ED=94=88=20=ED=95=A8=EC=88=98=EB=A5=BC=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EC=9C=A0=ED=8B=B8=EB=A6=AC=ED=8B=B0=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=EB=A1=9C=20=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/dashboard/ui/EventInfoPage.tsx | 8 +------- .../event/ui/create-event/EventOrganizerInfoPage.tsx | 8 +------- src/pages/join/InfoInputPage.tsx | 8 +------- src/shared/utils/phoneFormatter.ts | 6 ++++++ 4 files changed, 9 insertions(+), 21 deletions(-) create mode 100644 src/shared/utils/phoneFormatter.ts diff --git a/src/pages/dashboard/ui/EventInfoPage.tsx b/src/pages/dashboard/ui/EventInfoPage.tsx index fc0af2e9..281503c3 100644 --- a/src/pages/dashboard/ui/EventInfoPage.tsx +++ b/src/pages/dashboard/ui/EventInfoPage.tsx @@ -11,6 +11,7 @@ import { AddressSearch } from '../../../shared/ui/AddressSearch'; import KakaoMap from '../../../shared/ui/KakaoMap'; import { UpdateEventRequest } from '../../../features/dashboard/model/event'; import { useEventDetail } from '../../../entities/event/hook/useEventHook'; +import { formatPhoneNumber } from '../../../shared/utils/phoneFormatter'; const EventInfoPage = () => { const queryClient = useQueryClient(); @@ -18,13 +19,6 @@ const EventInfoPage = () => { const { data } = useEventDetail(); const { mutate } = useUpdateEventHook(); - const formatPhoneNumber = (value: string) => { - const numbers = value.replace(/[^\d]/g, '').slice(0, 11); // 11자리까지만 허용 - if (numbers.length <= 3) return numbers; - if (numbers.length <= 7) return `${numbers.slice(0, 3)}-${numbers.slice(3)}`; - return `${numbers.slice(0, 3)}-${numbers.slice(3, 7)}-${numbers.slice(7, 11)}`; - }; - const handleSelect = (option: string) => { setSelectedOption(option); }; diff --git a/src/pages/event/ui/create-event/EventOrganizerInfoPage.tsx b/src/pages/event/ui/create-event/EventOrganizerInfoPage.tsx index 147aae23..26c847cc 100644 --- a/src/pages/event/ui/create-event/EventOrganizerInfoPage.tsx +++ b/src/pages/event/ui/create-event/EventOrganizerInfoPage.tsx @@ -3,6 +3,7 @@ import { useForm } from 'react-hook-form'; import UnderlineTextField from '../../../../../design-system/ui/textFields/UnderlineTextField'; import { useFunnelState } from '../../../../features/event/model/FunnelContext'; import { OrganizerFormData, organizerZodValidation } from '../../../../shared/lib/formValidation'; +import { formatPhoneNumber } from '../../../../shared/utils/phoneFormatter'; interface EventOrganizerInfoPageProps { onValidationChange?: (isValid: boolean) => void; @@ -11,13 +12,6 @@ interface EventOrganizerInfoPageProps { const EventOrganizerInfoPage = ({ onValidationChange }: EventOrganizerInfoPageProps) => { const { eventState, setEventState } = useFunnelState(); - const formatPhoneNumber = (value: string) => { - const numbers = value.replace(/[^\d]/g, '').slice(0, 11); // 11자리까지만 허용 - if (numbers.length <= 3) return numbers; - if (numbers.length <= 7) return `${numbers.slice(0, 3)}-${numbers.slice(3)}`; - return `${numbers.slice(0, 3)}-${numbers.slice(3, 7)}-${numbers.slice(7, 11)}`; - }; - const { register, setValue, diff --git a/src/pages/join/InfoInputPage.tsx b/src/pages/join/InfoInputPage.tsx index 2fa3d209..cc8f574c 100644 --- a/src/pages/join/InfoInputPage.tsx +++ b/src/pages/join/InfoInputPage.tsx @@ -7,13 +7,7 @@ import UnderlineTextField from '../../../design-system/ui/textFields/UnderlineTe import { FormData, zodValidation } from '../../shared/lib/formValidation'; import { useUserInfo, useUserUpdate } from '../../features/join/hooks/useUserHook'; import useAuthStore from '../../app/provider/authStore'; - -const formatPhoneNumber = (value: string) => { - const numbers = value.replace(/[^\d]/g, '').slice(0, 11); // 11자리까지만 허용 - if (numbers.length <= 3) return numbers; - if (numbers.length <= 7) return `${numbers.slice(0, 3)}-${numbers.slice(3)}`; - return `${numbers.slice(0, 3)}-${numbers.slice(3, 7)}-${numbers.slice(7, 11)}`; -}; +import { formatPhoneNumber } from '../../shared/utils/phoneFormatter'; const InfoInputPage = () => { const { data, isLoading } = useUserInfo(); diff --git a/src/shared/utils/phoneFormatter.ts b/src/shared/utils/phoneFormatter.ts new file mode 100644 index 00000000..091de0d5 --- /dev/null +++ b/src/shared/utils/phoneFormatter.ts @@ -0,0 +1,6 @@ +export const formatPhoneNumber = (value: string) => { + const numbers = value.replace(/[^\d]/g, '').slice(0, 11); // 11자리까지만 허용 + if (numbers.length <= 3) return numbers; + if (numbers.length <= 7) return `${numbers.slice(0, 3)}-${numbers.slice(3)}`; + return `${numbers.slice(0, 3)}-${numbers.slice(3, 7)}-${numbers.slice(7, 11)}`; +}; \ No newline at end of file