Skip to content

Commit

Permalink
feat(fe): redesign signup modal (#1911)
Browse files Browse the repository at this point in the history
* feat(fe): redesign signup modal

* fix(be): fix bruno query grammar (#1860)

* chore(be): return including enable-copy-paste (#1867)

* fix(deps): update dependency @codemirror/view to ^6.28.6 (#1838)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* chore(deps): update dependency vitest to v2 (#1839)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* fix(deps): update dependency @sentry/nextjs to v8 (#1731)

* fix(deps): update dependency @sentry/nextjs to v8

* fix(fe): update withSentryconfig usage

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jimin Ha <[email protected]>

* fix(deps): update dependency nestjs-otel to v6 (#1732)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* fix(deps): update all non-major dependencies (#1808)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* fix(ci): disable backend e2e-test on cd-stage (#1871)

* chore: update issue template (#1876)

Notion task와의 연동을 위한 사전 작업입니다.

* fix: fix typo (#1877)

* fix(fe): fix sentry initialization (#1873)

* fix(fe): fix sentry initialization

* refactor(fe): simplify sentry init logic

* fix(infra): separate local minio and stage minio container (#1887)

* fix(infra): separate local minio and stage minio container

* fix(infra): change devcontainer settings of local-minio

* fix(infra): 1886 separate stage local minio container (#1888)

* fix(infra): separate local minio and stage minio container

* fix(infra): change devcontainer settings of local-minio

* fix(infra): change setup container depends-on container

* chore(infra): fix typo

* feat(fe): add editor copy paste preventing (#1874)

* feat(fe): add editor copy paste preventing

* fix(fe): resolve error in submission by adding initial value to enableCopyPaste

* fix(fe): resolve error in non-contest problem by adding initial value

* fix(fe): prevent copy paste when enableCopyPaste is false

* chore(fe): improve code readability

* feat(fe): implement image upload (#1862)

* feat(fe): implement image upload

* fix(fe): fix pnpm lock file

* feat(fe): refactor admin contest structure (#1863)

* chore: refactor admin contest folder structure

* refactor(fe): refactor admin create contest

* refactor(fe): refactor edit contest page

* chore: refactor zod schema, lifting inputStyle

* feat(fe): add show state for hint and source for initial state of switch

* refactor: componentize contest problem list label and button

* feat(fe): add contest detail in contest overall page

* chore: move directory of components only used in contest

* feat(fe): implement contest overall tabs using parallel routes

* chore: wrap tabs with overall folder

* chore(fe): edit color of tailwind config (#1893)

* chore: edit tailwind color config

* chore: apply level color at badge

* chore: change text and bg to dark and light

* feat(fe): redesign main page carousel (#1900)

* feat(fe): update carousel

* feat(fe): replace mono font

* chore(fe): fix
carousel margin

* chore(fe): change font weight

* fix(fe): fix css

* feat(be): add student-id and major field (#1806)

* feat(be): add student-id and major field

* feat(be): prisma migrate for add major, student-id

* feat(be): change default value to dto value

* feat(be): change seed.ts file

* feat(be): change student-id and major optional

* feat(be): prisma migrate

* feat(be): error fix

* feat(be): type error fix

* feat(be): delete annotation

* feat(be): restore bruno

* feat(be): review edit

* feat(be): add default value

* chore(be): rename student-id

* docs(be): add student-id and major

* docs(be): student-id fix

---------

Co-authored-by: Dayong Lee <[email protected]>
Co-authored-by: Jaehyeon Kim <[email protected]>
Co-authored-by: Jaehyeon Kim <[email protected]>

* chore(be): add cascade on announcement model (#1905)

* fix(be): remove unique constraint on student-id field (#1906)

* fix(be): remove unique constraint on student-id field

* chore(be): add migration

* fix(fe): add controlller to major combobox

* feat(fe): redesign signup modal

* feat(fe): redesign signup information fields

* fix(fe): cancel package.json change

* feat(fe): redesign signup modal and fix email timer

* chore(fe): improve code readability

* chore(fe): improve code readability

* chore(fe): improve code redability

* fix(fe): change code for readability

* chore(fe): erase comment

---------

Co-authored-by: jimin9038 <[email protected]>
Co-authored-by: Jaehyeon Kim <[email protected]>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Taehoon Kim <[email protected]>
Co-authored-by: YooJin Lee <[email protected]>
Co-authored-by: Kwon Seo Jin <[email protected]>
Co-authored-by: hwantae <[email protected]>
Co-authored-by: Dayong Lee <[email protected]>
Co-authored-by: Jaehyeon Kim <[email protected]>
Co-authored-by: Jiho Park <[email protected]>
Co-authored-by: jiho <[email protected]>
  • Loading branch information
12 people authored Aug 9, 2024
1 parent f1c10fc commit b4ca303
Show file tree
Hide file tree
Showing 4 changed files with 583 additions and 188 deletions.
30 changes: 16 additions & 14 deletions apps/frontend/components/auth/SignUp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,28 +26,30 @@ export default function SignUp() {
)}

<Image
className="absolute left-8 top-10"
className="absolute top-4"
src={CodedangLogo}
alt="codedang"
width={70}
width={100}
/>

{modalPage === 0 && <SignUpWelcome />}
{modalPage === 1 && <SignUpEmailVerify />}
{modalPage === 2 && <SignUpRegister />}

<div className="absolute bottom-6 flex items-center justify-center">
<span className="h-5 w-fit text-xs leading-5 text-gray-500">
Already have account?
</span>
<Button
onClick={() => showSignIn()}
variant={'link'}
className="h-5 w-fit text-xs text-gray-500"
>
Log In
</Button>
</div>
{modalPage === 0 && (
<div className="absolute bottom-6 flex items-center justify-center">
<span className="h-5 w-fit text-xs leading-5 text-gray-500">
Already have account?
</span>
<Button
onClick={() => showSignIn()}
variant={'link'}
className="h-5 w-fit text-xs text-gray-500"
>
Log In
</Button>
</div>
)}
</div>
)
}
186 changes: 118 additions & 68 deletions apps/frontend/components/auth/SignUpEmailVerify.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -26,6 +25,8 @@ const timeLimit = 300

export default function SignUpEmailVerify() {
const [timer, setTimer] = useState(timeLimit)
const intervalRef = useRef<NodeJS.Timeout | null>(null)
const previousTimeRef = useRef(Date.now())
const [expired, setExpired] = useState(false)
const { nextModal, setFormData } = useSignUpModalStore((state) => state)
const {
Expand All @@ -43,21 +44,34 @@ export default function SignUpEmailVerify() {
const [codeError, setCodeError] = useState<string>('')
const [emailVerified, setEmailVerified] = useState<boolean>(false)
const [emailAuthToken, setEmailAuthToken] = useState<string>('')

useInterval(
() => {
if (timer > 0) {
setTimer((prevTimer) => prevTimer - 1)
}
},
sentEmail ? 1000 : null
)
const [sendButtonDisabled, setSendButtonDisabled] = useState<boolean>(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)
Expand All @@ -77,19 +91,22 @@ 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' },
body: JSON.stringify({ email })
})
.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(() => {
Expand All @@ -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 (
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-1">
{sentEmail && !expired && (
<p className="absolute right-10 top-10 text-red-500">{formatTimer()}</p>
)}
<p className="mb-4 text-left text-xl font-bold text-blue-500">Sign Up</p>
<form
onSubmit={handleSubmit(onSubmit)}
className="flex w-full flex-col gap-2"
>
<p className="mb-4 text-left font-mono text-xl font-bold text-blue-500">
Sign up
</p>
{!sentEmail && (
<Input
id="email"
type="email"
className="w-64"
placeholder="Email Address"
{...register('email')}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault()
sendEmail()
}
}}
/>
)}
{errors.email && (
<p className="text-xs text-red-500">{errors.email?.message}</p>
<div>
<Input
id="email"
type="email"
className={cn(
'focus-visible:ring-primary w-full focus-visible:ring-1',
(emailError || errors.email) && 'ring-1 ring-red-500'
)}
placeholder="[email protected]"
{...register('email')}
onKeyDown={(e) => {
if (e.key === 'Enter' && !sendButtonDisabled) {
e.preventDefault()
setSendButtonDisabled(true)
sendEmail()
}
}}
/>
{errors.email && (
<p className="mt-1 text-xs text-red-500">{errors.email?.message}</p>
)}
{emailError && (
<p className="mt-1 text-xs text-red-500">{emailError}</p>
)}
</div>
)}
<p className="text-xs text-red-500">{emailError}</p>
{sentEmail && (
<>
<div className="text-sm font-semibold text-gray-500">
{emailContent}
<div>
<div className="flex justify-between">
<div className="text-sm text-black">{emailContent}</div>
{sentEmail && !expired && (
<p className="text-red-500">{formatTimer()}</p>
)}
</div>
<Input
type="number"
className="[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
className={cn(
'mt-2 [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none',
'focus-visible:ring-primary w-full focus-visible:ring-1',
(errors.verificationCode || expired || codeError) &&
'ring-1 ring-red-500 focus-visible:ring-red-500'
)}
placeholder="Verification Code"
{...register('verificationCode', {
onChange: () => verifyCode()
})}
/>
</>
)}
<p className="text-xs text-red-500">
{errors.verificationCode ? errors.verificationCode?.message : codeError}
</p>
{sentEmail &&
!expired &&
!errors.verificationCode &&
codeError === '' &&
!emailVerified && (
<p className="text-xs text-blue-500">We&apos;ve sent an email!</p>
)}
{expired && (
<p className="text-xs text-red-500">
Verification code expired
<br />
Please resend an email and try again
</p>
{sentEmail &&
!expired &&
!errors.verificationCode &&
!codeError &&
!emailVerified && (
<p className="text-primary mt-1 text-xs">
We&apos;ve sent an email
</p>
)}
{expired && (
<p className="mt-1 text-xs text-red-500">
Verification code expired
<br />
Please resend an email and try again
</p>
)}
{!expired && (
<p className="mt-1 text-xs text-red-500">
{errors.verificationCode
? errors.verificationCode?.message
: codeError}
</p>
)}
</div>
)}

{!sentEmail ? (
<Button
type="button"
className="mb-8 mt-2 w-64"
onClick={() => sendEmail()}
className="mt-4 w-full font-semibold"
disabled={sendButtonDisabled}
onClick={() => {
setSendButtonDisabled(true)
sendEmail()
}}
>
Send Email
</Button>
) : !expired ? (
<Button
type="submit"
className={cn('mb-8 mt-2 w-64', !emailVerified && 'bg-gray-400')}
disabled={!emailVerified}
className={cn(
'mt-2 w-full font-semibold',
(!emailVerified || !!errors.verificationCode) && 'bg-gray-400'
)}
disabled={!emailVerified || !!errors.verificationCode}
>
Next
</Button>
) : (
<Button
type="submit"
className="mb-8 mt-2 w-64"
className="mt-2 w-full font-semibold"
onClick={() => {
setExpired(false)
setTimer(timeLimit)
Expand Down
Loading

0 comments on commit b4ca303

Please sign in to comment.