Skip to content
Snippets Groups Projects
Select Git revision
  • d1fc46c751df29e76f1136d7c14a4556a561605b
  • main default protected
2 results

h

Blame
  • 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;