diff --git a/package.json b/package.json index 40289bf..c19769c 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "classnames": "^2.5.1", "date-fns": "^4.1.0", "dayjs": "^1.11.13", + "formik": "^2.4.6", "html5-qrcode": "^2.3.8", "next": "15.4.0-canary.10", "react": "^19.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 966572b..7ab76d0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: dayjs: specifier: ^1.11.13 version: 1.11.13 + formik: + specifier: ^2.4.6 + version: 2.4.6(react@19.1.0) html5-qrcode: specifier: ^2.3.8 version: 2.3.8 @@ -1884,6 +1887,9 @@ packages: '@types/estree@1.0.7': resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + '@types/hoist-non-react-statics@3.3.6': + resolution: {integrity: sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -2571,6 +2577,10 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge@2.2.1: + resolution: {integrity: sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==} + engines: {node: '>=0.10.0'} + deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} @@ -2984,6 +2994,11 @@ packages: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} + formik@2.4.6: + resolution: {integrity: sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g==} + peerDependencies: + react: '>=16.8.0' + fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -3116,6 +3131,9 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -3480,6 +3498,9 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} @@ -3929,6 +3950,9 @@ packages: peerDependencies: react: ^19.1.0 + react-fast-compare@2.0.4: + resolution: {integrity: sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==} + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -4336,6 +4360,9 @@ packages: tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tiny-warning@1.0.3: + resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -6471,6 +6498,11 @@ snapshots: '@types/estree@1.0.7': {} + '@types/hoist-non-react-statics@3.3.6': + dependencies: + '@types/react': 19.1.4 + hoist-non-react-statics: 3.3.2 + '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} @@ -7216,6 +7248,8 @@ snapshots: deep-is@0.1.4: {} + deepmerge@2.2.1: {} + deepmerge@4.3.1: {} defaults@1.0.4: @@ -7793,6 +7827,18 @@ snapshots: dependencies: fetch-blob: 3.2.0 + formik@2.4.6(react@19.1.0): + dependencies: + '@types/hoist-non-react-statics': 3.3.6 + deepmerge: 2.2.1 + hoist-non-react-statics: 3.3.2 + lodash: 4.17.21 + lodash-es: 4.17.21 + react: 19.1.0 + react-fast-compare: 2.0.4 + tiny-warning: 1.0.3 + tslib: 2.8.1 + fsevents@2.3.2: optional: true @@ -7936,6 +7982,10 @@ snapshots: dependencies: function-bind: 1.1.2 + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + html-escaper@2.0.2: {} html5-qrcode@2.3.8: {} @@ -8284,6 +8334,8 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash-es@4.17.21: {} + lodash.debounce@4.0.8: {} lodash.merge@4.6.2: {} @@ -8748,6 +8800,8 @@ snapshots: react: 19.1.0 scheduler: 0.26.0 + react-fast-compare@2.0.4: {} + react-is@16.13.1: {} react-is@17.0.2: {} @@ -9262,6 +9316,8 @@ snapshots: tiny-invariant@1.3.3: {} + tiny-warning@1.0.3: {} + tinybench@2.9.0: {} tinyexec@0.3.2: {} diff --git a/src/app/(auth)/join/[type]/[step]/_components/ParentStep02.tsx b/src/app/(auth)/join/[type]/[step]/_components/ParentStep02.tsx index 05b8657..8c592b6 100644 --- a/src/app/(auth)/join/[type]/[step]/_components/ParentStep02.tsx +++ b/src/app/(auth)/join/[type]/[step]/_components/ParentStep02.tsx @@ -1,171 +1,167 @@ "use client" -import { ChangeEvent, useState } from "react" import Picker from "react-mobile-picker" -import { useRouter } from "next/navigation" + +import { useFormik } from "formik" import BoyIcon from "@/assets/character/comm/img-boy.svg" import GirlIcon from "@/assets/character/comm/img-girl.svg" +import ErrorIconSvg from "@/assets/icons/common/icon-error.svg" import Button from "@/components/common/button/button" import Input from "@/components/common/input/input" import { useParentJoin } from "@/hooks/auth/useParentAuth" import { useUserStore } from "@/stores/userStore" +import { ChildInfoType } from "@/types/auth" + import pageStyles from "../page.module.scss" import styles from "./steps.module.scss" -function generateNumberArray(begin: number, end: number, unit: string) { +function generateNumberArray(begin: number, end: number) { const arr = [] for (let i = begin; i <= end; i++) { - arr.push(`${i}${unit}`) + arr.push(i) } return arr } const ParentStep02 = () => { - const router = useRouter() - - const { updateUser, parent } = useUserStore() + const { updateParent, parent } = useUserStore() const { mutate } = useParentJoin() - - - const [childInfo, setChildInfo] = useState({ - childName: "", - childGender: "", - }) - - const [pickerValues, setPickerValues] = useState<{ - childAge: string - childHeight: string - childWeight: string - }>({ - childAge: "10세", - childHeight: "120cm", - childWeight: "20kg", - }) - + // #region pickerValues const selections = { - childAge: generateNumberArray(8, 19, "세"), - childHeight: generateNumberArray(100, 200, "cm"), - childWeight: generateNumberArray(20, 150, "kg"), + child_age: generateNumberArray(8, 19), + child_height: generateNumberArray(100, 200), + child_weight: generateNumberArray(20, 150), } // #endregion - // #region Event - const handleChange = (key: string, e: ChangeEvent) => { - setChildInfo((prev) => ({ - ...prev, - [key]: e.target.value, - })) - } + // #region Formik + const formik = useFormik({ + initialValues: { + child_name: "", + child_gender: "", + child_age: 10, + child_height: 120, + child_weight: 20, + }, + validate: (values) => { + const errors: Partial = {} - const handlePickerChange = (valueMap: Record) => { - setPickerValues({ - childAge: valueMap.childAge || pickerValues.childAge, - childHeight: valueMap.childHeight || pickerValues.childHeight, - childWeight: valueMap.childWeight || pickerValues.childWeight, - }) - } + if (!values.child_name.trim()) { + errors.child_name = "아이 이름은 필수입니다" + } - - const handleClickJoin = async () => { - const updatedChildInfo = { - ...childInfo, - childAge: parseInt(pickerValues.childAge.replace("세", "")), - childHeight: parseInt(pickerValues.childHeight.replace("cm", "")), - childWeight: parseInt(pickerValues.childWeight.replace("kg", "")), - } + if (!values.child_gender) { + errors.child_gender = "성별을 선택해주세요" + } - updateUser(updatedChildInfo) + return errors + }, + onSubmit: async (values: ChildInfoType) => { + const updatedChildInfo = { + ...values, + child_age: values.child_age, + child_height: values.child_height, + childWeight: values.child_weight, + } - const requestData = { - nickname: parent.nickname ?? "", - child_name: updatedChildInfo.childName, - child_gender: updatedChildInfo.childGender, - child_age: updatedChildInfo.childAge, - child_height: updatedChildInfo.childHeight, - child_weight: updatedChildInfo.childWeight, - } + updateParent(updatedChildInfo) - // // 요청 데이터 확인 - // console.log("요청 데이터:", requestData) + const requestData = { + nickname: parent.nickname ?? "", + child_name: values.child_name, + child_gender: values.child_gender, + child_age: values.child_age, + child_height: values.child_height, + child_weight: values.child_weight, + } - // try { - // await mutate(requestData) - // } catch (error) { - // console.error("회원가입 실패:", error) - // } - // 쿠키 확인 - console.log("현재 쿠키:", document.cookie) + try { + const response = await mutate(requestData) + console.log("성공 응답:", response) + } catch (error) { + // 더 자세한 에러 정보 + console.error(error) + } + }, + }) + // #endregion - // 요청 데이터 타입 확인 - console.log("요청 데이터 타입:", { - nickname: typeof requestData.nickname, - child_name: typeof requestData.child_name, - child_gender: typeof requestData.child_gender, - child_age: typeof requestData.child_age, - child_height: typeof requestData.child_height, - child_weight: typeof requestData.child_weight, + // #region Event + const handlePickerChange = (valueMap: Record) => { + Object.entries(valueMap).forEach(([key, value]) => { + if (formik.values.hasOwnProperty(key) && value !== undefined) { + formik.setFieldValue(key, value) + } }) - - console.log("요청 데이터 값:", requestData) - - try { - const response = await mutate(requestData) - console.log("성공 응답:", response) - } catch (error) { - // 더 자세한 에러 정보 - console.error("에러 응답 데이터:", error.response?.data) - console.error("에러 상태 코드:", error.response?.status) - console.error("에러 헤더:", error.response?.headers) - console.error("요청 설정:", error.config) - } } // #endregion return ( <> -
+
{/* 아이 이름 */} handleChange("childName", e)} - value={childInfo.childName} + onChange={formik.handleChange} + onBlur={formik.handleBlur} + value={formik.values.child_name} + state={ + formik.errors.child_name + ? { + type: "error", + message: formik.errors.child_name, + } + : null + } /> + {/* 성별 */} -
+
성별 + {!!formik.errors.child_gender && ( + <> + +

{formik.errors.child_gender}

+ + )}