diff --git a/src/pages/sign-up/components/agreement-step.tsx b/src/pages/sign-up/components/agreement-step.tsx new file mode 100644 index 00000000..5212ce3b --- /dev/null +++ b/src/pages/sign-up/components/agreement-step.tsx @@ -0,0 +1,71 @@ +import Button from '@components/button/button/button'; +import Icon from '@components/icon/icon'; +import CheckboxRow from '@pages/sign-up/components/checkbox-row'; +import { useState } from 'react'; + +interface AgreementStepProps { + next: () => void; +} + +const AgreementStep = ({ next }: AgreementStepProps) => { + const [terms, setTerms] = useState(false); + const [privacy, setPrivacy] = useState(false); + + const isAllChecked = terms && privacy; + + const handleCheckAll = () => { + const next = !isAllChecked; + setTerms(next); + setPrivacy(next); + }; + + const handleCheckTerms = () => { + const next = !terms; + setTerms(next); + }; + + const handleCheckPrivacy = () => { + const next = !privacy; + setPrivacy(next); + }; + + return ( +
+
+

서비스 이용약관

+

+ 서비스 가입을 위해
아래 항목에 동의해주세요. +

+
+ +
+
+ + } + /> + + } + /> +
+
+
+
+
+ ); +}; + +export default AgreementStep; diff --git a/src/pages/sign-up/components/checkbox-row.tsx b/src/pages/sign-up/components/checkbox-row.tsx new file mode 100644 index 00000000..3de727d2 --- /dev/null +++ b/src/pages/sign-up/components/checkbox-row.tsx @@ -0,0 +1,35 @@ +import Icon from '@components/icon/icon'; +import { cn } from '@libs/cn'; +import type { ReactNode } from 'react'; + +interface CheckboxProps { + label: ReactNode; + checked: boolean; + onClick: () => void; + svg?: ReactNode; + divider?: boolean; + className?: string; +} + +const CheckboxRow = ({ label, checked, onClick, svg, divider, className }: CheckboxProps) => { + return ( + + {label} + + {svg} + + ); +}; + +export default CheckboxRow; diff --git a/src/pages/sign-up/components/signup-step.tsx b/src/pages/sign-up/components/signup-step.tsx index 483f5d87..3f9fb96d 100644 --- a/src/pages/sign-up/components/signup-step.tsx +++ b/src/pages/sign-up/components/signup-step.tsx @@ -7,11 +7,17 @@ import queryClient from '@libs/query-client'; import { BIRTHYEAR_RULE_MESSAGE, BIRTHYEAR_SUCCESS_MESSAGE, + INFORMATION_RULE_MESSAGE, NICKNAME_RULE_MESSAGE, NICKNAME_SUCCESS_MESSAGE, NICKNAME_TITLE, } from '@pages/sign-up/constants/NOTICE'; -import { BIRTH_PLACEHOLDER, NICKNAME_PLACEHOLDER } from '@pages/sign-up/constants/validation'; +import { + BIRTH_PLACEHOLDER, + INFORMATION_MAX_LENGTH, + INFORMATION_PLACEHOLDER, + NICKNAME_PLACEHOLDER, +} from '@pages/sign-up/constants/validation'; import { type NicknameFormValues, NicknameSchema } from '@pages/sign-up/schema/validation-schema'; import { ROUTES } from '@routes/routes-config'; import { useMutation } from '@tanstack/react-query'; @@ -28,7 +34,7 @@ const SignupStep = () => { } = useForm({ mode: 'onChange', resolver: zodResolver(NicknameSchema), - defaultValues: { nickname: '', gender: undefined, birthYear: '' }, + defaultValues: { nickname: '', gender: undefined, birthYear: '', information: '' }, }); const navigate = useNavigate(); @@ -36,13 +42,17 @@ const SignupStep = () => { const nicknameValue = watch('nickname'); const birthYearValue = watch('birthYear'); const genderValue = watch('gender'); + const informationValue = watch('information'); const isNicknameValid = !errors.nickname && nicknameValue.length > 0; const isBirthYearValid = !errors.birthYear && birthYearValue.length > 0; + const isInformationValid = !errors.information && informationValue.length > 0; const nicknameMutation = useMutation(userMutations.NICKNAME()); const userInfoMutation = useMutation(userMutations.USER_INFO()); + const informationLength = informationValue.length ?? 0; + const onSubmit = (data: NicknameFormValues) => { nicknameMutation.mutate( { nickname: data.nickname }, @@ -79,6 +89,12 @@ const SignupStep = () => { ...birthYearInputProps } = register('birthYear'); + const { + onBlur: onInformationBlur, + ref: informationRef, + ...informationInputProps + } = register('information'); + const handleGenderClick = (gender: '여성' | '남성') => { setValue('gender', gender, { shouldValidate: true, shouldDirty: true }); }; @@ -86,7 +102,7 @@ const SignupStep = () => { return (

{NICKNAME_TITLE}

@@ -102,9 +118,24 @@ const SignupStep = () => { ref={nicknameRef} {...nicknameInputProps} /> + ; diff --git a/src/pages/sign-up/sign-up.tsx b/src/pages/sign-up/sign-up.tsx index 0482d870..e9db80ed 100644 --- a/src/pages/sign-up/sign-up.tsx +++ b/src/pages/sign-up/sign-up.tsx @@ -1,9 +1,24 @@ +import { useFunnel } from '@hooks/use-funnel'; +import AgreementStep from '@pages/sign-up/components/agreement-step'; import SignupStep from '@pages/sign-up/components/signup-step'; +import { SIGNUP_STEPS } from '@pages/sign-up/constants/validation'; +import { ROUTES } from '@routes/routes-config'; const SignUp = () => { + const { Funnel, Step, goNext } = useFunnel(SIGNUP_STEPS, ROUTES.HOME); + return ( -
- +
+
+ + + + + + + + +
); }; diff --git a/src/shared/assets/svgs/arrow-right-18.svg b/src/shared/assets/svgs/arrow-right-18.svg new file mode 100644 index 00000000..b693a897 --- /dev/null +++ b/src/shared/assets/svgs/arrow-right-18.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/shared/assets/svgs/check-filled.svg b/src/shared/assets/svgs/check-filled.svg index 0f508292..a1400074 100644 --- a/src/shared/assets/svgs/check-filled.svg +++ b/src/shared/assets/svgs/check-filled.svg @@ -4,7 +4,7 @@ - + diff --git a/src/shared/components/input/input.tsx b/src/shared/components/input/input.tsx index 0fe866ce..d62c0f05 100644 --- a/src/shared/components/input/input.tsx +++ b/src/shared/components/input/input.tsx @@ -1,17 +1,24 @@ import Icon from '@components/icon/icon'; import { iconColorMap, inputClassMap } from '@components/input/styles/input-variants'; import { cn } from '@libs/cn'; +import type React from 'react'; import type { InputHTMLAttributes } from 'react'; import { useState } from 'react'; import { defineInputState } from '@/shared/utils/define-input-state'; -interface InputProps extends InputHTMLAttributes { +interface InputProps extends Omit, 'onBlur'> { label?: string; isError?: boolean; isValid?: boolean; + hasLength?: boolean; + maxLength?: number; defaultMessage?: string; validationMessage?: string; ref?: React.Ref; + className?: string; + multiline?: boolean; + length?: number; + onBlur?: (e: React.FocusEvent | React.FocusEvent) => void; } const Input = ({ @@ -21,8 +28,13 @@ const Input = ({ label, validationMessage, defaultMessage, + length, + maxLength = 50, onBlur, ref, + className, + hasLength = false, + multiline = false, ...props }: InputProps) => { const [isFocused, setIsFocused] = useState(false); @@ -41,31 +53,59 @@ const Input = ({ )}
- setIsFocused(true)} - onBlur={(e) => { - setIsFocused(false); - onBlur?.(e); - }} - {...props} - /> + {multiline ? ( +