ERP-node/frontend/hooks/useRegister.ts

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,
};
}