diff --git a/src/app/register/page.tsx b/src/app/register/page.tsx index 9b1f62a..f9616ee 100644 --- a/src/app/register/page.tsx +++ b/src/app/register/page.tsx @@ -31,6 +31,10 @@ export default function RegisterPage() { description: '', }); + // 유효성 검증 에러 상태 + const [userErrors, setUserErrors] = useState>({}); + const [companyErrors, setCompanyErrors] = useState>({}); + // 회원가입 스토어에서 상태와 메서드 가져오기 const { isRegistering, @@ -40,22 +44,107 @@ export default function RegisterPage() { resetRegisterSuccess } = useRegisterStore(); + // 전화번호 형식화 함수 + const formatPhoneNumber = (value: string): string => { + // 숫자만 추출 + const numbers = value.replace(/[^\d]/g, ''); + + if (numbers.length <= 3) { + return numbers; + } else if (numbers.length <= 7) { + return `${numbers.slice(0, 3)}-${numbers.slice(3)}`; + } else { + // 지역번호가 2자리인 경우(02) + if (numbers.startsWith('02') && numbers.length <= 10) { + return `${numbers.slice(0, 2)}-${numbers.slice(2, 6)}-${numbers.slice(6)}`; + } + // 일반적인 경우(3-4-4) + else { + return `${numbers.slice(0, 3)}-${numbers.slice(3, 7)}-${numbers.slice(7, 11)}`; + } + } + }; + // 사용자 정보 입력 필드 변경 처리 const handleUserInputChange = (e: React.ChangeEvent) => { const { name, value } = e.target; - setUserFormData((prev) => ({ - ...prev, - [name]: value - })); + + // 전화번호 필드인 경우 형식화 적용 + if (name === 'phone') { + const formattedPhone = formatPhoneNumber(value); + setUserFormData((prev) => ({ + ...prev, + [name]: formattedPhone + })); + } else { + setUserFormData((prev) => ({ + ...prev, + [name]: value + })); + } + + // 오류 상태 초기화 (사용자가 수정 중) + if (userErrors[name]) { + const newErrors = { ...userErrors }; + delete newErrors[name]; + setUserErrors(newErrors); + } }; // 회사 정보 입력 필드 변경 처리 const handleCompanyInputChange = (e: React.ChangeEvent) => { const { name, value } = e.target; - setCompanyFormData((prev) => ({ - ...prev, - [name]: value - })); + + // 전화번호 필드인 경우 형식화 적용 + if (name === 'phone') { + const formattedPhone = formatPhoneNumber(value); + setCompanyFormData((prev) => ({ + ...prev, + [name]: formattedPhone + })); + } else { + setCompanyFormData((prev) => ({ + ...prev, + [name]: value + })); + } + + // 오류 상태 초기화 (사용자가 수정 중) + if (companyErrors[name]) { + const newErrors = { ...companyErrors }; + delete newErrors[name]; + setCompanyErrors(newErrors); + } + }; + + // 다음 버튼 클릭 핸들러 + const handleNextButtonClick = () => { + // 필수 필드 비어있는지 확인 + const newErrors: Record = {}; + + // 각 필수 필드 검사 + if (!userFormData.name.trim()) newErrors.name = "이름을 입력해주세요"; + if (!userFormData.email.trim()) newErrors.email = "이메일을 입력해주세요"; + if (!userFormData.password.trim()) newErrors.password = "비밀번호를 입력해주세요"; + if (!userFormData.phone.trim()) newErrors.phone = "전화번호를 입력해주세요"; + if (!userFormData.jobTitle.trim()) newErrors.jobTitle = "직책을 입력해주세요"; + + // 형식 검사 (값이 있는 경우만) + if (userFormData.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(userFormData.email)) { + newErrors.email = "올바른 이메일 형식이 아닙니다"; + } + + if (userFormData.phone && !/^\d{2,3}-\d{3,4}-\d{4}$/.test(userFormData.phone)) { + newErrors.phone = "올바른 전화번호 형식이 아닙니다 (예: 010-1234-5678)"; + } + + // 오류 상태 업데이트 + setUserErrors(newErrors); + + // 오류가 없을 때만 탭 변경 + if (Object.keys(newErrors).length === 0) { + setActiveTab('company'); + } }; // 뒤로가기 처리 @@ -65,7 +154,10 @@ export default function RegisterPage() { // 탭 변경 처리 const handleTabChange = (tab: string) => { - setActiveTab(tab); + // 회사 정보 탭에서 사용자 정보 탭으로 이동할 때는 항상 허용 + if (tab === 'user') { + setActiveTab(tab); + } }; // useEffect를 사용하여 등록 성공 시 리다이렉션 처리 @@ -83,21 +175,58 @@ export default function RegisterPage() { } }, [registerSuccess, router]); + // 회사 정보 유효성 검증 + const validateCompanyInfo = () => { + const newErrors: Record = {}; + + // 필수 필드는 회사명과 전화번호 + if (!companyFormData.name.trim()) newErrors.name = "회사명을 입력해주세요"; + if (!companyFormData.phone.trim()) newErrors.phone = "회사 전화번호를 입력해주세요"; + + // 이메일 형식 검사 (입력된 경우) + if (companyFormData.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(companyFormData.email)) { + newErrors.email = "올바른 이메일 형식이 아닙니다"; + } + + // 전화번호 형식 검사 + if (companyFormData.phone && !/^\d{2,3}-\d{3,4}-\d{4}$/.test(companyFormData.phone)) { + newErrors.phone = "올바른 전화번호 형식이 아닙니다 (예: 02-1234-5678)"; + } + + setCompanyErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + // 폼 제출 처리 const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); + // 회사 정보 유효성 검증 + if (!validateCompanyInfo()) { + return; + } + // 두 탭의 필수 입력값이 모두 채워졌는지 확인 const requestData: RootUserRequest = { user: userFormData, company: companyFormData }; - console.log('폼 제출 데이터:', requestData); - console.log('JSON 문자열:', JSON.stringify(requestData)); - - // 스토어의 registerRootUser 메서드 호출 - await registerRootUser(requestData); + try { + // 스토어의 registerRootUser 메서드 호출 + await registerRootUser(requestData); + } catch (error: any) { + console.log('회원가입 에러 처리:', error); + // 이메일 중복 오류 처리 + if (error?.message === "Email already exists") { + // 사용자 탭으로 전환하고 이메일 필드에 오류 표시 + setActiveTab('user'); + setUserErrors({ + ...userErrors, + email: "이미 등록된 이메일입니다. 다른 이메일을 사용해주세요." + }); + } + } }; return ( @@ -117,7 +246,11 @@ export default function RegisterPage() { {/* 오류 메시지 */} {registerError && (
-

{registerError}

+

+ {registerError === "Email already exists" + ? "이미 등록된 이메일입니다. 다른 이메일을 사용해주세요." + : registerError} +

)} @@ -168,10 +301,13 @@ export default function RegisterPage() { value={userFormData.name} onChange={handleUserInputChange} required - className={`w-full rounded-lg border ${currentTheme.border} ${currentTheme.inputBg} ${currentTheme.text} px-4 py-2.5 focus:ring-2 focus:ring-blue-500 focus:border-blue-500`} + className={`w-full rounded-lg border ${userErrors.name ? 'border-red-500' : currentTheme.border} ${currentTheme.inputBg} ${currentTheme.text} px-4 py-2.5 focus:ring-2 focus:ring-blue-500 focus:border-blue-500`} placeholder="이름을 입력하세요" disabled={isRegistering} /> + {userErrors.name && ( +

{userErrors.name}

+ )} {/* 이메일 */} @@ -186,10 +322,13 @@ export default function RegisterPage() { value={userFormData.email} onChange={handleUserInputChange} required - className={`w-full rounded-lg border ${currentTheme.border} ${currentTheme.inputBg} ${currentTheme.text} px-4 py-2.5 focus:ring-2 focus:ring-blue-500 focus:border-blue-500`} + className={`w-full rounded-lg border ${userErrors.email ? 'border-red-500' : currentTheme.border} ${currentTheme.inputBg} ${currentTheme.text} px-4 py-2.5 focus:ring-2 focus:ring-blue-500 focus:border-blue-500`} placeholder="이메일 주소를 입력하세요" disabled={isRegistering} /> + {userErrors.email && ( +

{userErrors.email}

+ )} {/* 비밀번호 */} @@ -204,10 +343,13 @@ export default function RegisterPage() { value={userFormData.password} onChange={handleUserInputChange} required - className={`w-full rounded-lg border ${currentTheme.border} ${currentTheme.inputBg} ${currentTheme.text} px-4 py-2.5 focus:ring-2 focus:ring-blue-500 focus:border-blue-500`} + className={`w-full rounded-lg border ${userErrors.password ? 'border-red-500' : currentTheme.border} ${currentTheme.inputBg} ${currentTheme.text} px-4 py-2.5 focus:ring-2 focus:ring-blue-500 focus:border-blue-500`} placeholder="비밀번호를 입력하세요" disabled={isRegistering} /> + {userErrors.password && ( +

{userErrors.password}

+ )} {/* 전화번호 */} @@ -222,10 +364,14 @@ export default function RegisterPage() { value={userFormData.phone} onChange={handleUserInputChange} required - className={`w-full rounded-lg border ${currentTheme.border} ${currentTheme.inputBg} ${currentTheme.text} px-4 py-2.5 focus:ring-2 focus:ring-blue-500 focus:border-blue-500`} - placeholder="연락처를 입력하세요" + maxLength={13} + className={`w-full rounded-lg border ${userErrors.phone ? 'border-red-500' : currentTheme.border} ${currentTheme.inputBg} ${currentTheme.text} px-4 py-2.5 focus:ring-2 focus:ring-blue-500 focus:border-blue-500`} + placeholder="숫자만 입력하세요 (예: 01012345678)" disabled={isRegistering} /> + {userErrors.phone && ( +

{userErrors.phone}

+ )} {/* 직책 */} @@ -240,10 +386,13 @@ export default function RegisterPage() { value={userFormData.jobTitle} onChange={handleUserInputChange} required - className={`w-full rounded-lg border ${currentTheme.border} ${currentTheme.inputBg} ${currentTheme.text} px-4 py-2.5 focus:ring-2 focus:ring-blue-500 focus:border-blue-500`} + className={`w-full rounded-lg border ${userErrors.jobTitle ? 'border-red-500' : currentTheme.border} ${currentTheme.inputBg} ${currentTheme.text} px-4 py-2.5 focus:ring-2 focus:ring-blue-500 focus:border-blue-500`} placeholder="직책을 입력하세요" disabled={isRegistering} /> + {userErrors.jobTitle && ( +

{userErrors.jobTitle}

+ )} @@ -262,10 +411,13 @@ export default function RegisterPage() { value={companyFormData.name} onChange={handleCompanyInputChange} required - className={`w-full rounded-lg border ${currentTheme.border} ${currentTheme.inputBg} ${currentTheme.text} px-4 py-2.5 focus:ring-2 focus:ring-blue-500 focus:border-blue-500`} + className={`w-full rounded-lg border ${companyErrors.name ? 'border-red-500' : currentTheme.border} ${currentTheme.inputBg} ${currentTheme.text} px-4 py-2.5 focus:ring-2 focus:ring-blue-500 focus:border-blue-500`} placeholder="회사명을 입력하세요" disabled={isRegistering} /> + {companyErrors.name && ( +

{companyErrors.name}

+ )} {/* 주소 */} @@ -291,21 +443,24 @@ export default function RegisterPage() { 회사 이메일 + {companyErrors.email && ( +

{companyErrors.email}

+ )} {/* 회사 전화번호 */}
+ {companyErrors.phone && ( +

{companyErrors.phone}

+ )}
{/* 웹사이트 */} @@ -352,6 +512,11 @@ export default function RegisterPage() { disabled={isRegistering} /> + +
+

* 표시는 필수 입력 항목입니다.

+

전화번호는 자동으로 하이픈(-)이 추가됩니다.

+
)} @@ -362,7 +527,7 @@ export default function RegisterPage() {