import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import { RegisterFormData } from "@/types/auth"; import { authApi } from "@/lib/api/auth"; import { useToast } from "@/hooks/use-toast"; /** * 회원가입 비즈니스 로직 훅 */ export function useRegister() { const router = useRouter(); const { toast } = useToast(); const [formData, setFormData] = useState({ userId: "", password: "", passwordConfirm: "", userName: "", licenseNumber: "", vehicleNumber: "", phoneNumber: "", }); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(""); const [validationErrors, setValidationErrors] = useState>({}); const [showPassword, setShowPassword] = useState(false); const [showPasswordConfirm, setShowPasswordConfirm] = useState(false); const [isFormValid, setIsFormValid] = useState(false); /** * 실시간 폼 유효성 검사 및 에러 메시지 업데이트 */ useEffect(() => { const checkFormValidity = () => { const errors: Record = {}; // 사용자 ID 검사 (입력이 있을 때만) if (formData.userId.length > 0 && formData.userId.length < 2) { errors.userId = "사용자 ID는 최소 2자 이상이어야 합니다"; } // 비밀번호 검사 (입력이 있을 때만) if (formData.password.length > 0 && formData.password.length < 6) { errors.password = "비밀번호는 최소 6자 이상이어야 합니다"; } // 비밀번호 확인 검사 (입력이 있을 때만) if (formData.passwordConfirm.length > 0 && formData.password !== formData.passwordConfirm) { errors.passwordConfirm = "비밀번호가 일치하지 않습니다"; } // 이름 검사 (입력이 있을 때만) if (formData.userName.length > 0 && formData.userName.trim().length < 2) { errors.userName = "이름은 최소 2자 이상이어야 합니다"; } // 면허번호 검사 (입력이 있을 때만) if (formData.licenseNumber.length > 0 && !validateLicenseNumber(formData.licenseNumber)) { errors.licenseNumber = "올바른 면허번호 형식이 아닙니다 (예: 12-34-567890-12)"; } // 차량 번호 검사 (입력이 있을 때만) if (formData.vehicleNumber.length > 0 && !validateVehicleNumber(formData.vehicleNumber)) { errors.vehicleNumber = "올바른 차량 번호 형식이 아닙니다 (예: 12가3456, 123가4567)"; } // 휴대폰 번호 검사 (입력이 있을 때만) if (formData.phoneNumber.length > 0 && !validatePhoneNumber(formData.phoneNumber)) { errors.phoneNumber = "올바른 휴대폰 번호 형식이 아닙니다 (예: 010-1234-5678)"; } setValidationErrors(errors); // 모든 필드가 채워져 있고 에러가 없는지 확인 const allFieldsFilled = formData.userId.trim().length >= 2 && formData.password.length >= 6 && formData.passwordConfirm.length > 0 && formData.userName.trim().length >= 2 && formData.licenseNumber.length > 0 && formData.vehicleNumber.length > 0 && formData.phoneNumber.length > 0; const isValid = allFieldsFilled && Object.keys(errors).length === 0; setIsFormValid(isValid); }; checkFormValidity(); }, [formData]); /** * 입력값 변경 핸들러 */ const handleInputChange = (e: React.ChangeEvent) => { const { name, value } = e.target; setFormData((prev) => ({ ...prev, [name]: value })); // 전역 에러 초기화 if (error) setError(""); }; /** * 비밀번호 표시/숨김 토글 */ const togglePasswordVisibility = () => { setShowPassword((prev) => !prev); }; /** * 비밀번호 확인 표시/숨김 토글 */ const togglePasswordConfirmVisibility = () => { setShowPasswordConfirm((prev) => !prev); }; /** * 면허번호 유효성 검사 * 한국 운전면허 형식: 12-34-567890-12 (지역번호-발급년도-일련번호-체크) */ const validateLicenseNumber = (licenseNumber: string): boolean => { // 하이픈 포함 형식: 12-34-567890-12 const pattern = /^\d{2}-\d{2}-\d{6}-\d{2}$/; return pattern.test(licenseNumber); }; /** * 차량 번호 유효성 검사 * 한국 차량 번호 형식: 12가3456, 123가4567, 서울12가3456 등 */ const validateVehicleNumber = (vehicleNumber: string): boolean => { // 공백 제거 const cleanNumber = vehicleNumber.replace(/\s/g, ""); // 한국 차량 번호 패턴 // 1. 구형: 12가3456 (2자리 숫자 + 한글 1자 + 4자리 숫자) // 2. 신형: 123가4567 (3자리 숫자 + 한글 1자 + 4자리 숫자) // 3. 지역명 포함: 서울12가3456, 서울123가4567 const patterns = [ /^\d{2}[가-힣]{1}\d{4}$/, // 12가3456 /^\d{3}[가-힣]{1}\d{4}$/, // 123가4567 /^[가-힣]{2}\d{2}[가-힣]{1}\d{4}$/, // 서울12가3456 /^[가-힣]{2}\d{3}[가-힣]{1}\d{4}$/, // 서울123가4567 ]; return patterns.some(pattern => pattern.test(cleanNumber)); }; /** * 휴대폰 번호 유효성 검사 * 형식: 010-1234-5678, 011-123-4567 등 */ const validatePhoneNumber = (phoneNumber: string): boolean => { // 하이픈 포함 형식: 010-1234-5678, 011-123-4567 const pattern = /^01[016789]-\d{3,4}-\d{4}$/; return pattern.test(phoneNumber); }; /** * 폼 유효성 검사 */ const validateForm = (): boolean => { const errors: Record = {}; // 사용자 ID 검사 if (formData.userId.length < 2) { errors.userId = "사용자 ID는 최소 2자 이상이어야 합니다"; } // 비밀번호 검사 if (formData.password.length < 6) { errors.password = "비밀번호는 최소 6자 이상이어야 합니다"; } // 비밀번호 확인 검사 if (formData.password !== formData.passwordConfirm) { errors.passwordConfirm = "비밀번호가 일치하지 않습니다"; } // 이름 검사 if (formData.userName.trim().length < 2) { errors.userName = "이름은 최소 2자 이상이어야 합니다"; } // 면허번호 검사 if (!validateLicenseNumber(formData.licenseNumber)) { errors.licenseNumber = "올바른 면허번호 형식이 아닙니다 (예: 12-34-567890-12)"; } // 차량 번호 검사 if (!validateVehicleNumber(formData.vehicleNumber)) { errors.vehicleNumber = "올바른 차량 번호 형식이 아닙니다 (예: 12가3456, 123가4567)"; } // 휴대폰 번호 검사 if (!validatePhoneNumber(formData.phoneNumber)) { errors.phoneNumber = "올바른 휴대폰 번호 형식이 아닙니다 (예: 010-1234-5678)"; } setValidationErrors(errors); return Object.keys(errors).length === 0; }; /** * 회원가입 핸들러 */ const handleRegister = async (e: React.FormEvent) => { e.preventDefault(); // 유효성 검사 if (!validateForm()) { return; } setIsLoading(true); setError(""); try { const response = await authApi.register({ userId: formData.userId, password: formData.password, userName: formData.userName, licenseNumber: formData.licenseNumber, vehicleNumber: formData.vehicleNumber.replace(/\s/g, ""), // 공백 제거 phoneNumber: formData.phoneNumber, }); if (response.success) { // 회원가입 성공 - toast 알림 표시 toast({ title: "회원가입 완료", description: "회원가입이 성공적으로 완료되었습니다. 로그인 페이지로 이동합니다.", }); // 로그인 페이지로 이동 setTimeout(() => { router.push("/login"); }, 1500); } else { setError(response.message || "회원가입에 실패했습니다"); } } catch (err: any) { console.error("회원가입 오류:", err); setError(err.message || "회원가입 중 오류가 발생했습니다"); } finally { setIsLoading(false); } }; return { formData, isLoading, error, validationErrors, showPassword, showPasswordConfirm, isFormValid, handleInputChange, handleRegister, togglePasswordVisibility, togglePasswordConfirmVisibility, }; }