diff --git a/apps/frontend/components/auth/SignUp.tsx b/apps/frontend/components/auth/SignUp.tsx index dce82e008a..735a71f5f8 100644 --- a/apps/frontend/components/auth/SignUp.tsx +++ b/apps/frontend/components/auth/SignUp.tsx @@ -26,28 +26,30 @@ export default function SignUp() { )} codedang {modalPage === 0 && } {modalPage === 1 && } {modalPage === 2 && } -
- - Already have account? - - -
+ {modalPage === 0 && ( +
+ + Already have account? + + +
+ )} ) } diff --git a/apps/frontend/components/auth/SignUpEmailVerify.tsx b/apps/frontend/components/auth/SignUpEmailVerify.tsx index dab4fe0bf2..f19c714bd0 100644 --- a/apps/frontend/components/auth/SignUpEmailVerify.tsx +++ b/apps/frontend/components/auth/SignUpEmailVerify.tsx @@ -4,9 +4,8 @@ import { baseUrl } from '@/lib/constants' import { cn } from '@/lib/utils' import useSignUpModalStore from '@/stores/signUpModal' import { zodResolver } from '@hookform/resolvers/zod' -import React, { useState, useEffect } from 'react' +import React, { useState, useEffect, useRef } from 'react' import { useForm } from 'react-hook-form' -import { useInterval } from 'react-use' import { z } from 'zod' interface EmailVerifyInput { @@ -26,6 +25,8 @@ const timeLimit = 300 export default function SignUpEmailVerify() { const [timer, setTimer] = useState(timeLimit) + const intervalRef = useRef(null) + const previousTimeRef = useRef(Date.now()) const [expired, setExpired] = useState(false) const { nextModal, setFormData } = useSignUpModalStore((state) => state) const { @@ -43,21 +44,34 @@ export default function SignUpEmailVerify() { const [codeError, setCodeError] = useState('') const [emailVerified, setEmailVerified] = useState(false) const [emailAuthToken, setEmailAuthToken] = useState('') - - useInterval( - () => { - if (timer > 0) { - setTimer((prevTimer) => prevTimer - 1) - } - }, - sentEmail ? 1000 : null - ) + const [sendButtonDisabled, setSendButtonDisabled] = useState(false) useEffect(() => { - if (timer === 0) { - setExpired(true) + if (sentEmail && !expired) { + previousTimeRef.current = Date.now() + intervalRef.current = setInterval(() => { + const currentTime = Date.now() + const elapsedTime = (currentTime - previousTimeRef.current) / 1000 + previousTimeRef.current = currentTime + + setTimer((prevTimer) => { + const updatedTimer = Math.max(prevTimer - Math.round(elapsedTime), 0) + if (updatedTimer === 0) { + setExpired(true) + if (intervalRef.current) { + clearInterval(intervalRef.current) + } + } + return updatedTimer + }) + }, 1000) + } + return () => { + if (intervalRef.current) { + clearInterval(intervalRef.current) + } } - }, [timer]) + }, [sentEmail, expired]) const formatTimer = () => { const minutes = Math.floor(timer / 60) @@ -77,8 +91,9 @@ export default function SignUpEmailVerify() { const sendEmail = async () => { const { email } = getValues() setEmailContent(email) + setEmailError('') await trigger('email') - if (!errors.email && !sentEmail) { + if (!errors.email) { await fetch(baseUrl + '/email-auth/send-email/register-new', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -86,10 +101,12 @@ export default function SignUpEmailVerify() { }) .then((res) => { if (res.status === 409) { - setEmailError('You have already signed up!') + setEmailError('You have already signed up') + setSendButtonDisabled(false) } else if (res.status === 201) { setSentEmail(true) setEmailError('') + setSendButtonDisabled(false) } }) .catch(() => { @@ -116,91 +133,124 @@ export default function SignUpEmailVerify() { setCodeError('') setEmailAuthToken(response.headers.get('email-auth') || '') } else { - setCodeError('Verification code is not valid!') + setCodeError('Verification code is not valid') + setEmailVerified(false) } } catch { - setCodeError('Email verification failed!') + setEmailVerified(false) + setCodeError('Email verification failed') } } } return ( -
- {sentEmail && !expired && ( -

{formatTimer()}

- )} -

Sign Up

+ +

+ Sign up +

{!sentEmail && ( - { - if (e.key === 'Enter') { - e.preventDefault() - sendEmail() - } - }} - /> - )} - {errors.email && ( -

{errors.email?.message}

+
+ { + if (e.key === 'Enter' && !sendButtonDisabled) { + e.preventDefault() + setSendButtonDisabled(true) + sendEmail() + } + }} + /> + {errors.email && ( +

{errors.email?.message}

+ )} + {emailError && ( +

{emailError}

+ )} +
)} -

{emailError}

{sentEmail && ( - <> -
- {emailContent} +
+
+
{emailContent}
+ {sentEmail && !expired && ( +

{formatTimer()}

+ )}
verifyCode() })} /> - - )} -

- {errors.verificationCode ? errors.verificationCode?.message : codeError} -

- {sentEmail && - !expired && - !errors.verificationCode && - codeError === '' && - !emailVerified && ( -

We've sent an email!

- )} - {expired && ( -

- Verification code expired -
- Please resend an email and try again -

+ {sentEmail && + !expired && + !errors.verificationCode && + !codeError && + !emailVerified && ( +

+ We've sent an email +

+ )} + {expired && ( +

+ Verification code expired +
+ Please resend an email and try again +

+ )} + {!expired && ( +

+ {errors.verificationCode + ? errors.verificationCode?.message + : codeError} +

+ )} +
)} + {!sentEmail ? ( ) : !expired ? ( ) : (
- {inputFocus === 2 && ( -
-
    -
  • User ID used for log in
  • -
  • - Your ID must be 3-10 characters of small -
    - alphabet letters, numbers -
  • -
-
- )} - {errors.username ? ( -

Unavailable

- ) : ( - usernameVerify && - (disableUsername ? ( -

Available

- ) : ( -

Unavailable

- )) - )} +
+ {inputFocus !== 1 && ( + <> + {isRequiredError && requiredMessage('Required')} + {isInvalidFormatError && ( +
    +
  • User ID used for log in
  • +
  • 3-10 characters of small letters, numbers
  • +
+ )} + {isAvailable && ( +

Available

+ )} + {isUnavailable &&

Unavailable

} + {shouldCheckUserId && ( +

Check user ID

+ )} + + )} + {inputFocus === 1 && + (!isUsernameAvailable && + checkedUsername === getValues('username') && + getValues('username') ? ( +

Unavailable

+ ) : ( +
+
    +
  • User ID used for log in
  • +
  • 3-10 characters of small letters, numbers
  • +
+
+ ))} +
-
+
validation('password') })} type={passwordShow ? 'text' : 'password'} onFocus={() => { - setInputFocus(3) + updateFocus(2) }} /> setPasswordShow(!passwordShow)} > {passwordShow ? ( @@ -225,38 +426,56 @@ export default function SignUpRegister() { )}
- {inputFocus === 3 && ( -
-
    -
  • - Your password must be at least 8 characters -
  • -
  • and include two of the followings:
  • -
  • Capital letters, Small letters, or Numbers
  • + {inputFocus === 2 && + (errors.password || !getValues('password') ? ( +
    +
      +
    • 8-20 characters
    • +
    • Include two of the followings:
    • +
    • capital letters, small letters, numbers
    • +
    +
    + ) : ( +

    Available

    + ))} + {inputFocus !== 2 && + errors.password && + (errors.password.message == 'Required' ? ( + requiredMessage('Required') + ) : ( +
      +
    • 8-20 characters
    • +
    • Include two of the followings:
    • +
    • capital letters, small letters, numbers
    -
- )} + ))}
-
+
validation('passwordAgain') })} + className={cn( + focusedList[3] && 'ring-1 focus-visible:ring-1', + errors.passwordAgain && + (getValues('passwordAgain') || inputFocus !== 3) + ? 'ring-red-500 focus-visible:ring-red-500' + : 'ring-primary' + )} placeholder="Re-enter password" type={passwordAgainShow ? 'text' : 'password'} onFocus={() => { - setInputFocus(4) + updateFocus(3) }} /> setPasswordAgainShow(!passwordAgainShow)} > {passwordAgainShow ? ( @@ -266,15 +485,139 @@ export default function SignUpRegister() { )}
- {errors.passwordAgain && ( -

Incorrect

- )} + {errors.passwordAgain && + (getValues('passwordAgain') || inputFocus !== 3) && + requiredMessage(errors.passwordAgain.message)} +
+
+
+
+ validation('firstName') + })} + className={cn( + focusedList[4] && 'ring-1 focus-visible:ring-1', + errors.firstName && (getValues('firstName') || inputFocus !== 4) + ? 'ring-red-500 focus-visible:ring-red-500' + : 'ring-primary' + )} + onFocus={() => { + updateFocus(4) + }} + /> + {errors.firstName && + (getValues('firstName') || inputFocus !== 4) && + requiredMessage(errors.firstName.message)} +
+
+ validation('lastName') + })} + className={cn( + focusedList[5] && 'ring-1 focus-visible:ring-1', + errors.lastName && (getValues('lastName') || inputFocus !== 5) + ? 'ring-red-500 focus-visible:ring-red-500' + : 'ring-primary' + )} + onFocus={() => { + updateFocus(5) + }} + /> + {errors.lastName && + (getValues('lastName') || inputFocus !== 5) && + requiredMessage(errors.lastName.message)} +
+
+
+ validation('studentId') + })} + className={cn( + focusedList[6] && 'ring-1 focus-visible:ring-1', + errors.studentId && (getValues('studentId') || inputFocus !== 6) + ? 'ring-red-500 focus-visible:ring-red-500' + : 'ring-primary' + )} + onFocus={() => { + updateFocus(6) + }} + /> + {errors.studentId && + (getValues('studentId') || inputFocus !== 6) && + requiredMessage(errors.studentId.message)} +
+
+ + + + + + + + + No major found. + + + {majors?.map((major) => ( + { + setMajorValue(currentValue) + setMajorOpen(false) + }} + > + + {major} + + ))} + + + + + + + {!majorValue && + focusedList[7] && + !majorOpen && + requiredMessage('Required')}
diff --git a/apps/frontend/components/auth/SignUpWelcome.tsx b/apps/frontend/components/auth/SignUpWelcome.tsx index 20a92b83c7..255f13413f 100644 --- a/apps/frontend/components/auth/SignUpWelcome.tsx +++ b/apps/frontend/components/auth/SignUpWelcome.tsx @@ -12,11 +12,11 @@ import useSignUpModalStore from '@/stores/signUpModal' export default function SignUpWelcome() { const { nextModal } = useSignUpModalStore((state) => state) return ( -
-

- "Welcome to CODEDANG" +

+

+ WELCOME to CODEDANG

- {/*