From 89c3d32c989f30ae6fe98533cee8a88177e7618b Mon Sep 17 00:00:00 2001 From: mina-gwak Date: Wed, 9 Apr 2025 23:37:47 +0900 Subject: [PATCH 01/10] =?UTF-8?q?Refactor:=20PD-246=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20=ED=95=84=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sign-up/api/use-request-verification.ts | 0 .../sign-up/api/use-verify-code.ts | 0 src/features/sign-up/index.ts | 1 + src/features/sign-up/model/form-values.ts | 39 ++++ src/features/sign-up/model/rules.ts | 52 +++++ src/features/sign-up/ui/field/email.tsx | 183 ++++++++++++++++++ src/pages/sign-up/ui/account.tsx | 178 +---------------- 7 files changed, 282 insertions(+), 171 deletions(-) rename src/{pages => features}/sign-up/api/use-request-verification.ts (100%) rename src/{pages => features}/sign-up/api/use-verify-code.ts (100%) create mode 100644 src/features/sign-up/index.ts create mode 100644 src/features/sign-up/model/form-values.ts create mode 100644 src/features/sign-up/model/rules.ts create mode 100644 src/features/sign-up/ui/field/email.tsx diff --git a/src/pages/sign-up/api/use-request-verification.ts b/src/features/sign-up/api/use-request-verification.ts similarity index 100% rename from src/pages/sign-up/api/use-request-verification.ts rename to src/features/sign-up/api/use-request-verification.ts diff --git a/src/pages/sign-up/api/use-verify-code.ts b/src/features/sign-up/api/use-verify-code.ts similarity index 100% rename from src/pages/sign-up/api/use-verify-code.ts rename to src/features/sign-up/api/use-verify-code.ts diff --git a/src/features/sign-up/index.ts b/src/features/sign-up/index.ts new file mode 100644 index 00000000..3ea06e3c --- /dev/null +++ b/src/features/sign-up/index.ts @@ -0,0 +1 @@ +export { Email } from './ui/field/email' diff --git a/src/features/sign-up/model/form-values.ts b/src/features/sign-up/model/form-values.ts new file mode 100644 index 00000000..b7ff647f --- /dev/null +++ b/src/features/sign-up/model/form-values.ts @@ -0,0 +1,39 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import { SignUpRequest } from 'entities/auth' + +export type FormValues = { + email: string + code: string + password: string + confirmPassword: string + firstName: string + lastName: string + countryId: number | null + genderType: 'MALE' | 'FEMALE' + birthDate: string | null +} + +export const defaultValues: FormValues = { + email: '', + code: '', + password: '', + confirmPassword: '', + firstName: '', + lastName: '', + countryId: null, + genderType: 'FEMALE', + birthDate: null, +} + +export const convertToSignUpDTO = ({ + code, + confirmPassword, + ...restFormValues +}: FormValues): SignUpRequest => { + return { + ...restFormValues, + birthDate: restFormValues.birthDate!, + countryId: restFormValues.countryId!, + } +} diff --git a/src/features/sign-up/model/rules.ts b/src/features/sign-up/model/rules.ts new file mode 100644 index 00000000..093533b3 --- /dev/null +++ b/src/features/sign-up/model/rules.ts @@ -0,0 +1,52 @@ +import { FormValues } from './form-values' + +const REG_EMAIL = + /^([A-Za-z0-9]+([-_.]?[A-Za-z0-9])*)@([A-Za-z0-9]+([-]?[A-Za-z0-9])*)(\.([A-Za-z0-9]+([-]?[A-Za-z0-9])*))?(\.([A-Za-z0-9]([-]?[A-Za-z0-9])*))?((\.[A-Za-z]{2,63})$)/ + +export const email = { + required: 'Please enter your email', + maxLength: { + value: 254, + message: 'Input exceeds maximum allowed length of 254 characters', + }, + pattern: { + value: REG_EMAIL, + message: "Invalid email format. Please use the format 'example@domain.com'", + }, +} + +export const code = { + required: 'Please enter your email verification code', +} + +const REG_UPPER_LOWER_CASE_LETTERS = /(?=.*[a-z])(?=.*[A-Z])/ +const REG_NUMBER = /(?=.*\d)/ +const REG_SPECIAL_CHAR = /(?=.*[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?])/ + +export const isCorrectLength = (value: string) => + value.length >= 9 && value.length <= 28 + +export const hasNumber = (value: string) => REG_NUMBER.test(value) + +export const hasLowercaseAndUppercaseLetter = (value: string) => + REG_UPPER_LOWER_CASE_LETTERS.test(value) + +export const hasSpecialChar = (value: string) => REG_SPECIAL_CHAR.test(value) + +export const password = { + required: true, + validate: (value: string) => + isCorrectLength(value) && + hasNumber(value) && + hasLowercaseAndUppercaseLetter(value) && + hasSpecialChar(value), +} + +export const confirmPassword = { + required: 'Please enter your password', + validate: (value: string | number | null, formValues: FormValues) => { + if (value === formValues.password) return true + + return 'The password you entered do not match' + }, +} diff --git a/src/features/sign-up/ui/field/email.tsx b/src/features/sign-up/ui/field/email.tsx new file mode 100644 index 00000000..91eff876 --- /dev/null +++ b/src/features/sign-up/ui/field/email.tsx @@ -0,0 +1,183 @@ +import { useState } from 'react' +import { useFormContext, useWatch } from 'react-hook-form' +import { toast } from 'react-toastify' + +import { + EmailVerificationCodeExceptionCode, + HttpError, + ResourceNotFoundExceptionCode, + UserExceptionCode, +} from 'shared/api' +import { fieldCss, Form, hasError } from 'shared/form' +import { Button, HelperText, Label } from 'shared/ui' + +import { useRequestVerification } from '../../api/use-request-verification' +import { useVerifyCode } from '../../api/use-verify-code' +import { FormValues } from '../../model/form-values' +import { email as emailRule, code as codeRule } from '../../model/rules' + +export const Email = () => { + const [showVerificationField, setShowVerificationField] = useState(false) + + const { + trigger, + getValues, + setError, + formState: { errors }, + reset, + control, + } = useFormContext() + + const email = useWatch({ + name: 'email', + control, + }) + + const requestVerification = useRequestVerification() + const verifyCode = useVerifyCode() + + const handleEmailChange = () => { + if (!(requestVerification.isPending || requestVerification.isIdle)) { + requestVerification.reset() + } + + if (!(verifyCode.isPending || verifyCode.isIdle)) { + verifyCode.reset() + reset({ ...getValues(), code: '' }) + } + } + + const handleRequestVerificationSuccess = () => { + toast.success('A verification email has been sent') + + if (!showVerificationField) setShowVerificationField(true) + } + + const handleRequestVerificationError = (error: HttpError) => { + if (error.code === EmailVerificationCodeExceptionCode.TOO_MANY_REQUEST) { + toast.error( + 'You have requested the verification code too many times. Please try again in 10 minutes.', + ) + } else if (error.code === UserExceptionCode.ALREADY_USED_EMAIL) { + setError('email', { + message: 'An account with that email already exists', + }) + } else { + toast.error('An error occurred while requesting verification') + } + } + + const handleSendButtonClick = async () => { + const isEmailValid = await trigger('email') + + if (!isEmailValid) return + + const data = { email } + + requestVerification.mutate(data, { + onSuccess: handleRequestVerificationSuccess, + onError: handleRequestVerificationError, + }) + } + + const handleVerifyCodeError = (error: HttpError) => { + if ( + error.code === EmailVerificationCodeExceptionCode.ALREADY_VERIFIED_CODE + ) { + setError('code', { + message: 'The verification code you entered has already been used', + }) + } else if (error.code === EmailVerificationCodeExceptionCode.EXPIRED_CODE) { + setError('code', { + message: 'The verification code has expired. Please request a new one.', + }) + } else if ( + error.code === + ResourceNotFoundExceptionCode.EMAIL_VERIFICATION_CODE_NOT_FOUND + ) { + setError('code', { + message: 'The verification code you entered is incorrect', + }) + } + } + + const handleCheckButtonClick = async () => { + const isCodeValid = await trigger('code') + + if (!isCodeValid) return + + if (requestVerification.isPending || requestVerification.isIdle) { + setError('email', { + message: 'Please verify the email first', + }) + + return + } + + const data = { email, code: getValues('code') } + + verifyCode.mutate(data, { + onError: handleVerifyCodeError, + }) + } + + return ( +
+ +
+
+ + + + +
+ +
+ + {showVerificationField && ( +
+
+ + + + + {!hasError(errors?.code) && verifyCode.isSuccess && ( + + Authentication completed + + )} + {!hasError(errors?.code) && !verifyCode.isSuccess && ( + Please enter the code sent to the email + )} +
+ +
+ )} +
+ ) +} diff --git a/src/pages/sign-up/ui/account.tsx b/src/pages/sign-up/ui/account.tsx index 20125296..2871cbd5 100644 --- a/src/pages/sign-up/ui/account.tsx +++ b/src/pages/sign-up/ui/account.tsx @@ -1,21 +1,12 @@ 'use client' -import { useState } from 'react' import { useFormContext, useWatch } from 'react-hook-form' -import { toast } from 'react-toastify' -import { - EmailVerificationCodeExceptionCode, - HttpError, - ResourceNotFoundExceptionCode, - UserExceptionCode, -} from 'shared/api' -import { Form, hasError, fieldCss } from 'shared/form' +import { Email } from 'features/sign-up' +import { Form, fieldCss } from 'shared/form' import { isEmptyString } from 'shared/lib' -import { Button, Heading, HelperText, Label } from 'shared/ui' +import { Heading, HelperText, Label } from 'shared/ui' -import { useRequestVerification } from '../api/use-request-verification' -import { useVerifyCode } from '../api/use-verify-code' import { FormValues } from '../model/form-values' import * as rules from '../model/rules' import { @@ -26,25 +17,13 @@ import { } from '../model/rules' export const Account = () => { - const [showVerificationField, setShowVerificationField] = useState(false) - - const { - trigger, - getValues, - setError, - formState: { errors }, - reset, - control, - } = useFormContext() + const { control } = useFormContext() - const [email, password] = useWatch({ - name: ['email', 'password'], + const password = useWatch({ + name: 'password', control, }) - const requestVerification = useRequestVerification() - const verifyCode = useVerifyCode() - const checkPasswordCondition = (condition: (value: string) => boolean) => { if (isEmptyString(password)) return 'default' if (condition(password)) return 'success' @@ -52,91 +31,6 @@ export const Account = () => { return 'error' } - const handleRequestVerificationSuccess = () => { - toast.success('A verification email has been sent') - - if (!showVerificationField) setShowVerificationField(true) - } - - const handleRequestVerificationError = (error: HttpError) => { - if (error.code === EmailVerificationCodeExceptionCode.TOO_MANY_REQUEST) { - toast.error( - 'You have requested the verification code too many times. Please try again in 10 minutes.', - ) - } else if (error.code === UserExceptionCode.ALREADY_USED_EMAIL) { - setError('email', { - message: 'An account with that email already exists', - }) - } else { - toast.error('An error occurred while requesting verification') - } - } - - const handleSendButtonClick = async () => { - const isEmailValid = await trigger('email') - - if (!isEmailValid) return - - const data = { email } - - requestVerification.mutate(data, { - onSuccess: handleRequestVerificationSuccess, - onError: handleRequestVerificationError, - }) - } - - const handleEmailChange = () => { - if (!(requestVerification.isPending || requestVerification.isIdle)) { - requestVerification.reset() - } - - if (!(verifyCode.isPending || verifyCode.isIdle)) { - verifyCode.reset() - reset({ ...getValues(), code: '' }) - } - } - - const handleVerifyCodeError = (error: HttpError) => { - if ( - error.code === EmailVerificationCodeExceptionCode.ALREADY_VERIFIED_CODE - ) { - setError('code', { - message: 'The verification code you entered has already been used', - }) - } else if (error.code === EmailVerificationCodeExceptionCode.EXPIRED_CODE) { - setError('code', { - message: 'The verification code has expired. Please request a new one.', - }) - } else if ( - error.code === - ResourceNotFoundExceptionCode.EMAIL_VERIFICATION_CODE_NOT_FOUND - ) { - setError('code', { - message: 'The verification code you entered is incorrect', - }) - } - } - - const handleCheckButtonClick = async () => { - const isCodeValid = await trigger('code') - - if (!isCodeValid) return - - if (requestVerification.isPending || requestVerification.isIdle) { - setError('email', { - message: 'Please verify the email first', - }) - - return - } - - const data = { email, code: getValues('code') } - - verifyCode.mutate(data, { - onError: handleVerifyCodeError, - }) - } - return (
@@ -144,65 +38,7 @@ export const Account = () => {
-
- -
-
- - - - -
- -
- - {showVerificationField && ( -
-
- - - - - {!hasError(errors?.code) && verifyCode.isSuccess && ( - - Authentication completed - - )} - {!hasError(errors?.code) && !verifyCode.isSuccess && ( - - Please enter the code sent to the email - - )} -
- -
- )} -
+
From 1f5b27279bbbe9e5c0c188d8e0caefdce26f7980 Mon Sep 17 00:00:00 2001 From: mina-gwak Date: Wed, 9 Apr 2025 23:44:39 +0900 Subject: [PATCH 02/10] =?UTF-8?q?Refactor:=20PD-246=20=EB=B9=84=EB=B0=80?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=ED=95=84=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/sign-up/index.ts | 1 + src/features/sign-up/ui/field/password.tsx | 60 +++++++++++++++++++++ src/pages/sign-up/ui/account.tsx | 63 ++-------------------- 3 files changed, 64 insertions(+), 60 deletions(-) create mode 100644 src/features/sign-up/ui/field/password.tsx diff --git a/src/features/sign-up/index.ts b/src/features/sign-up/index.ts index 3ea06e3c..28b75e43 100644 --- a/src/features/sign-up/index.ts +++ b/src/features/sign-up/index.ts @@ -1 +1,2 @@ export { Email } from './ui/field/email' +export { Password } from './ui/field/password' diff --git a/src/features/sign-up/ui/field/password.tsx b/src/features/sign-up/ui/field/password.tsx new file mode 100644 index 00000000..ebae17e5 --- /dev/null +++ b/src/features/sign-up/ui/field/password.tsx @@ -0,0 +1,60 @@ +import { useFormContext, useWatch } from 'react-hook-form' + +import { fieldCss, Form } from 'shared/form' +import { isEmptyString } from 'shared/lib' +import { HelperText, Label } from 'shared/ui' + +import { FormValues } from '../../model/form-values' +import { + hasLowercaseAndUppercaseLetter, + hasNumber, + hasSpecialChar, + isCorrectLength, + password as passwordRule, +} from '../../model/rules' + +export const Password = () => { + const { control } = useFormContext() + + const password = useWatch({ + name: 'password', + control, + }) + + const checkPasswordCondition = (condition: (value: string) => boolean) => { + if (isEmptyString(password)) return 'default' + if (condition(password)) return 'success' + + return 'error' + } + + return ( +
+ +
+ + + 9 ~ 28 characters long + + + Upper & lower case letters + + + At least one number + + + At least special character + +
+
+ ) +} diff --git a/src/pages/sign-up/ui/account.tsx b/src/pages/sign-up/ui/account.tsx index 2871cbd5..32725b99 100644 --- a/src/pages/sign-up/ui/account.tsx +++ b/src/pages/sign-up/ui/account.tsx @@ -1,36 +1,12 @@ 'use client' -import { useFormContext, useWatch } from 'react-hook-form' - -import { Email } from 'features/sign-up' +import { Email, Password } from 'features/sign-up' import { Form, fieldCss } from 'shared/form' -import { isEmptyString } from 'shared/lib' -import { Heading, HelperText, Label } from 'shared/ui' +import { Heading, Label } from 'shared/ui' -import { FormValues } from '../model/form-values' import * as rules from '../model/rules' -import { - isCorrectLength, - hasLowercaseAndUppercaseLetter, - hasNumber, - hasSpecialChar, -} from '../model/rules' export const Account = () => { - const { control } = useFormContext() - - const password = useWatch({ - name: 'password', - control, - }) - - const checkPasswordCondition = (condition: (value: string) => boolean) => { - if (isEmptyString(password)) return 'default' - if (condition(password)) return 'success' - - return 'error' - } - return (
@@ -39,40 +15,7 @@ export const Account = () => {
- -
- -
- - - 9 ~ 28 characters long - - - Upper & lower case letters - - - At least one number - - - At least special character - -
-
+
From dc7e1c360ffd0b73c2bb33d0802414a6a741d22d Mon Sep 17 00:00:00 2001 From: mina-gwak Date: Wed, 9 Apr 2025 23:46:38 +0900 Subject: [PATCH 03/10] =?UTF-8?q?Refactor:=20PD-246=20=EB=B9=84=EB=B0=80?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=ED=99=95=EC=9D=B8=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/sign-up/index.ts | 1 + .../sign-up/ui/field/confirm-password.tsx | 20 +++++++++++++++++++ src/pages/sign-up/ui/account.tsx | 20 +++---------------- 3 files changed, 24 insertions(+), 17 deletions(-) create mode 100644 src/features/sign-up/ui/field/confirm-password.tsx diff --git a/src/features/sign-up/index.ts b/src/features/sign-up/index.ts index 28b75e43..a56bc801 100644 --- a/src/features/sign-up/index.ts +++ b/src/features/sign-up/index.ts @@ -1,2 +1,3 @@ export { Email } from './ui/field/email' export { Password } from './ui/field/password' +export { ConfirmPassword } from './ui/field/confirm-password' diff --git a/src/features/sign-up/ui/field/confirm-password.tsx b/src/features/sign-up/ui/field/confirm-password.tsx new file mode 100644 index 00000000..c2106630 --- /dev/null +++ b/src/features/sign-up/ui/field/confirm-password.tsx @@ -0,0 +1,20 @@ +import { fieldCss, Form } from 'shared/form' +import { Label } from 'shared/ui' + +import { confirmPassword as confirmPasswordRule } from '../../model/rules' + +export const ConfirmPassword = () => { + return ( +
+ + + + + +
+ ) +} diff --git a/src/pages/sign-up/ui/account.tsx b/src/pages/sign-up/ui/account.tsx index 32725b99..ae1f3b5a 100644 --- a/src/pages/sign-up/ui/account.tsx +++ b/src/pages/sign-up/ui/account.tsx @@ -1,10 +1,7 @@ 'use client' -import { Email, Password } from 'features/sign-up' -import { Form, fieldCss } from 'shared/form' -import { Heading, Label } from 'shared/ui' - -import * as rules from '../model/rules' +import { Email, Password, ConfirmPassword } from 'features/sign-up' +import { Heading } from 'shared/ui' export const Account = () => { return ( @@ -16,18 +13,7 @@ export const Account = () => {
- -
- - - - - -
+
) From f6512f00796b15421d1c12ce67eeaa66913307f7 Mon Sep 17 00:00:00 2001 From: mina-gwak Date: Wed, 9 Apr 2025 23:59:24 +0900 Subject: [PATCH 04/10] =?UTF-8?q?Refactor:=20PD-246=20=EB=82=98=EB=A8=B8?= =?UTF-8?q?=EC=A7=80=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/sign-up/index.ts | 8 ++- .../sign-up/lib/use-email-validation-state.ts | 0 src/features/sign-up/model/rules.ts | 24 ++++++++ src/features/sign-up/ui/field/birth-date.tsx | 16 +++++ src/features/sign-up/ui/field/first-name.tsx | 16 +++++ src/features/sign-up/ui/field/gender.tsx | 16 +++++ src/features/sign-up/ui/field/last-name.tsx | 16 +++++ src/features/sign-up/ui/field/nationality.tsx | 17 ++++++ src/pages/sign-up/model/rules.ts | 52 ----------------- src/pages/sign-up/ui/sign-up-page/index.tsx | 2 +- src/widgets/my-account/model/rules.ts | 23 -------- .../my-account/ui/personal-information.tsx | 58 ++++--------------- 12 files changed, 125 insertions(+), 123 deletions(-) rename src/{pages => features}/sign-up/lib/use-email-validation-state.ts (100%) create mode 100644 src/features/sign-up/ui/field/birth-date.tsx create mode 100644 src/features/sign-up/ui/field/first-name.tsx create mode 100644 src/features/sign-up/ui/field/gender.tsx create mode 100644 src/features/sign-up/ui/field/last-name.tsx create mode 100644 src/features/sign-up/ui/field/nationality.tsx delete mode 100644 src/pages/sign-up/model/rules.ts delete mode 100644 src/widgets/my-account/model/rules.ts diff --git a/src/features/sign-up/index.ts b/src/features/sign-up/index.ts index a56bc801..f63461a7 100644 --- a/src/features/sign-up/index.ts +++ b/src/features/sign-up/index.ts @@ -1,3 +1,9 @@ +export { BirthDate } from './ui/field/birth-date' +export { ConfirmPassword } from './ui/field/confirm-password' export { Email } from './ui/field/email' +export { FirstName } from './ui/field/first-name' +export { Gender } from './ui/field/gender' +export { LastName } from './ui/field/last-name' +export { Nationality } from './ui/field/nationality' export { Password } from './ui/field/password' -export { ConfirmPassword } from './ui/field/confirm-password' +export { useEmailValidationState } from './lib/use-email-validation-state' diff --git a/src/pages/sign-up/lib/use-email-validation-state.ts b/src/features/sign-up/lib/use-email-validation-state.ts similarity index 100% rename from src/pages/sign-up/lib/use-email-validation-state.ts rename to src/features/sign-up/lib/use-email-validation-state.ts diff --git a/src/features/sign-up/model/rules.ts b/src/features/sign-up/model/rules.ts index 093533b3..84e6a3f8 100644 --- a/src/features/sign-up/model/rules.ts +++ b/src/features/sign-up/model/rules.ts @@ -50,3 +50,27 @@ export const confirmPassword = { return 'The password you entered do not match' }, } + +export const firstName = { + required: 'Please enter your first name', + maxLength: { + value: 254, + message: 'First name exceeds the maximum allowed length of 254 characters', + }, +} + +export const lastName = { + required: 'Please enter your last name', + maxLength: { + value: 254, + message: 'Last name exceeds the maximum allowed length of 254 characters', + }, +} + +export const country = { + required: 'Please select your nationality', +} + +export const birthDate = { + required: 'Please enter your date of birth', +} diff --git a/src/features/sign-up/ui/field/birth-date.tsx b/src/features/sign-up/ui/field/birth-date.tsx new file mode 100644 index 00000000..dc15f13a --- /dev/null +++ b/src/features/sign-up/ui/field/birth-date.tsx @@ -0,0 +1,16 @@ +import { fieldCss, Form } from 'shared/form' +import { Label } from 'shared/ui' + +import { birthDate as birthDateRule } from '../../model/rules' + +export const BirthDate = () => { + return ( +
+ + + + + +
+ ) +} diff --git a/src/features/sign-up/ui/field/first-name.tsx b/src/features/sign-up/ui/field/first-name.tsx new file mode 100644 index 00000000..0b5835c9 --- /dev/null +++ b/src/features/sign-up/ui/field/first-name.tsx @@ -0,0 +1,16 @@ +import { fieldCss, Form } from 'shared/form' +import { Label } from 'shared/ui' + +import { firstName as firstNameRule } from '../../model/rules' + +export const FirstName = () => { + return ( +
+ + + + + +
+ ) +} diff --git a/src/features/sign-up/ui/field/gender.tsx b/src/features/sign-up/ui/field/gender.tsx new file mode 100644 index 00000000..61d73afa --- /dev/null +++ b/src/features/sign-up/ui/field/gender.tsx @@ -0,0 +1,16 @@ +import { fieldCss, Form } from 'shared/form' +import { Label } from 'shared/ui' + +export const Gender = () => { + return ( +
+ + + + + + + +
+ ) +} diff --git a/src/features/sign-up/ui/field/last-name.tsx b/src/features/sign-up/ui/field/last-name.tsx new file mode 100644 index 00000000..039ac8c0 --- /dev/null +++ b/src/features/sign-up/ui/field/last-name.tsx @@ -0,0 +1,16 @@ +import { fieldCss, Form } from 'shared/form' +import { Label } from 'shared/ui' + +import { lastName as lastNameRule } from '../../model/rules' + +export const LastName = () => { + return ( +
+ + + + + +
+ ) +} diff --git a/src/features/sign-up/ui/field/nationality.tsx b/src/features/sign-up/ui/field/nationality.tsx new file mode 100644 index 00000000..ad01ce26 --- /dev/null +++ b/src/features/sign-up/ui/field/nationality.tsx @@ -0,0 +1,17 @@ +import { CountrySelect } from 'entities/country' +import { fieldCss, Form } from 'shared/form' +import { Label } from 'shared/ui' + +import { country as countryRule } from '../../model/rules' + +export const Nationality = () => { + return ( +
+ + + + + +
+ ) +} diff --git a/src/pages/sign-up/model/rules.ts b/src/pages/sign-up/model/rules.ts deleted file mode 100644 index e94d974c..00000000 --- a/src/pages/sign-up/model/rules.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { FormValues } from '../model/form-values' - -const REG_EMAIL = - /^([A-Za-z0-9]+([-_.]?[A-Za-z0-9])*)@([A-Za-z0-9]+([-]?[A-Za-z0-9])*)(\.([A-Za-z0-9]+([-]?[A-Za-z0-9])*))?(\.([A-Za-z0-9]([-]?[A-Za-z0-9])*))?((\.[A-Za-z]{2,63})$)/ - -export const email = { - required: 'Please enter your email', - maxLength: { - value: 254, - message: 'Input exceeds maximum allowed length of 254 characters', - }, - pattern: { - value: REG_EMAIL, - message: "Invalid email format. Please use the format 'example@domain.com'", - }, -} - -export const code = { - required: 'Please enter your email verification code', -} - -const REG_UPPER_LOWER_CASE_LETTERS = /(?=.*[a-z])(?=.*[A-Z])/ -const REG_NUMBER = /(?=.*\d)/ -const REG_SPECIAL_CHAR = /(?=.*[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>/?])/ - -export const isCorrectLength = (value: string) => - value.length >= 9 && value.length <= 28 - -export const hasNumber = (value: string) => REG_NUMBER.test(value) - -export const hasLowercaseAndUppercaseLetter = (value: string) => - REG_UPPER_LOWER_CASE_LETTERS.test(value) - -export const hasSpecialChar = (value: string) => REG_SPECIAL_CHAR.test(value) - -export const password = { - required: true, - validate: (value: string) => - isCorrectLength(value) && - hasNumber(value) && - hasLowercaseAndUppercaseLetter(value) && - hasSpecialChar(value), -} - -export const confirmPassword = { - required: 'Please enter your password', - validate: (value: string | number | null, formValues: FormValues) => { - if (value === formValues.password) return true - - return 'The password you entered do not match' - }, -} diff --git a/src/pages/sign-up/ui/sign-up-page/index.tsx b/src/pages/sign-up/ui/sign-up-page/index.tsx index 6d6854de..0a34c70f 100644 --- a/src/pages/sign-up/ui/sign-up-page/index.tsx +++ b/src/pages/sign-up/ui/sign-up-page/index.tsx @@ -5,12 +5,12 @@ import { useForm } from 'react-hook-form' import { toast } from 'react-toastify' import { signUp } from 'entities/auth' +import { useEmailValidationState } from 'features/sign-up' import { isServerError, useServerErrorHandler } from 'shared/api' import { Form } from 'shared/form' import { useCheckbox } from 'shared/lib' import { Button, Checkbox, Layout, Link, linkVariants } from 'shared/ui' -import { useEmailValidationState } from '../../lib/use-email-validation-state' import { FormValues, defaultValues, diff --git a/src/widgets/my-account/model/rules.ts b/src/widgets/my-account/model/rules.ts deleted file mode 100644 index 6cecdcab..00000000 --- a/src/widgets/my-account/model/rules.ts +++ /dev/null @@ -1,23 +0,0 @@ -export const firstName = { - required: 'Please enter your first name', - maxLength: { - value: 254, - message: 'First name exceeds the maximum allowed length of 254 characters', - }, -} - -export const lastName = { - required: 'Please enter your last name', - maxLength: { - value: 254, - message: 'Last name exceeds the maximum allowed length of 254 characters', - }, -} - -export const country = { - required: 'Please select your nationality', -} - -export const birthDate = { - required: 'Please enter your date of birth', -} diff --git a/src/widgets/my-account/ui/personal-information.tsx b/src/widgets/my-account/ui/personal-information.tsx index 215e5fb0..bb51c112 100644 --- a/src/widgets/my-account/ui/personal-information.tsx +++ b/src/widgets/my-account/ui/personal-information.tsx @@ -1,8 +1,10 @@ -import { CountrySelect } from 'entities/country' -import { Form, fieldCss } from 'shared/form' -import { Label } from 'shared/ui' - -import * as rules from '../model/rules' +import { + BirthDate, + FirstName, + LastName, + Nationality, + Gender, +} from 'features/sign-up' type Props = { className?: string @@ -11,47 +13,11 @@ type Props = { export const PersonalInformationForm = ({ className }: Props) => { return (
-
- - - - - -
- -
- - - - - -
- -
- - - - - -
- -
- - - - - - - -
- -
- - - - - -
+ + + + +
) } From 6b608161e35a6d586b94967cd6e56fd2ae3740f1 Mon Sep 17 00:00:00 2001 From: mina-gwak Date: Thu, 10 Apr 2025 00:18:28 +0900 Subject: [PATCH 05/10] =?UTF-8?q?Feat:=20PD-246=20=ED=95=99=EC=9B=90=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=A4=91=20=EC=9D=B4=EB=A6=84,=20?= =?UTF-8?q?=ED=95=99=EC=9B=90=20=EC=9D=B4=EB=A6=84,=20=ED=95=99=EC=9B=90?= =?UTF-8?q?=20=EC=98=81=EB=AC=B8=20=EC=9D=B4=EB=A6=84,=20=EB=8C=80?= =?UTF-8?q?=ED=91=9C=EC=9E=90=EB=AA=85=20=ED=95=84=EB=93=9C=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/sign-up/index.ts | 4 ++++ src/features/sign-up/ui/field/academy-name-en.tsx | 14 ++++++++++++++ src/features/sign-up/ui/field/academy-name.tsx | 14 ++++++++++++++ src/features/sign-up/ui/field/full-name.tsx | 14 ++++++++++++++ .../sign-up/ui/field/representative-name.tsx | 14 ++++++++++++++ 5 files changed, 60 insertions(+) create mode 100644 src/features/sign-up/ui/field/academy-name-en.tsx create mode 100644 src/features/sign-up/ui/field/academy-name.tsx create mode 100644 src/features/sign-up/ui/field/full-name.tsx create mode 100644 src/features/sign-up/ui/field/representative-name.tsx diff --git a/src/features/sign-up/index.ts b/src/features/sign-up/index.ts index f63461a7..db79d7f4 100644 --- a/src/features/sign-up/index.ts +++ b/src/features/sign-up/index.ts @@ -1,9 +1,13 @@ +export { AcademyName } from './ui/field/academy-name' +export { AcademyNameEn } from './ui/field/academy-name-en' export { BirthDate } from './ui/field/birth-date' export { ConfirmPassword } from './ui/field/confirm-password' export { Email } from './ui/field/email' export { FirstName } from './ui/field/first-name' +export { FullName } from './ui/field/full-name' export { Gender } from './ui/field/gender' export { LastName } from './ui/field/last-name' export { Nationality } from './ui/field/nationality' export { Password } from './ui/field/password' +export { RepresentativeName } from './ui/field/representative-name' export { useEmailValidationState } from './lib/use-email-validation-state' diff --git a/src/features/sign-up/ui/field/academy-name-en.tsx b/src/features/sign-up/ui/field/academy-name-en.tsx new file mode 100644 index 00000000..188a9270 --- /dev/null +++ b/src/features/sign-up/ui/field/academy-name-en.tsx @@ -0,0 +1,14 @@ +import { fieldCss, Form } from 'shared/form' +import { Label } from 'shared/ui' + +export const AcademyNameEn = () => { + return ( +
+ + + + + +
+ ) +} diff --git a/src/features/sign-up/ui/field/academy-name.tsx b/src/features/sign-up/ui/field/academy-name.tsx new file mode 100644 index 00000000..11f09d82 --- /dev/null +++ b/src/features/sign-up/ui/field/academy-name.tsx @@ -0,0 +1,14 @@ +import { fieldCss, Form } from 'shared/form' +import { Label } from 'shared/ui' + +export const AcademyName = () => { + return ( +
+ + + + + +
+ ) +} diff --git a/src/features/sign-up/ui/field/full-name.tsx b/src/features/sign-up/ui/field/full-name.tsx new file mode 100644 index 00000000..66c8a11a --- /dev/null +++ b/src/features/sign-up/ui/field/full-name.tsx @@ -0,0 +1,14 @@ +import { fieldCss, Form } from 'shared/form' +import { Label } from 'shared/ui' + +export const FullName = () => { + return ( +
+ + + + + +
+ ) +} diff --git a/src/features/sign-up/ui/field/representative-name.tsx b/src/features/sign-up/ui/field/representative-name.tsx new file mode 100644 index 00000000..195dd204 --- /dev/null +++ b/src/features/sign-up/ui/field/representative-name.tsx @@ -0,0 +1,14 @@ +import { fieldCss, Form } from 'shared/form' +import { Label } from 'shared/ui' + +export const RepresentativeName = () => { + return ( +
+ + + + + +
+ ) +} From 3521adc83bcc038e438f793ab19d5434e5e2eded Mon Sep 17 00:00:00 2001 From: mina-gwak Date: Thu, 10 Apr 2025 00:19:17 +0900 Subject: [PATCH 06/10] =?UTF-8?q?Fix:=20PD-246=20=EB=B9=84=EB=B0=80?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=ED=99=95=EC=9D=B8=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/sign-up/ui/field/confirm-password.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/sign-up/ui/field/confirm-password.tsx b/src/features/sign-up/ui/field/confirm-password.tsx index c2106630..bb17c133 100644 --- a/src/features/sign-up/ui/field/confirm-password.tsx +++ b/src/features/sign-up/ui/field/confirm-password.tsx @@ -5,7 +5,7 @@ import { confirmPassword as confirmPasswordRule } from '../../model/rules' export const ConfirmPassword = () => { return ( -
+
Date: Thu, 10 Apr 2025 00:23:47 +0900 Subject: [PATCH 07/10] =?UTF-8?q?Feat:=20PD-246=20=EC=82=AC=EC=97=85?= =?UTF-8?q?=EC=9E=90=20=EB=93=B1=EB=A1=9D=EB=B2=88=ED=98=B8=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/sign-up/index.ts | 1 + .../ui/field/business-registration-number.tsx | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 src/features/sign-up/ui/field/business-registration-number.tsx diff --git a/src/features/sign-up/index.ts b/src/features/sign-up/index.ts index db79d7f4..88a84900 100644 --- a/src/features/sign-up/index.ts +++ b/src/features/sign-up/index.ts @@ -1,6 +1,7 @@ export { AcademyName } from './ui/field/academy-name' export { AcademyNameEn } from './ui/field/academy-name-en' export { BirthDate } from './ui/field/birth-date' +export { BusinessRegistrationNumber } from './ui/field/business-registration-number' export { ConfirmPassword } from './ui/field/confirm-password' export { Email } from './ui/field/email' export { FirstName } from './ui/field/first-name' diff --git a/src/features/sign-up/ui/field/business-registration-number.tsx b/src/features/sign-up/ui/field/business-registration-number.tsx new file mode 100644 index 00000000..805194f0 --- /dev/null +++ b/src/features/sign-up/ui/field/business-registration-number.tsx @@ -0,0 +1,14 @@ +import { fieldCss, Form } from 'shared/form' +import { Label } from 'shared/ui' + +export const BusinessRegistrationNumber = () => { + return ( +
+ + + + + +
+ ) +} From 28140dba4f8f944e70fb111eda35df7541526c12 Mon Sep 17 00:00:00 2001 From: mina-gwak Date: Thu, 10 Apr 2025 00:32:05 +0900 Subject: [PATCH 08/10] =?UTF-8?q?Feat:=20PD-246=20=EC=A3=BC=EC=86=8C=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EB=A7=88=ED=81=AC=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/sign-up/index.ts | 1 + src/features/sign-up/ui/field/address.tsx | 18 ++++++++++++++++++ src/shared/ui/button/button.tsx | 3 +-- 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 src/features/sign-up/ui/field/address.tsx diff --git a/src/features/sign-up/index.ts b/src/features/sign-up/index.ts index 88a84900..4df4f2ea 100644 --- a/src/features/sign-up/index.ts +++ b/src/features/sign-up/index.ts @@ -1,5 +1,6 @@ export { AcademyName } from './ui/field/academy-name' export { AcademyNameEn } from './ui/field/academy-name-en' +export { Address } from './ui/field/address' export { BirthDate } from './ui/field/birth-date' export { BusinessRegistrationNumber } from './ui/field/business-registration-number' export { ConfirmPassword } from './ui/field/confirm-password' diff --git a/src/features/sign-up/ui/field/address.tsx b/src/features/sign-up/ui/field/address.tsx new file mode 100644 index 00000000..b226aa64 --- /dev/null +++ b/src/features/sign-up/ui/field/address.tsx @@ -0,0 +1,18 @@ +import { fieldCss } from 'shared/form' +import { Button, Label, TextField } from 'shared/ui' + +export const Address = () => { + return ( +
+ +
+ + +
+ + +
+ ) +} diff --git a/src/shared/ui/button/button.tsx b/src/shared/ui/button/button.tsx index d053972a..2f5eb949 100644 --- a/src/shared/ui/button/button.tsx +++ b/src/shared/ui/button/button.tsx @@ -13,9 +13,8 @@ import { cn } from 'shared/lib' import { button, ButtonVariants } from 'shared/ui/button/variants' import { Icon } from '../icon' -import { IconType } from '../icon/assets' - import { ButtonContext, useButtonContext } from './context' +import { IconType } from '../icon/assets' type ButtonProps = ButtonVariants & { as?: 'button' | 'a' From 36114fff51e7291ea4e6824818c31c9a7f2f4c46 Mon Sep 17 00:00:00 2001 From: mina-gwak Date: Thu, 10 Apr 2025 00:35:34 +0900 Subject: [PATCH 09/10] =?UTF-8?q?Feat:=20PD-246=20=ED=95=99=EC=9B=90=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(business)/business/sign-up/page.ts | 1 + src/pages/sign-up/index.ts | 3 +- .../ui/business-sign-up-page/index.tsx | 97 +++++++++++++++++++ 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 app/(business)/business/sign-up/page.ts create mode 100644 src/pages/sign-up/ui/business-sign-up-page/index.tsx diff --git a/app/(business)/business/sign-up/page.ts b/app/(business)/business/sign-up/page.ts new file mode 100644 index 00000000..71fa232f --- /dev/null +++ b/app/(business)/business/sign-up/page.ts @@ -0,0 +1 @@ +export { BusinessSignUpPage as default } from 'pages/sign-up' diff --git a/src/pages/sign-up/index.ts b/src/pages/sign-up/index.ts index d2d4e14a..b090d547 100644 --- a/src/pages/sign-up/index.ts +++ b/src/pages/sign-up/index.ts @@ -1 +1,2 @@ -export { SignUpPage } from './ui/sign-up-page/index' +export { SignUpPage } from './ui/sign-up-page' +export { BusinessSignUpPage } from './ui/business-sign-up-page' diff --git a/src/pages/sign-up/ui/business-sign-up-page/index.tsx b/src/pages/sign-up/ui/business-sign-up-page/index.tsx new file mode 100644 index 00000000..30fc0f91 --- /dev/null +++ b/src/pages/sign-up/ui/business-sign-up-page/index.tsx @@ -0,0 +1,97 @@ +'use client' + +import { useForm } from 'react-hook-form' + +import { + AcademyName, + AcademyNameEn, + BirthDate, + FullName, + Gender, + RepresentativeName, + BusinessRegistrationNumber, + Address, +} from 'features/sign-up' +import { Email, Password, ConfirmPassword } from 'features/sign-up' +import { Form } from 'shared/form' +import { useCheckbox } from 'shared/lib' +import { + Button, + Checkbox, + Heading, + Layout, + Link, + linkVariants, +} from 'shared/ui' + +import { FormValues, defaultValues } from '../../model/form-values' + +export const BusinessSignUpPage = () => { + const form = useForm({ + defaultValues, + reValidateMode: 'onSubmit', + }) + + const { isChecked, getCheckboxProps } = useCheckbox({ options: ['checked'] }) + + return ( + +
+

Sign Up

+
+

Have an account?

+ Sign In +
+
+
+
+ + Account + +
+ + + + + + +
+
+
+ + 학원 정보 + +
+ + + +
+ +
+
+ ( +

+ { + event.stopPropagation() + }} + className={linkVariants({ variant: 'secondary' })} + > + 개인정보 수집 및 이용 정책과 이용 약관 + + 에 동의합니다. +

+ )} + /> + + +
+ ) +} From 0d0a07655aa3e146e4a853e4a47f8c957b00f985 Mon Sep 17 00:00:00 2001 From: mina-gwak Date: Sat, 12 Apr 2025 16:48:02 +0900 Subject: [PATCH 10/10] =?UTF-8?q?Feat:=20PD-246=20=ED=95=99=EC=9B=90=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EB=8B=A4=EA=B5=AD=EC=96=B4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/sign-up/index.ts | 1 + src/features/sign-up/model/rules.ts | 24 ++++----- .../sign-up/ui/field/academy-name-en.tsx | 8 ++- .../sign-up/ui/field/academy-name.tsx | 8 ++- src/features/sign-up/ui/field/address.tsx | 8 ++- src/features/sign-up/ui/field/birth-date.tsx | 8 ++- .../ui/field/business-registration-number.tsx | 10 +++- .../sign-up/ui/field/confirm-password.tsx | 8 ++- src/features/sign-up/ui/field/email.tsx | 37 +++++++------ src/features/sign-up/ui/field/full-name.tsx | 8 ++- src/features/sign-up/ui/field/gender.tsx | 10 ++-- src/features/sign-up/ui/field/password.tsx | 15 +++--- .../sign-up/ui/field/representative-name.tsx | 10 +++- .../ui/field/terms-and-conditions-of-use.tsx | 48 +++++++++++++++++ .../ui/business-sign-up-page/index.tsx | 46 ++++++---------- src/pages/sign-up/ui/sign-up-page/index.tsx | 28 +++------- .../internationalization/locales/en/auth.json | 25 +++++++++ .../locales/en/exception.json | 10 +++- .../locales/en/field.json | 54 +++++++++++++++++++ .../locales/en/validation.json | 17 ++++++ .../internationalization/locales/ko/auth.json | 25 +++++++++ .../locales/ko/exception.json | 10 +++- .../locales/ko/field.json | 54 +++++++++++++++++++ .../locales/ko/validation.json | 23 ++++++-- .../config/internationalization/request.ts | 5 +- src/shared/form/ui/radio/radio.tsx | 3 +- src/shared/ui/checkbox/index.tsx | 6 ++- src/shared/ui/index.ts | 2 +- src/shared/ui/radio/variants.ts | 2 +- 29 files changed, 392 insertions(+), 121 deletions(-) create mode 100644 src/features/sign-up/ui/field/terms-and-conditions-of-use.tsx create mode 100644 src/shared/config/internationalization/locales/en/field.json create mode 100644 src/shared/config/internationalization/locales/ko/field.json diff --git a/src/features/sign-up/index.ts b/src/features/sign-up/index.ts index 4df4f2ea..f1d2e545 100644 --- a/src/features/sign-up/index.ts +++ b/src/features/sign-up/index.ts @@ -12,4 +12,5 @@ export { LastName } from './ui/field/last-name' export { Nationality } from './ui/field/nationality' export { Password } from './ui/field/password' export { RepresentativeName } from './ui/field/representative-name' +export { TermsAndConditionsOfUse } from './ui/field/terms-and-conditions-of-use' export { useEmailValidationState } from './lib/use-email-validation-state' diff --git a/src/features/sign-up/model/rules.ts b/src/features/sign-up/model/rules.ts index 84e6a3f8..302852ab 100644 --- a/src/features/sign-up/model/rules.ts +++ b/src/features/sign-up/model/rules.ts @@ -4,19 +4,19 @@ const REG_EMAIL = /^([A-Za-z0-9]+([-_.]?[A-Za-z0-9])*)@([A-Za-z0-9]+([-]?[A-Za-z0-9])*)(\.([A-Za-z0-9]+([-]?[A-Za-z0-9])*))?(\.([A-Za-z0-9]([-]?[A-Za-z0-9])*))?((\.[A-Za-z]{2,63})$)/ export const email = { - required: 'Please enter your email', + required: 'validation.email.required', maxLength: { value: 254, - message: 'Input exceeds maximum allowed length of 254 characters', + message: 'validation.email.maxLength', }, pattern: { value: REG_EMAIL, - message: "Invalid email format. Please use the format 'example@domain.com'", + message: 'validation.email.pattern', }, } export const code = { - required: 'Please enter your email verification code', + required: 'validation.code.required', } const REG_UPPER_LOWER_CASE_LETTERS = /(?=.*[a-z])(?=.*[A-Z])/ @@ -43,34 +43,34 @@ export const password = { } export const confirmPassword = { - required: 'Please enter your password', + required: 'validation.confirm-password.required', validate: (value: string | number | null, formValues: FormValues) => { if (value === formValues.password) return true - return 'The password you entered do not match' + return 'validation.confirm-password.match' }, } export const firstName = { - required: 'Please enter your first name', + required: 'validation.firstName.required', maxLength: { value: 254, - message: 'First name exceeds the maximum allowed length of 254 characters', + message: 'validation.firstName.maxLength', }, } export const lastName = { - required: 'Please enter your last name', + required: 'validation.lastName.required', maxLength: { value: 254, - message: 'Last name exceeds the maximum allowed length of 254 characters', + message: 'validation.lastName.maxLength', }, } export const country = { - required: 'Please select your nationality', + required: 'validation.country.required', } export const birthDate = { - required: 'Please enter your date of birth', + required: 'validation.birthDate.required', } diff --git a/src/features/sign-up/ui/field/academy-name-en.tsx b/src/features/sign-up/ui/field/academy-name-en.tsx index 188a9270..88ae3d14 100644 --- a/src/features/sign-up/ui/field/academy-name-en.tsx +++ b/src/features/sign-up/ui/field/academy-name-en.tsx @@ -1,12 +1,16 @@ +import { useTranslations } from 'next-intl' + import { fieldCss, Form } from 'shared/form' import { Label } from 'shared/ui' export const AcademyNameEn = () => { + const t = useTranslations() + return (
- + - +
diff --git a/src/features/sign-up/ui/field/academy-name.tsx b/src/features/sign-up/ui/field/academy-name.tsx index 11f09d82..a7741164 100644 --- a/src/features/sign-up/ui/field/academy-name.tsx +++ b/src/features/sign-up/ui/field/academy-name.tsx @@ -1,12 +1,16 @@ +import { useTranslations } from 'next-intl' + import { fieldCss, Form } from 'shared/form' import { Label } from 'shared/ui' export const AcademyName = () => { + const t = useTranslations() + return (
- + - +
diff --git a/src/features/sign-up/ui/field/address.tsx b/src/features/sign-up/ui/field/address.tsx index b226aa64..dbe0a304 100644 --- a/src/features/sign-up/ui/field/address.tsx +++ b/src/features/sign-up/ui/field/address.tsx @@ -1,10 +1,14 @@ +import { useTranslations } from 'next-intl' + import { fieldCss } from 'shared/form' import { Button, Label, TextField } from 'shared/ui' export const Address = () => { + const t = useTranslations() + return (
- +
- +
) } diff --git a/src/features/sign-up/ui/field/birth-date.tsx b/src/features/sign-up/ui/field/birth-date.tsx index dc15f13a..5dc1d650 100644 --- a/src/features/sign-up/ui/field/birth-date.tsx +++ b/src/features/sign-up/ui/field/birth-date.tsx @@ -1,14 +1,18 @@ +import { useTranslations } from 'next-intl' + import { fieldCss, Form } from 'shared/form' import { Label } from 'shared/ui' import { birthDate as birthDateRule } from '../../model/rules' export const BirthDate = () => { + const t = useTranslations() + return (
- + - +
diff --git a/src/features/sign-up/ui/field/business-registration-number.tsx b/src/features/sign-up/ui/field/business-registration-number.tsx index 805194f0..943dc840 100644 --- a/src/features/sign-up/ui/field/business-registration-number.tsx +++ b/src/features/sign-up/ui/field/business-registration-number.tsx @@ -1,12 +1,18 @@ +import { useTranslations } from 'next-intl' + import { fieldCss, Form } from 'shared/form' import { Label } from 'shared/ui' export const BusinessRegistrationNumber = () => { + const t = useTranslations() + return (
- + - +
diff --git a/src/features/sign-up/ui/field/confirm-password.tsx b/src/features/sign-up/ui/field/confirm-password.tsx index bb17c133..c074dc02 100644 --- a/src/features/sign-up/ui/field/confirm-password.tsx +++ b/src/features/sign-up/ui/field/confirm-password.tsx @@ -1,15 +1,19 @@ +import { useTranslations } from 'next-intl' + import { fieldCss, Form } from 'shared/form' import { Label } from 'shared/ui' import { confirmPassword as confirmPasswordRule } from '../../model/rules' export const ConfirmPassword = () => { + const t = useTranslations() + return (
- + diff --git a/src/features/sign-up/ui/field/email.tsx b/src/features/sign-up/ui/field/email.tsx index 91eff876..e3257db1 100644 --- a/src/features/sign-up/ui/field/email.tsx +++ b/src/features/sign-up/ui/field/email.tsx @@ -1,3 +1,4 @@ +import { useTranslations } from 'next-intl' import { useState } from 'react' import { useFormContext, useWatch } from 'react-hook-form' import { toast } from 'react-toastify' @@ -17,6 +18,8 @@ import { FormValues } from '../../model/form-values' import { email as emailRule, code as codeRule } from '../../model/rules' export const Email = () => { + const t = useTranslations() + const [showVerificationField, setShowVerificationField] = useState(false) const { @@ -48,22 +51,20 @@ export const Email = () => { } const handleRequestVerificationSuccess = () => { - toast.success('A verification email has been sent') + toast.success(t('sign-up.success.request-verification')) if (!showVerificationField) setShowVerificationField(true) } const handleRequestVerificationError = (error: HttpError) => { if (error.code === EmailVerificationCodeExceptionCode.TOO_MANY_REQUEST) { - toast.error( - 'You have requested the verification code too many times. Please try again in 10 minutes.', - ) + toast.error(t('exception.email.too-many-request')) } else if (error.code === UserExceptionCode.ALREADY_USED_EMAIL) { setError('email', { - message: 'An account with that email already exists', + message: t('exception.user.already-used-email'), }) } else { - toast.error('An error occurred while requesting verification') + toast.error(t('sign-up.error.request-verification')) } } @@ -85,18 +86,18 @@ export const Email = () => { error.code === EmailVerificationCodeExceptionCode.ALREADY_VERIFIED_CODE ) { setError('code', { - message: 'The verification code you entered has already been used', + message: t('exception.email.already-verified-code'), }) } else if (error.code === EmailVerificationCodeExceptionCode.EXPIRED_CODE) { setError('code', { - message: 'The verification code has expired. Please request a new one.', + message: t('exception.email.expired-code'), }) } else if ( error.code === ResourceNotFoundExceptionCode.EMAIL_VERIFICATION_CODE_NOT_FOUND ) { setError('code', { - message: 'The verification code you entered is incorrect', + message: t('exception.resource-not-found.email-verification-code'), }) } } @@ -108,7 +109,7 @@ export const Email = () => { if (requestVerification.isPending || requestVerification.isIdle) { setError('email', { - message: 'Please verify the email first', + message: t('sign-up.error.not-verified-email'), }) return @@ -123,13 +124,13 @@ export const Email = () => { return (
- +
@@ -143,7 +144,7 @@ export const Email = () => { disabled={requestVerification.isSuccess} className="w-[95px]" > - Send + {t('sign-up.button.send-verification-code')}
@@ -152,7 +153,7 @@ export const Email = () => {
@@ -160,11 +161,13 @@ export const Email = () => { {!hasError(errors?.code) && verifyCode.isSuccess && ( - Authentication completed + {t('sign-up.success.verify-code')} )} {!hasError(errors?.code) && !verifyCode.isSuccess && ( - Please enter the code sent to the email + + {t('sign-up.error.not-enter-verification-code')} + )}
)} diff --git a/src/features/sign-up/ui/field/full-name.tsx b/src/features/sign-up/ui/field/full-name.tsx index 66c8a11a..ba2adeaa 100644 --- a/src/features/sign-up/ui/field/full-name.tsx +++ b/src/features/sign-up/ui/field/full-name.tsx @@ -1,12 +1,16 @@ +import { useTranslations } from 'next-intl' + import { fieldCss, Form } from 'shared/form' import { Label } from 'shared/ui' export const FullName = () => { + const t = useTranslations() + return (
- + - +
diff --git a/src/features/sign-up/ui/field/gender.tsx b/src/features/sign-up/ui/field/gender.tsx index 61d73afa..6c660092 100644 --- a/src/features/sign-up/ui/field/gender.tsx +++ b/src/features/sign-up/ui/field/gender.tsx @@ -1,14 +1,18 @@ +import { useTranslations } from 'next-intl' + import { fieldCss, Form } from 'shared/form' import { Label } from 'shared/ui' export const Gender = () => { + const t = useTranslations() + return (
- + - - + +
diff --git a/src/features/sign-up/ui/field/password.tsx b/src/features/sign-up/ui/field/password.tsx index ebae17e5..41644db4 100644 --- a/src/features/sign-up/ui/field/password.tsx +++ b/src/features/sign-up/ui/field/password.tsx @@ -1,3 +1,4 @@ +import { useTranslations } from 'next-intl' import { useFormContext, useWatch } from 'react-hook-form' import { fieldCss, Form } from 'shared/form' @@ -14,6 +15,8 @@ import { } from '../../model/rules' export const Password = () => { + const t = useTranslations() + const { control } = useFormContext() const password = useWatch({ @@ -30,29 +33,29 @@ export const Password = () => { return (
- +
- 9 ~ 28 characters long + {t('validation.password.length')} - Upper & lower case letters + {t('validation.password.has-lowercase-and-uppercase-letter')} - At least one number + {t('validation.password.has-number')} - At least special character + {t('validation.password.has-special-char')}
diff --git a/src/features/sign-up/ui/field/representative-name.tsx b/src/features/sign-up/ui/field/representative-name.tsx index 195dd204..9d58e986 100644 --- a/src/features/sign-up/ui/field/representative-name.tsx +++ b/src/features/sign-up/ui/field/representative-name.tsx @@ -1,12 +1,18 @@ +import { useTranslations } from 'next-intl' + import { fieldCss, Form } from 'shared/form' import { Label } from 'shared/ui' export const RepresentativeName = () => { + const t = useTranslations() + return (
- + - +
diff --git a/src/features/sign-up/ui/field/terms-and-conditions-of-use.tsx b/src/features/sign-up/ui/field/terms-and-conditions-of-use.tsx new file mode 100644 index 00000000..a8141fd1 --- /dev/null +++ b/src/features/sign-up/ui/field/terms-and-conditions-of-use.tsx @@ -0,0 +1,48 @@ +import { Checkbox, CheckboxProps, linkVariants } from 'shared/ui' + +const KoreanLabel = (className: string) => ( +

+ { + event.stopPropagation() + }} + className={linkVariants({ variant: 'secondary' })} + > + 개인정보 수집 및 이용 정책과 이용 약관 + + 에 동의합니다. +

+) + +const EnglishLabel = (className: string) => ( +

+ I have read and agree to the Plus 82's +
+ { + event.stopPropagation() + }} + className={linkVariants({ variant: 'secondary' })} + > + Terms and Conditions of Use. (Essential) + +

+) + +type Props = CheckboxProps & { + locale: 'ko' | 'en' +} + +export const TermsAndConditionsOfUse = ({ locale, ...props }: Props) => { + return ( + + ) +} diff --git a/src/pages/sign-up/ui/business-sign-up-page/index.tsx b/src/pages/sign-up/ui/business-sign-up-page/index.tsx index 30fc0f91..dc7ab1a9 100644 --- a/src/pages/sign-up/ui/business-sign-up-page/index.tsx +++ b/src/pages/sign-up/ui/business-sign-up-page/index.tsx @@ -1,5 +1,6 @@ 'use client' +import { useLocale, useTranslations } from 'next-intl' import { useForm } from 'react-hook-form' import { @@ -11,22 +12,20 @@ import { RepresentativeName, BusinessRegistrationNumber, Address, + TermsAndConditionsOfUse, } from 'features/sign-up' import { Email, Password, ConfirmPassword } from 'features/sign-up' +import { Locale } from 'shared/config' import { Form } from 'shared/form' import { useCheckbox } from 'shared/lib' -import { - Button, - Checkbox, - Heading, - Layout, - Link, - linkVariants, -} from 'shared/ui' +import { Button, Heading, Layout, Link } from 'shared/ui' import { FormValues, defaultValues } from '../../model/form-values' export const BusinessSignUpPage = () => { + const t = useTranslations('sign-up') + const locale = useLocale() as Locale + const form = useForm({ defaultValues, reValidateMode: 'onSubmit', @@ -37,16 +36,16 @@ export const BusinessSignUpPage = () => { return (
-

Sign Up

+

{t('title')}

-

Have an account?

- Sign In +

{t('description')}

+ {t('link.sign-in')}
- Account + {t('heading.personal-information')}
@@ -59,7 +58,7 @@ export const BusinessSignUpPage = () => {
- 학원 정보 + {t('heading.academy-information')}
@@ -69,27 +68,12 @@ export const BusinessSignUpPage = () => {
- ( -

- { - event.stopPropagation() - }} - className={linkVariants({ variant: 'secondary' })} - > - 개인정보 수집 및 이용 정책과 이용 약관 - - 에 동의합니다. -

- )} /> diff --git a/src/pages/sign-up/ui/sign-up-page/index.tsx b/src/pages/sign-up/ui/sign-up-page/index.tsx index 0a34c70f..6671b479 100644 --- a/src/pages/sign-up/ui/sign-up-page/index.tsx +++ b/src/pages/sign-up/ui/sign-up-page/index.tsx @@ -5,11 +5,14 @@ import { useForm } from 'react-hook-form' import { toast } from 'react-toastify' import { signUp } from 'entities/auth' -import { useEmailValidationState } from 'features/sign-up' +import { + TermsAndConditionsOfUse, + useEmailValidationState, +} from 'features/sign-up' import { isServerError, useServerErrorHandler } from 'shared/api' import { Form } from 'shared/form' import { useCheckbox } from 'shared/lib' -import { Button, Checkbox, Layout, Link, linkVariants } from 'shared/ui' +import { Button, Layout, Link } from 'shared/ui' import { FormValues, @@ -82,26 +85,7 @@ export const SignUpPage = () => {
- ( -

- I have read and agree to the Plus 82's -
- { - event.stopPropagation() - }} - className={linkVariants({ variant: 'secondary' })} - > - Terms and Conditions of Use. (Essential) - -

- )} - /> +