263 lines
8.4 KiB
TypeScript
263 lines
8.4 KiB
TypeScript
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<RegisterFormData>({
|
|
userId: "",
|
|
password: "",
|
|
passwordConfirm: "",
|
|
userName: "",
|
|
licenseNumber: "",
|
|
vehicleNumber: "",
|
|
phoneNumber: "",
|
|
});
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState("");
|
|
const [validationErrors, setValidationErrors] = useState<Record<string, string>>({});
|
|
const [showPassword, setShowPassword] = useState(false);
|
|
const [showPasswordConfirm, setShowPasswordConfirm] = useState(false);
|
|
const [isFormValid, setIsFormValid] = useState(false);
|
|
|
|
/**
|
|
* 실시간 폼 유효성 검사 및 에러 메시지 업데이트
|
|
*/
|
|
useEffect(() => {
|
|
const checkFormValidity = () => {
|
|
const errors: Record<string, string> = {};
|
|
|
|
// 사용자 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<HTMLInputElement>) => {
|
|
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<string, string> = {};
|
|
|
|
// 사용자 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,
|
|
};
|
|
}
|
|
|