From 84fa0f80231d305886765cbb17e20f707a980cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EC=84=B8=EC=9C=A4?= <2ne1jenna@naver.com> Date: Wed, 28 Jan 2026 17:34:54 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat(matching-suggest):=20=EC=BA=A0?= =?UTF-8?q?=ED=8E=98=EC=9D=B8=20=EC=A0=9C=EC=95=88=20=ED=8F=BC=20UI=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 폼 필드 컴포넌트 스타일 통일 - TextArea auto-resize 기능 추가 - 폼 컨테이너 전체 너비 레이아웃 적용 --- package.json | 2 + pnpm-lock.yaml | 40 +- src/assets/icon/exist-suggest.svg | 4 +- src/components/form/DateField.tsx | 24 + src/components/form/FeeInput.tsx | 26 ++ src/components/form/SelectField.tsx | 27 ++ src/components/form/TextArea.tsx | 41 ++ src/components/form/TextInput.tsx | 28 ++ src/components/form/index.ts | 5 + src/data/existing-campaigns.ts | 6 + src/hooks/useHideBottomTab.ts | 22 + src/routeTree.gen.ts | 429 ++++-------------- src/routes/_main.tsx | 19 +- src/routes/_main/chat/chat-content.tsx | 17 +- src/routes/_main/layout-context.tsx | 2 + .../components/ProfileSelector.tsx | 49 ++ .../components/SuggestHeader.tsx | 46 ++ .../create/create-campaign-content.tsx | 293 ++++++++++++ .../_main/matching-suggest/create/route.tsx | 11 + .../_main/matching-suggest/create/schema.ts | 37 ++ src/routes/_main/matching-suggest/index.tsx | 6 + .../matching-suggest-content.tsx | 42 ++ src/routes/_main/matching-suggest/route.tsx | 28 ++ .../_main/matching/brand/brand-content.tsx | 4 + .../matching/campaign/campaign-content.tsx | 4 + 25 files changed, 852 insertions(+), 360 deletions(-) create mode 100644 src/components/form/DateField.tsx create mode 100644 src/components/form/FeeInput.tsx create mode 100644 src/components/form/SelectField.tsx create mode 100644 src/components/form/TextArea.tsx create mode 100644 src/components/form/TextInput.tsx create mode 100644 src/components/form/index.ts create mode 100644 src/data/existing-campaigns.ts create mode 100644 src/hooks/useHideBottomTab.ts create mode 100644 src/routes/_main/matching-suggest/components/ProfileSelector.tsx create mode 100644 src/routes/_main/matching-suggest/components/SuggestHeader.tsx create mode 100644 src/routes/_main/matching-suggest/create/create-campaign-content.tsx create mode 100644 src/routes/_main/matching-suggest/create/route.tsx create mode 100644 src/routes/_main/matching-suggest/create/schema.ts create mode 100644 src/routes/_main/matching-suggest/index.tsx create mode 100644 src/routes/_main/matching-suggest/matching-suggest-content.tsx create mode 100644 src/routes/_main/matching-suggest/route.tsx diff --git a/package.json b/package.json index f55dae1..ef6940b 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "@hookform/resolvers": "^5.2.2", "@iconify/react": "^6.0.2", "@tanstack/react-query": "^5.90.16", "@tanstack/react-router": "^1.145.7", @@ -20,6 +21,7 @@ "react-hook-form": "^7.71.0", "react-mobile-picker": "^1.2.0", "tailwind-merge": "^3.4.0", + "zod": "3.23.8", "zustand": "^5.0.9" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fc67060..4cff640 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@hookform/resolvers': + specifier: ^5.2.2 + version: 5.2.2(react-hook-form@7.71.1(react@19.2.3)) '@iconify/react': specifier: ^6.0.2 version: 6.0.2(react@19.2.3) @@ -38,6 +41,9 @@ importers: tailwind-merge: specifier: ^3.4.0 version: 3.4.0 + zod: + specifier: 3.23.8 + version: 3.23.8 zustand: specifier: ^5.0.9 version: 5.0.10(@types/react@19.2.8)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) @@ -828,6 +834,11 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@hookform/resolvers@5.2.2': + resolution: {integrity: sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==} + peerDependencies: + react-hook-form: ^7.55.0 + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -1060,6 +1071,9 @@ packages: cpu: [x64] os: [win32] + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + '@surma/rollup-plugin-off-main-thread@2.2.3': resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} @@ -3101,11 +3115,14 @@ packages: peerDependencies: zod: ^3.25.0 || ^4.0.0 + zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - zod@4.3.5: - resolution: {integrity: sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==} + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} zustand@5.0.10: resolution: {integrity: sha512-U1AiltS1O9hSy3rul+Ub82ut2fqIAefiSuwECWt6jlMVUGejvf+5omLcRBSzqbRagSM3hQZbtzdeRc6QVScXTg==} @@ -3934,6 +3951,11 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 + '@hookform/resolvers@5.2.2(react-hook-form@7.71.1(react@19.2.3))': + dependencies: + '@standard-schema/utils': 0.3.0 + react-hook-form: 7.71.1(react@19.2.3) + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -4118,6 +4140,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.55.1': optional: true + '@standard-schema/utils@0.3.0': {} + '@surma/rollup-plugin-off-main-thread@2.2.3': dependencies: ejs: 3.1.10 @@ -4911,8 +4935,8 @@ snapshots: '@babel/parser': 7.28.6 eslint: 9.39.2(jiti@2.6.1) hermes-parser: 0.25.1 - zod: 4.3.5 - zod-validation-error: 4.0.2(zod@4.3.5) + zod: 4.3.6 + zod-validation-error: 4.0.2(zod@4.3.6) transitivePeerDependencies: - supports-color @@ -6388,13 +6412,15 @@ snapshots: yocto-queue@0.1.0: {} - zod-validation-error@4.0.2(zod@4.3.5): + zod-validation-error@4.0.2(zod@4.3.6): dependencies: - zod: 4.3.5 + zod: 4.3.6 + + zod@3.23.8: {} zod@3.25.76: {} - zod@4.3.5: {} + zod@4.3.6: {} zustand@5.0.10(@types/react@19.2.8)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)): optionalDependencies: diff --git a/src/assets/icon/exist-suggest.svg b/src/assets/icon/exist-suggest.svg index 2feaecb..3fcf6c2 100644 --- a/src/assets/icon/exist-suggest.svg +++ b/src/assets/icon/exist-suggest.svg @@ -1,4 +1,4 @@ - - + + diff --git a/src/components/form/DateField.tsx b/src/components/form/DateField.tsx new file mode 100644 index 0000000..df996f9 --- /dev/null +++ b/src/components/form/DateField.tsx @@ -0,0 +1,24 @@ +interface DateFieldProps { + placeholder: string; + value?: string; + onClick: () => void; +} + +export default function DateField({ + placeholder, + value, + onClick, +}: DateFieldProps) { + return ( + + ); +} diff --git a/src/components/form/FeeInput.tsx b/src/components/form/FeeInput.tsx new file mode 100644 index 0000000..0ef3fec --- /dev/null +++ b/src/components/form/FeeInput.tsx @@ -0,0 +1,26 @@ +interface FeeInputProps { + value: string; + onChange: (value: string) => void; + placeholder?: string; + unit?: string; +} + +export default function FeeInput({ + value, + onChange, + placeholder = "", + unit = "원", +}: FeeInputProps) { + return ( +
+ onChange(e.target.value.replace(/[^0-9]/g, ""))} + className="flex-1 text-title3 text-text-black placeholder:text-text-gray3 focus:outline-none bg-transparent" + placeholder={placeholder} + /> + {unit} +
+ ); +} diff --git a/src/components/form/SelectField.tsx b/src/components/form/SelectField.tsx new file mode 100644 index 0000000..88901e3 --- /dev/null +++ b/src/components/form/SelectField.tsx @@ -0,0 +1,27 @@ +interface SelectFieldProps { + placeholder: string; + value?: string; + onClick: () => void; +} + +export default function SelectField({ + placeholder, + value, + onClick, +}: SelectFieldProps) { + return ( + + ); +} diff --git a/src/components/form/TextArea.tsx b/src/components/form/TextArea.tsx new file mode 100644 index 0000000..8daa369 --- /dev/null +++ b/src/components/form/TextArea.tsx @@ -0,0 +1,41 @@ +import { useRef, useEffect } from "react"; + +interface TextAreaProps { + placeholder: string; + maxLength: number; + value: string; + onChange: (value: string) => void; +} + +export default function TextArea({ + placeholder, + maxLength, + value, + onChange, +}: TextAreaProps) { + const textareaRef = useRef(null); + + useEffect(() => { + const textarea = textareaRef.current; + if (textarea) { + textarea.style.height = "auto"; + textarea.style.height = `${textarea.scrollHeight}px`; + } + }, [value]); + + return ( +
+