import { useState, useCallback, useEffect } from "react"; import { useRouter } from "next/navigation"; import { SignupFormData } from "@/types/auth"; import { signupUser } from "@/lib/api/auth"; import { useToast } from "@/hooks/use-toast"; /** * 유효성 검사 함수들 */ const validators = { // 연락처: 010-1234-5678 형식 phoneNumber: (value: string): string | null => { const phoneRegex = /^01[0-9]-\d{3,4}-\d{4}$/; if (!value) return "연락처를 입력해주세요"; if (!phoneRegex.test(value)) return "올바른 연락처 형식이 아닙니다 (예: 010-1234-5678)"; return null; }, // 면허번호: 12-34-567890-12 형식 licenseNumber: (value: string): string | null => { const licenseRegex = /^\d{2}-\d{2}-\d{6}-\d{2}$/; if (!value) return "면허번호를 입력해주세요"; if (!licenseRegex.test(value)) return "올바른 면허번호 형식이 아닙니다 (예: 12-34-567890-12)"; return null; }, // 차량번호: 12가1234 형식 vehicleNumber: (value: string): string | null => { const vehicleRegex = /^\d{2,3}[가-힣]\d{4}$/; if (!value) return "차량번호를 입력해주세요"; if (!vehicleRegex.test(value)) return "올바른 차량번호 형식이 아닙니다 (예: 12가1234)"; return null; }, // 차량 타입: 필수 입력 vehicleType: (value: string): string | null => { if (!value) return "차량 타입을 입력해주세요"; if (value.length < 2) return "차량 타입은 2자 이상이어야 합니다"; return null; }, // 아이디: 4자 이상 userId: (value: string): string | null => { if (!value) return "아이디를 입력해주세요"; if (value.length < 4) return "아이디는 4자 이상이어야 합니다"; return null; }, // 비밀번호: 6자 이상 password: (value: string): string | null => { if (!value) return "비밀번호를 입력해주세요"; if (value.length < 6) return "비밀번호는 6자 이상이어야 합니다"; return null; }, // 비밀번호 확인: password와 일치해야 함 passwordConfirm: (value: string, password?: string): string | null => { if (!value) return "비밀번호 확인을 입력해주세요"; if (password && value !== password) return "비밀번호가 일치하지 않습니다"; return null; }, // 이름: 2자 이상 userName: (value: string): string | null => { if (!value) return "이름을 입력해주세요"; if (value.length < 2) return "이름은 2자 이상이어야 합니다"; return null; }, }; /** * 회원가입 커스텀 훅 */ export function useSignup() { const router = useRouter(); const { toast } = useToast(); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(""); const [showPassword, setShowPassword] = useState(false); const [formData, setFormData] = useState({ userId: "", password: "", passwordConfirm: "", userName: "", phoneNumber: "", licenseNumber: "", vehicleNumber: "", vehicleType: "", }); const [validationErrors, setValidationErrors] = useState>({}); const [touchedFields, setTouchedFields] = useState>({}); const [isFormValid, setIsFormValid] = useState(false); // 유효성 검사 실행 const validateField = useCallback((name: string, value: string, password?: string) => { const validator = validators[name as keyof typeof validators]; if (validator) { // passwordConfirm 검증 시 password도 함께 전달 if (name === "passwordConfirm") { return validator(value, password); } return validator(value); } return null; }, []); // 전체 폼 유효성 검사 const validateForm = useCallback(() => { const errors: Record = {}; let hasErrors = false; Object.keys(formData).forEach((key) => { const error = validateField(key, formData[key as keyof SignupFormData], formData.password); if (error) { errors[key] = error; hasErrors = true; } }); setValidationErrors(errors); setIsFormValid(!hasErrors && Object.values(formData).every((value) => value !== "")); }, [formData, validateField]); // 입력 변경 핸들러 const handleInputChange = useCallback( (e: React.ChangeEvent) => { const { name, value } = e.target; const newFormData = { ...formData, [name]: value }; setFormData(newFormData); // touched된 필드만 실시간 유효성 검사 if (touchedFields[name]) { const fieldError = validateField(name, value, newFormData.password); setValidationErrors((prev) => ({ ...prev, [name]: fieldError || "", })); } // password가 변경되면 passwordConfirm도 다시 검증 if (name === "password" && touchedFields.passwordConfirm) { const confirmError = validateField("passwordConfirm", newFormData.passwordConfirm, value); setValidationErrors((prev) => ({ ...prev, passwordConfirm: confirmError || "", })); } setError(""); }, [validateField, touchedFields, formData], ); // 포커스 아웃 핸들러 (Blur) const handleBlur = useCallback( (e: React.FocusEvent) => { const { name, value } = e.target; // 필드를 touched로 표시 setTouchedFields((prev) => ({ ...prev, [name]: true })); // 유효성 검사 실행 const fieldError = validateField(name, value, formData.password); setValidationErrors((prev) => ({ ...prev, [name]: fieldError || "", })); }, [validateField, formData.password], ); // 회원가입 제출 const handleSignup = useCallback( async (e: React.FormEvent) => { e.preventDefault(); setError(""); // 모든 필드를 touched로 표시 (제출 시 모든 에러 표시) const allTouched = Object.keys(formData).reduce((acc, key) => ({ ...acc, [key]: true }), {}); setTouchedFields(allTouched); // 최종 유효성 검사 validateForm(); if (!isFormValid) { setError("입력 정보를 다시 확인해주세요"); return; } setIsLoading(true); try { const response = await signupUser(formData); if (response.success) { toast({ title: "회원가입 성공", description: "로그인 페이지로 이동합니다", }); // 1초 후 로그인 페이지로 이동 setTimeout(() => { router.push("/login"); }, 1000); } else { setError(response.message || "회원가입에 실패했습니다"); } } catch (err: any) { console.error("회원가입 오류:", err); setError(err.message || "회원가입 중 오류가 발생했습니다"); } finally { setIsLoading(false); } }, [formData, isFormValid, router, toast, validateForm], ); // 비밀번호 표시 토글 const togglePasswordVisibility = useCallback(() => { setShowPassword((prev) => !prev); }, []); // 폼 데이터 변경 시 유효성 검사 실행 useEffect(() => { validateForm(); }, [formData, validateForm]); return { formData, isLoading, error, showPassword, validationErrors, touchedFields, isFormValid, handleInputChange, handleBlur, handleSignup, togglePasswordVisibility, }; }