diff --git a/app/(routes)/(auth)/register/_components/register-form.tsx b/app/(routes)/(auth)/register/_components/register-form.tsx index 41faeb2..d6d1361 100644 --- a/app/(routes)/(auth)/register/_components/register-form.tsx +++ b/app/(routes)/(auth)/register/_components/register-form.tsx @@ -12,15 +12,15 @@ import { Label } from '@/components/ui/label'; import { formatPhoneNumber, formatTelNo, validateField } from '@/lib/utils'; // WebOTP API 타입 정의 -interface OTPCredential extends Credential { - code: string; -} - -interface OTPCredentialRequestOptions extends CredentialRequestOptions { - otp: { - transport: string[]; - }; -} +// interface OTPCredential extends Credential { +// code: string; +// } +// +// interface OTPCredentialRequestOptions extends CredentialRequestOptions { +// otp: { +// transport: string[]; +// }; +// } export interface FormData { name: string; @@ -97,42 +97,42 @@ export default function RegisterForm() { typeof window !== 'undefined' && 'OTPCredential' in window; // WebOTP 이벤트 리스너 설정 - useEffect(() => { - if (!isWebOTPSupported || currentStep !== 2) return; // phone step이 아닐 때는 리스너 제거 - - const abortController = new AbortController(); - - const handleWebOTP = async () => { - try { - const credential = (await navigator.credentials.get({ - otp: { transport: ['sms'] }, - signal: abortController.signal, - } as OTPCredentialRequestOptions)) as OTPCredential; - - if (credential && credential.code) { - // WebOTP로 받은 코드를 인증번호 필드에 자동 입력 - handleInputChange('verificationCode', credential.code); - } - } catch (error) { - // 사용자가 취소하거나 지원하지 않는 경우 무시 - console.log('WebOTP not available or cancelled:', error); - } - }; - - // 페이지가 포커스될 때 WebOTP 요청 - const handleFocus = () => { - if (isCodeSent) { - handleWebOTP(); - } - }; - - window.addEventListener('focus', handleFocus); - - return () => { - abortController.abort(); - window.removeEventListener('focus', handleFocus); - }; - }, [isWebOTPSupported, currentStep, isCodeSent]); + // useEffect(() => { + // if (!isWebOTPSupported || currentStep !== 2) return; // phone step이 아닐 때는 리스너 제거 + // + // const abortController = new AbortController(); + // + // const handleWebOTP = async () => { + // try { + // const credential = (await navigator.credentials.get({ + // otp: { transport: ['sms'] }, + // signal: abortController.signal, + // } as OTPCredentialRequestOptions)) as OTPCredential; + // + // if (credential && credential.code) { + // // WebOTP로 받은 코드를 인증번호 필드에 자동 입력 + // handleInputChange('verificationCode', credential.code); + // } + // } catch (error) { + // // 사용자가 취소하거나 지원하지 않는 경우 무시 + // console.log('WebOTP not available or cancelled:', error); + // } + // }; + // + // // 페이지가 포커스될 때 WebOTP 요청 + // const handleFocus = () => { + // if (isCodeSent) { + // handleWebOTP(); + // } + // }; + // + // window.addEventListener('focus', handleFocus); + // + // return () => { + // abortController.abort(); + // window.removeEventListener('focus', handleFocus); + // }; + // }, [isWebOTPSupported, currentStep, isCodeSent]); const steps: (keyof FormData)[] = [ 'name', @@ -195,21 +195,21 @@ export default function RegisterForm() { case 'rrn': return validateField('rrn', formData.rrn, formData); - // case 'phone': - // return ( - // validateField('phone', formData.phone, formData) && - // validateField( - // 'verificationCode', - // formData.verificationCode, - // formData - // ) && - // isCodeSent && - // formData.verificationCode === sentCode - // ); + case 'phone': + return ( + validateField('phone', formData.phone, formData) && + validateField( + 'verificationCode', + formData.verificationCode, + formData + ) && + isCodeSent && + formData.verificationCode === sentCode + ); // 문자 인증 패스 - case 'phone': - return true; + // case 'phone': + // return true; case 'address': return validateField('address', formData.address, formData); @@ -317,12 +317,12 @@ export default function RegisterForm() { setIsCodeSent(true); toast.success('인증번호가 전송되었습니다.'); // WebOTP 지원 시 자동으로 인증번호 입력 요청 - if (isWebOTPSupported) { - setTimeout(() => { - // 페이지 포커스 시 WebOTP 요청 - window.focus(); - }, 1000); - } + // if (isWebOTPSupported) { + // setTimeout(() => { + // // 페이지 포커스 시 WebOTP 요청 + // window.focus(); + // }, 1000); + // } } else { alert('인증번호 전송에 실패했습니다. 다시 시도해주세요.'); } @@ -492,13 +492,11 @@ export default function RegisterForm() { 인증번호가 일치하지 않습니다.
)} - {isWebOTPSupported && - isCodeSent && - formData.verificationCode.length < 3 && ( -- SMS로 받은 인증번호를 입력해주세요. -
- )} + {isCodeSent && formData.verificationCode.length < 3 && ( ++ SMS로 받은 인증번호를 입력해주세요. +
+ )} diff --git a/app/(routes)/mypage/edit-profile/phone/_components/edit-phone-container.tsx b/app/(routes)/mypage/edit-profile/phone/_components/edit-phone-container.tsx index 184d9c9..32f11ad 100644 --- a/app/(routes)/mypage/edit-profile/phone/_components/edit-phone-container.tsx +++ b/app/(routes)/mypage/edit-profile/phone/_components/edit-phone-container.tsx @@ -1,6 +1,7 @@ 'use client'; import { useEffect, useState } from 'react'; +import toast from 'react-hot-toast'; import { useRouter } from 'next/navigation'; import { FormData } from '@/app/(routes)/(auth)/register/_components/register-form'; import { useHeader } from '@/context/header-context'; @@ -25,6 +26,9 @@ export const EditPhoneContainer = () => { const router = useRouter(); const [loading, setLoading] = useState(false); + const [sending, setSending] = useState(false); // 인증번호 전송 중 여부 + const [isCodeSent, setIsCodeSent] = useState(false); + const [sentCode, setSentCode] = useState(''); // 전송된 코드 useEffect(() => { setHeader('내 정보 수정하기', '전화번호 수정'); @@ -74,14 +78,52 @@ export const EditPhoneContainer = () => { const raw = value.replace(/\D/g, '').slice(0, 3); setPhoneData((prev) => ({ ...prev, verificationCode: raw })); - setValidationErrors((prev) => ({ - ...prev, - verificationCode: !validateField('verificationCode', raw, phoneData), - })); + if (raw.length === 3 && raw === sentCode) { + setValidationErrors((prev) => ({ ...prev, verificationCode: false })); + } else { + setValidationErrors((prev) => ({ + ...prev, + verificationCode: !validateField('verificationCode', raw, phoneData), + })); + } return; } }; + // 인증번호 전송 + const handleSendCode = async () => { + if (validationErrors.phone || !phoneData.phone) { + toast.error('올바른 전화번호를 입력해주세요.'); + return; + } + + setSending(true); + try { + // 임시 3자리 코드 생성 + const code = Math.floor(100 + Math.random() * 900).toString(); + + const res = await fetch('/api/auth/sms', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ phone: phoneData.phone, code }), + }); + const data = await res.json(); + + if (data.success) { + setIsCodeSent(true); + setSentCode(code); + toast.success('인증번호가 전송되었습니다.'); + } else { + toast.error('인증번호 전송에 실패했습니다.'); + } + } catch (e) { + console.error(e); + toast.error('인증번호 전송 중 오류가 발생했습니다.'); + } finally { + setSending(false); + } + }; + const submitData = async () => { const data = { phone: phoneData.phone }; setLoading(true); @@ -108,8 +150,12 @@ export const EditPhoneContainer = () => { value={phoneData.phone} onChangeField={handleInputChange} /> -