Select Git revision
-
Hwanyong Lee authoredHwanyong Lee authored
SignupModal.jsx 11.85 KiB
import React, { useState, useEffect } from "react";
import axiosSignup from "@/api/auth/axiosSignup";
import "./SignupModal.css";
import { sendVerificationEmail, verifyEmailCode } from "@/api/auth/verifyEmail";
import checkNickname from "@/api/auth/checkNickname";
const SignupModal = ({ onClose }) => {
const [formData, setFormData] = useState({
email: '',
verificationCode: '',
nickname: '',
password: '',
confirmPassword: ''
});
const [errors, setErrors] = useState({
email: '',
verificationCode: '',
nickname: '',
password: ''
});
const [verified, setVerified] = useState({
email: false,
verificationCode: false,
nickname: false,
password: false
});
const [timer, setTimer] = useState(180);
const [timerActive, setTimerActive] = useState(false);
const [inputTimer, setInputTimer] = useState(null);
useEffect(() => {
return () => {
if (inputTimer) clearTimeout(inputTimer);
};
}, [inputTimer]);
const validateField = async (field) => {
let newErrors = { ...errors };
let newVerified = { ...verified };
switch (field) {
case 'email':
if (!formData.email.includes("@ajou.ac.kr")) {
newErrors.email = "@ajou.ac.kr이 포함되어야 합니다.";
newVerified.email = false;
} else {
try {
const result = await sendVerificationEmail(formData.email);
if (result) {
newErrors.email = "";
newVerified.email = true;
startTimer();
}
} catch (error) {
newErrors.email = "이메일 인증 요청 중 오류가 발생했습니다.";
newVerified.email = false;
}
}
break;
case 'verificationCode':
if (!formData.verificationCode) {
newErrors.verificationCode = "인증번호를 입력해주세요.";
newVerified.verificationCode = false;
} else if (formData.verificationCode.length !== 6) {
newErrors.verificationCode = "인증번호는 6자리여야 합니다.";
newVerified.verificationCode = false;
} else {
try {
const result = await verifyEmailCode(formData.email, formData.verificationCode);
if (result.statusCode === 200) {
newErrors.verificationCode = "";
newVerified.verificationCode = true;
setTimerActive(false);
} else {
newErrors.verificationCode = "잘못된 인증번호입니다.";
newVerified.verificationCode = false;
}
} catch (error) {
newErrors.verificationCode = "인증 확인 중 오류가 발생했습니다.";
newVerified.verificationCode = false;
}
}
break;
case 'nickname':
const nicknameRegex = /^[a-zA-Zㄱ-ㅎ가-힣0-9]{4,12}$/;
if (!nicknameRegex.test(formData.nickname)) {
newErrors.nickname = "닉네임은 4-12자, 영문, 한글, 숫자만 가능합니다.";
newVerified.nickname = false;
} else {
try {
const isDuplicated = await checkNickname(formData.nickname);
if (isDuplicated) {
newErrors.nickname = "이미 사용 중인 닉네임입니다.";
newVerified.nickname = false;
} else {
newErrors.nickname = "";
newVerified.nickname = true;
}
} catch (error) {
newErrors.nickname = "닉네임 중복 확인 중 오류가 발생했습니다.";
newVerified.nickname = false;
}
}
break;
case 'password':
if (formData.password && formData.confirmPassword) {
if (formData.password !== formData.confirmPassword) {
newErrors.password = "비밀호가 일치하지 않습니다.";
newVerified.password = false;
} else {
newErrors.password = "";
newVerified.password = true;
}
}
break;
default: break;
}
setErrors(newErrors);
setVerified(newVerified);
};
const handleInputChange = (field) => (e) => {
const value = e.target.value;
const newFormData = { ...formData, [field]: value };
setFormData(newFormData);
if (inputTimer) clearTimeout(inputTimer);
if (field === 'password' || field === 'confirmPassword') {
if (newFormData.password && newFormData.confirmPassword) {
validateField('password');
}
} else {
const timer = setTimeout(() => {
if (document.activeElement !== e.target) {
validateField(field);
}
}, 3000);
setInputTimer(timer);
}
};
const handleBlur = (field) => () => {
if (inputTimer) clearTimeout(inputTimer);
validateField(field);
};
const handleSubmit = async (e) => {
e.preventDefault();
if (Object.values(errors).some(error => error) ||
Object.values(formData).some(value => !value)) {
return;
}
try {
const result = await axiosSignup(formData);
if (result) {
sessionStorage.setItem('accessToken', result.token);
// 만료 시간도 저장
sessionStorage.setItem('tokenExpiresAt', result.expiresAt.toString());
window.location.href = '/';
}
} catch (error) {
setErrors(prev => ({
...prev,
submit: "회원가입 실패. 다시 시도해주세요."
}));
}
};
const startTimer = () => {
setTimer(180);
setTimerActive(true);
const countdown = setInterval(() => {
setTimer((prev) => {
if (prev <= 1) {
clearInterval(countdown);
setTimerActive(false);
setErrors(prev => ({
...prev,
verificationCode: "인증 시간이 만료되었습니다."
}));
return 0;
}
return prev - 1;
});
}, 1000);
};
const handleResendVerification = async () => {
try {
const result = await sendVerificationEmail(formData.email);
if (result) {
startTimer();
setErrors(prev => ({ ...prev, verificationCode: "" }));
}
} catch (error) {
setErrors(prev => ({
...prev,
verificationCode: "인증번호 재전송에 실패했습니다."
}));
}
};
const isFormComplete = () => {
return Object.values(formData).every(value => value) &&
Object.values(verified).every(v => v) &&
!Object.values(errors).some(error => error);
};
const handleKeyDown = (field) => (e) => {
if (e.key === 'Enter') {
e.preventDefault();
const fields = ['email', 'verificationCode', 'nickname', 'password', 'confirmPassword'];
const currentIndex = fields.indexOf(field);
const nextField = fields[currentIndex + 1];
if (nextField) {
document.querySelector(`[name="${nextField}"]`).focus();
} else if (isFormComplete()) {
handleSubmit(e);
}
}
};
return (
<div className="modal-overlay">
<div className="modal-content">
<button className="close-icon" onClick={onClose}>
✕
</button>
<h2>회원가입</h2>
<form className="signup-form" onSubmit={handleSubmit}>
<div className="form-group">
<label>이메일</label>
<input
name="email"
className={`form-input ${errors.email ? 'input-error' : ''} ${verified.email ? 'verified' : ''}`}
type="email"
value={formData.email}
onChange={handleInputChange('email')}
onBlur={handleBlur('email')}
onKeyDown={handleKeyDown('email')}
placeholder="이메일을 입력하세요"
autoComplete="email"
disabled={verified.verificationCode}
/>
{errors.email && <span className="error-message">{errors.email}</span>}
</div>
<div className="form-group">
<label>인증번호</label>
<div className="verification-input-container">
<input
name="verificationCode"
className={`form-input ${errors.verificationCode ? 'input-error' : ''} ${verified.verificationCode ? 'verified' : ''}`}
type="text"
value={formData.verificationCode}
onChange={handleInputChange('verificationCode')}
onBlur={handleBlur('verificationCode')}
onKeyDown={handleKeyDown('verificationCode')}
placeholder="인증번호를 입력하세요"
autoComplete="off"
disabled={verified.verificationCode}
/>
{timerActive && !verified.verificationCode && (
<button
type="button"
className="resend-button"
onClick={handleResendVerification}
>
재전송
</button>
)}
</div>
<div className="verification-messages">
{errors.verificationCode && <div className="error-message">{errors.verificationCode}</div>}
{timerActive && (
<div className="timer-text">
남은 시간: {`${Math.floor(timer / 60)}:${timer % 60 < 10 ? '0' : ''}${timer % 60}`}
</div>
)}
</div>
</div>
<div className="form-group">
<label>닉네임</label>
<input
name="nickname"
className={`form-input ${errors.nickname ? 'input-error' : ''} ${verified.nickname ? 'verified' : ''}`}
type="text"
value={formData.nickname}
onChange={handleInputChange('nickname')}
onBlur={handleBlur('nickname')}
onKeyDown={handleKeyDown('nickname')}
placeholder="닉네임을 입력하세요"
autoComplete="off"
/>
{errors.nickname && <span className="error-message">{errors.nickname}</span>}
</div>
<div className="form-group">
<label>비밀번호</label>
<input
name="password"
className={`form-input ${errors.password ? 'input-error' : ''} ${verified.password ? 'verified' : ''}`}
type="password"
value={formData.password}
onChange={handleInputChange('password')}
onBlur={handleBlur('password')}
onKeyDown={handleKeyDown('password')}
placeholder="비밀번호를 입력하세요"
autoComplete="new-password"
/>
</div>
<div className="form-group">
<label>비밀번호 확인</label>
<input
name="confirmPassword"
className={`form-input ${errors.password ? 'input-error' : ''} ${verified.password ? 'verified' : ''}`}
type="password"
value={formData.confirmPassword}
onChange={handleInputChange('confirmPassword')}
onBlur={handleBlur('password')}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
if (isFormComplete()) {
handleSubmit(e);
}
}
}}
placeholder="비밀번호를 다시 입력하세요"
autoComplete="new-password"
/>
{errors.password && <span className="error-message">{errors.password}</span>}
</div>
{errors.submit && (
<div className="error-message">{errors.submit}</div>
)}
<button
type="submit"
className="submit-button"
disabled={!isFormComplete()}
>
회원가입
</button>
</form>
</div>
</div>
);
};
export default SignupModal;