From a1fe1d6c5e80296fec4f4f295320d06e5696a9c7 Mon Sep 17 00:00:00 2001 From: PC2502 Date: Thu, 10 Jul 2025 17:15:24 +0900 Subject: [PATCH 01/14] =?UTF-8?q?fix:=20axios=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EB=A6=AC=ED=84=B4=20=ED=83=80=EC=9E=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/api.ts b/src/api.ts index 69d735ef..66a42a23 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,5 +1,8 @@ -import axios, { type AxiosRequestConfig } from "axios"; -import { type IProduct } from "./pages/ItemDetailsPage/ItemDetailsPage"; +import axios from "axios"; +import { + type IComments, + type IProduct, +} from "./pages/ItemDetailsPage/ItemDetailsPage"; interface APIResponse { status: number; @@ -49,7 +52,7 @@ export const getProducts = async ({ export const getProductById = async ( productId: number -): Promise> => { +): Promise => { try { const response = await instance.get(`/products/${productId}`); @@ -64,7 +67,7 @@ export const getComments = async ({ productId, limit, cursor = 0, -}: ICommentsQuery): Promise> => { +}: ICommentsQuery): Promise => { try { const response = await instance.get(`/products/${productId}/comments`, { params: { From efd1550ea697c46037fb3ef98eda6a82b4f10860 Mon Sep 17 00:00:00 2001 From: PC2502 Date: Sun, 13 Jul 2025 15:40:37 +0900 Subject: [PATCH 02/14] =?UTF-8?q?feat:=20=EB=9E=9C=EB=94=A9=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/MainPage/MainPage.tsx | 70 +++++++++++++++++++ src/pages/MainPage/components/Closing.tsx | 28 ++++++++ src/pages/MainPage/components/Footer.tsx | 46 ++++++++++++ src/pages/MainPage/components/Hero.tsx | 45 ++++++++++++ .../MainPage/components/Introduction.tsx | 54 ++++++++++++++ 5 files changed, 243 insertions(+) create mode 100644 src/pages/MainPage/MainPage.tsx create mode 100644 src/pages/MainPage/components/Closing.tsx create mode 100644 src/pages/MainPage/components/Footer.tsx create mode 100644 src/pages/MainPage/components/Hero.tsx create mode 100644 src/pages/MainPage/components/Introduction.tsx diff --git a/src/pages/MainPage/MainPage.tsx b/src/pages/MainPage/MainPage.tsx new file mode 100644 index 00000000..629e0fb9 --- /dev/null +++ b/src/pages/MainPage/MainPage.tsx @@ -0,0 +1,70 @@ +import Hero from "./components/Hero"; +import Introduction from "./components/Introduction"; +import introImg1 from "@/assets/img/img_home_01.png"; +import introImg2 from "@/assets/img/img_home_02.png"; +import introImg3 from "@/assets/img/img_home_03.png"; +import Closing from "./components/Closing"; +import Footer from "./components/Footer"; + +const INTRODUCTION_MAP = { + section1: { + className: "items-start text-left", + imgSrc: introImg1, + keyword: "Hot item", + title: "인기 상품을\n확인해보세요", + description: "가장 HOT한 중고거래 물품을\n판다 마켓에서 확인해보세요", + }, + section2: { + className: "items-end text-right xl:flex-row-reverse", + imgSrc: introImg2, + keyword: "Search", + title: "구매를 원하는\n상품을 검색하세요", + description: "구매하고 싶은 물품은 검색해서\n쉽게 찾아보세요", + }, + section3: { + className: "items-start text-left", + imgSrc: introImg3, + keyword: "Register", + title: "판매를 원하는\n상품을 등록하세요", + description: "어떤 물건이든 판매하고 싶은 상품을\n쉽게 등록하세요", + }, +}; + +const MainPage = () => { + return ( +
+ +
+ + + +
+ +
+
+ ); +}; + +export default MainPage; diff --git a/src/pages/MainPage/components/Closing.tsx b/src/pages/MainPage/components/Closing.tsx new file mode 100644 index 00000000..cd1a3a64 --- /dev/null +++ b/src/pages/MainPage/components/Closing.tsx @@ -0,0 +1,28 @@ +import closingImg from "@/assets/img/img_home_bottom.png"; + +const Closing = () => { + return ( +
+
+
+ 믿을 수 있는 +
+ 판다마켓 중고 거래 +
+ 판다가 리뷰와 별점을 남기는 이미지 +
+
+ ); +}; + +export default Closing; diff --git a/src/pages/MainPage/components/Footer.tsx b/src/pages/MainPage/components/Footer.tsx new file mode 100644 index 00000000..e2388f3c --- /dev/null +++ b/src/pages/MainPage/components/Footer.tsx @@ -0,0 +1,46 @@ +import facebookIcon from "@/assets/icons/ic_facebook.svg"; +import twitterIcon from "@/assets/icons/ic_twitter.svg"; +import youtubeIcon from "@/assets/icons/ic_youtube.svg"; +import instagramIcon from "@/assets/icons/ic_instagram.svg"; + +const Footer = () => { + return ( + + ); +}; + +export default Footer; diff --git a/src/pages/MainPage/components/Hero.tsx b/src/pages/MainPage/components/Hero.tsx new file mode 100644 index 00000000..3b7a7a8f --- /dev/null +++ b/src/pages/MainPage/components/Hero.tsx @@ -0,0 +1,45 @@ +import heroImg from "@/assets/img/img_home_top.png"; +import { Link } from "react-router-dom"; + +const Hero = () => { + return ( +
+
+
+

+ 일상의 모든 물건을
+ 거래해보세요 +

+ +
+ 메인 화면의 장바구니를 든 판다 이미지 +
+
+ ); +}; + +export default Hero; diff --git a/src/pages/MainPage/components/Introduction.tsx b/src/pages/MainPage/components/Introduction.tsx new file mode 100644 index 00000000..31a080ba --- /dev/null +++ b/src/pages/MainPage/components/Introduction.tsx @@ -0,0 +1,54 @@ +interface Props { + className: string; + imgSrc: string; + keyword: string; + title: string; + description: string; +} + +const Introduction = ({ + className, + imgSrc, + keyword, + title, + description, +}: Props) => { + return ( +
+ 사이트의 주요 기능을 보여주는 이미지 +
+
+ {keyword} +
+
+ {title} +
+
+ {description} +
+
+
+ ); +}; + +export default Introduction; From ffa8b80d5a380fa19c70c71e7fe57fce03efa192 Mon Sep 17 00:00:00 2001 From: PC2502 Date: Mon, 14 Jul 2025 21:32:45 +0900 Subject: [PATCH 03/14] =?UTF-8?q?feat:=EB=A1=9C=EA=B7=B8=EC=9D=B8,=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=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84,=20=EC=9D=B4=EB=A9=94=EC=9D=BC=EA=B3=BC=20=EB=B9=84?= =?UTF-8?q?=EB=B0=80=EB=B2=88=ED=98=B8=20=EA=B2=80=EC=A6=9D=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 698 ++++++++++++++++-- package.json | 2 + public/reset.css | 48 -- src/App.tsx | 18 +- src/assets/icons/ic_facebook.svg | 3 + src/assets/icons/ic_google.svg | 10 + src/assets/icons/ic_instagram.svg | 3 + src/assets/icons/ic_kakaotalk.svg | 12 + src/assets/icons/ic_twitter.svg | 3 + src/assets/icons/ic_visibility_off.svg | 3 + src/assets/icons/ic_visibility_on.svg | 3 + src/assets/icons/ic_youtube.svg | 10 + src/assets/img/Img_home_01.png | Bin 0 -> 13524 bytes src/assets/img/Img_home_02.png | Bin 0 -> 16715 bytes src/assets/img/Img_home_03.png | Bin 0 -> 10666 bytes src/assets/img/Img_home_bottom.png | Bin 0 -> 55857 bytes src/assets/img/Img_home_top.png | Bin 0 -> 48969 bytes src/assets/img/panda-market_thumbnail.png | Bin 0 -> 112176 bytes src/index.css | 47 +- src/layouts/Nav.jsx | 59 +- src/main.tsx | 1 - src/pages/Login_SignupPage/LoginPage.tsx | 95 +++ src/pages/Login_SignupPage/SignupPage.tsx | 74 ++ .../components/FieldUncontrolled.tsx | 51 ++ .../components/SocialLogin.tsx | 16 + src/utils/validations.ts | 17 + vite.config.js | 3 +- 27 files changed, 1025 insertions(+), 151 deletions(-) delete mode 100644 public/reset.css create mode 100644 src/assets/icons/ic_facebook.svg create mode 100644 src/assets/icons/ic_google.svg create mode 100644 src/assets/icons/ic_instagram.svg create mode 100644 src/assets/icons/ic_kakaotalk.svg create mode 100644 src/assets/icons/ic_twitter.svg create mode 100644 src/assets/icons/ic_visibility_off.svg create mode 100644 src/assets/icons/ic_visibility_on.svg create mode 100644 src/assets/icons/ic_youtube.svg create mode 100644 src/assets/img/Img_home_01.png create mode 100644 src/assets/img/Img_home_02.png create mode 100644 src/assets/img/Img_home_03.png create mode 100644 src/assets/img/Img_home_bottom.png create mode 100644 src/assets/img/Img_home_top.png create mode 100644 src/assets/img/panda-market_thumbnail.png create mode 100644 src/pages/Login_SignupPage/LoginPage.tsx create mode 100644 src/pages/Login_SignupPage/SignupPage.tsx create mode 100644 src/pages/Login_SignupPage/components/FieldUncontrolled.tsx create mode 100644 src/pages/Login_SignupPage/components/SocialLogin.tsx create mode 100644 src/utils/validations.ts diff --git a/package-lock.json b/package-lock.json index 26aee5c1..4a250817 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "vite-project", "version": "0.0.0", "dependencies": { + "@tailwindcss/vite": "^4.1.11", "axios": "^1.10.0", "clsx": "^2.1.1", "react": "^19.1.0", @@ -25,6 +26,7 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.19", "globals": "^16.0.0", + "tailwindcss": "^4.1.11", "typescript": "^5.8.3", "vite": "^6.3.5" } @@ -33,7 +35,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -332,7 +333,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -349,7 +349,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -366,7 +365,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -383,7 +381,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -400,7 +397,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -417,7 +413,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -434,7 +429,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -451,7 +445,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -468,7 +461,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -485,7 +477,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -502,7 +493,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -519,7 +509,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -536,7 +525,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -553,7 +541,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -570,7 +557,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -587,7 +573,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -604,7 +589,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -621,7 +605,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -638,7 +621,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -655,7 +637,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -672,7 +653,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -689,7 +669,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -706,7 +685,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -723,7 +701,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -740,7 +717,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -983,11 +959,22 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.12", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -998,7 +985,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1008,14 +994,12 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.29", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1074,7 +1058,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1088,7 +1071,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1102,7 +1084,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1116,7 +1097,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1130,7 +1110,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1144,7 +1123,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1158,7 +1136,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1172,7 +1149,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1186,7 +1162,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1200,7 +1175,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1214,7 +1188,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1228,7 +1201,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1242,7 +1214,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1256,7 +1227,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1270,7 +1240,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1284,7 +1253,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1298,7 +1266,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1312,7 +1279,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1326,7 +1292,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1340,13 +1305,274 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ] }, + "node_modules/@tailwindcss/node": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", + "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "enhanced-resolve": "^5.18.1", + "jiti": "^2.4.2", + "lightningcss": "1.30.1", + "magic-string": "^0.30.17", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.11" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", + "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.4", + "tar": "^7.4.3" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.11", + "@tailwindcss/oxide-darwin-arm64": "4.1.11", + "@tailwindcss/oxide-darwin-x64": "4.1.11", + "@tailwindcss/oxide-freebsd-x64": "4.1.11", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", + "@tailwindcss/oxide-linux-x64-musl": "4.1.11", + "@tailwindcss/oxide-wasm32-wasi": "4.1.11", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", + "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", + "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", + "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", + "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", + "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", + "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", + "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", + "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", + "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", + "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@emnapi/wasi-threads": "^1.0.2", + "@napi-rs/wasm-runtime": "^0.2.11", + "@tybys/wasm-util": "^0.9.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", + "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", + "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz", + "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.11", + "@tailwindcss/oxide": "4.1.11", + "tailwindcss": "4.1.11" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1396,7 +1622,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, "license": "MIT" }, "node_modules/@types/json-schema": { @@ -1922,6 +2147,15 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -2042,6 +2276,15 @@ "node": ">=0.4.0" } }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2063,6 +2306,19 @@ "dev": true, "license": "ISC" }, + "node_modules/enhanced-resolve": { + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", + "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2112,7 +2368,6 @@ "version": "0.25.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -2415,7 +2670,6 @@ "version": "6.4.6", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "dev": true, "license": "MIT", "peerDependencies": { "picomatch": "^3 || ^4" @@ -2530,7 +2784,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -2635,6 +2888,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -2768,6 +3027,15 @@ "dev": true, "license": "ISC" }, + "node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2859,6 +3127,234 @@ "node": ">= 0.8.0" } }, + "node_modules/lightningcss": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", + "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", + "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", + "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", + "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", + "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", + "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", + "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2892,6 +3388,15 @@ "yallist": "^3.0.2" } }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -2972,6 +3477,42 @@ "node": "*" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2983,7 +3524,6 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, "funding": [ { "type": "github", @@ -3099,14 +3639,12 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -3119,7 +3657,6 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, "funding": [ { "type": "opencollective", @@ -3285,7 +3822,6 @@ "version": "4.44.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.1.tgz", "integrity": "sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -3394,7 +3930,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -3426,11 +3961,51 @@ "node": ">=8" } }, + "node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", + "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.4.4", @@ -3541,7 +4116,6 @@ "version": "6.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", - "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", diff --git a/package.json b/package.json index 857000d9..939c05d5 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "@tailwindcss/vite": "^4.1.11", "axios": "^1.10.0", "clsx": "^2.1.1", "react": "^19.1.0", @@ -27,6 +28,7 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.19", "globals": "^16.0.0", + "tailwindcss": "^4.1.11", "typescript": "^5.8.3", "vite": "^6.3.5" } diff --git a/public/reset.css b/public/reset.css deleted file mode 100644 index af944401..00000000 --- a/public/reset.css +++ /dev/null @@ -1,48 +0,0 @@ -/* http://meyerweb.com/eric/tools/css/reset/ - v2.0 | 20110126 - License: none (public domain) -*/ - -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -menu, nav, output, ruby, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; -} -/* HTML5 display-role reset for older browsers */ -article, aside, details, figcaption, figure, -footer, header, hgroup, menu, nav, section { - display: block; -} -body { - line-height: 1; -} -ol, ul { - list-style: none; -} -blockquote, q { - quotes: none; -} -blockquote:before, blockquote:after, -q:before, q:after { - content: ''; - content: none; -} -table { - border-collapse: collapse; - border-spacing: 0; -} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 1a5d1acb..5c506bff 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,18 +1,24 @@ -import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; +import { BrowserRouter, Routes, Route } from "react-router-dom"; import Nav from "./layouts/Nav"; import ItemsPage from "./pages/ItemsPage/ItemsPage"; import AddItemPage from "./pages/AddItemPage/AddItemPage"; import ItemDetialsPage from "./pages/ItemDetailsPage/ItemDetailsPage"; +import MainPage from "./pages/MainPage/MainPage"; +import LoginPage from "./pages/Login_SignupPage/LoginPage"; +import SignupPage from "./pages/Login_SignupPage/SignupPage"; const App = () => { return ( - +
+ +
+ ); }; diff --git a/src/main.tsx b/src/main.tsx index 47e3869b..b5ec4d28 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,5 +1,4 @@ import { createRoot } from "react-dom/client"; -import "../public/reset.css"; import "../public/common.css"; import "./index.css"; import App from "./App.jsx"; diff --git a/src/pages/Login_SignupPage/LoginPage.tsx b/src/pages/Login_SignupPage/LoginPage.tsx new file mode 100644 index 00000000..f365d87f --- /dev/null +++ b/src/pages/Login_SignupPage/LoginPage.tsx @@ -0,0 +1,95 @@ +import logo from "@/assets/logo/logo.svg"; +import { Link } from "react-router-dom"; +import FieldUncontrolled from "./components/FieldUncontrolled"; +import SocialLogin from "./components/SocialLogin"; +import { useEffect, useRef, useState } from "react"; +import clsx from "clsx"; + +export interface ILoginData { + email: string; + password: string; +} + +export const FIELD_MAP = { + email: { + id: "email", + label: "이메일", + type: "email", + }, + nickname: { + id: "nickname", + label: "닉네임", + type: "text", + }, + password: { + id: "password", + label: "비밀번호", + type: "password", + }, + passwordCheck: { + id: "passwordCheck", + label: "비밀번호 확인", + type: "password", + }, +} as const; + +const LoginPage = () => { + // const [isDisabled, setIsDisabled] = useState(true); + const loginData = useRef({ email: "", password: "" }); + + console.log(loginData.current); + + // useEffect(() => { + // for (const key in loginData.current) { + // console.log(key); + // if (!loginData.current[key as keyof ILoginData]) { + // console.log(key, loginData.current[key as keyof ILoginData]); + // setIsDisabled(true); + // return; + // } + // } + // setIsDisabled(false); + // }, [loginData.current.email, loginData.current.password]); + + const handleValidateInputs = () => {}; + + return ( +
+ + 판다마켓 로고 + + + + + + + 판다마켓이 처음이신가요? + 회원가입 + +
+ ); +}; + +export default LoginPage; diff --git a/src/pages/Login_SignupPage/SignupPage.tsx b/src/pages/Login_SignupPage/SignupPage.tsx new file mode 100644 index 00000000..56603996 --- /dev/null +++ b/src/pages/Login_SignupPage/SignupPage.tsx @@ -0,0 +1,74 @@ +import logo from "@/assets/logo/logo.svg"; +import { Link } from "react-router-dom"; +import FieldUncontrolled from "./components/FieldUncontrolled"; +import SocialLogin from "./components/SocialLogin"; +import { FIELD_MAP } from "./LoginPage"; +import { useRef, useState } from "react"; +import { type ILoginData } from "./LoginPage"; +import clsx from "clsx"; + +interface ISignupData extends ILoginData { + nickname: string; + passwordCheck: string; +} + +const SignupPage = () => { + // const [isDisabled, setIsDisabled] = useState(true); + const signupData = useRef({ + email: "", + nickname: "", + password: "", + passwordCheck: "", + }); + + console.log(signupData.current); + return ( +
+ + 판다마켓 로고 + + + + + + + + + 이미 회원이신가요? + 로그인 + +
+ ); +}; + +export default SignupPage; diff --git a/src/pages/Login_SignupPage/components/FieldUncontrolled.tsx b/src/pages/Login_SignupPage/components/FieldUncontrolled.tsx new file mode 100644 index 00000000..98bde9b1 --- /dev/null +++ b/src/pages/Login_SignupPage/components/FieldUncontrolled.tsx @@ -0,0 +1,51 @@ +import visibilityOn from "@/assets/icons/ic_visibility_on.svg"; +import visibilityOff from "@/assets/icons/ic_visibility_off.svg"; +import { type ILoginData } from "../LoginPage"; +import { useRef, useState } from "react"; + +interface Props { + id: string; + label: string; + type: "email" | "text" | "password"; + formData: ILoginData; +} + +const FieldUncontrolled = ({ id, label, type, formData }: Props) => { + const [isVisibilityOn, setIsVisibilityOn] = useState(false); + const inputRef = useRef(null); + const isPassword = type === "password"; + + const handleChangeValue = () => { + if (id in formData && inputRef.current) { + formData[id as keyof ILoginData] = inputRef.current?.value; + } + }; + + return ( +
+ +
+ + {isPassword ? ( + + ) : undefined} +
+
+ ); +}; + +export default FieldUncontrolled; diff --git a/src/pages/Login_SignupPage/components/SocialLogin.tsx b/src/pages/Login_SignupPage/components/SocialLogin.tsx new file mode 100644 index 00000000..26c78464 --- /dev/null +++ b/src/pages/Login_SignupPage/components/SocialLogin.tsx @@ -0,0 +1,16 @@ +import googleIcon from "@/assets/icons/ic_google.svg"; +import kakaoIcon from "@/assets/icons/ic_kakaotalk.svg"; + +const SocialLogin = () => { + return ( +
+ 간편 로그인하기 +
+ 구글 아이콘 + 카카오 아이콘 +
+
+ ); +}; + +export default SocialLogin; diff --git a/src/utils/validations.ts b/src/utils/validations.ts new file mode 100644 index 00000000..1246e18c --- /dev/null +++ b/src/utils/validations.ts @@ -0,0 +1,17 @@ +const EMAIL_REG = /^[A-Za-z0-9_\.\-]+@[A-Za-z0-9\-]+\.[A-Za-z0-9\-]+/; + +export const validateEmail = (targetEmail: string) => { + if (EMAIL_REG.test(targetEmail)) { + return { isValid: true, errorMsg: null }; + } else { + return { isValid: false, errorMsg: "잘못된 이메일 형식입니다" }; + } +}; + +export const validatePassword = (targetPassword: string) => { + if (targetPassword.length < 8) { + return { isValid: false, errorMsg: "비밀번호를 8자 이상 입력해주세요" }; + } else { + return { isValid: true, errorMsg: null }; + } +}; diff --git a/vite.config.js b/vite.config.js index 461d5972..48aa681d 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,9 +1,10 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; +import tailwindcss from "@tailwindcss/vite"; // https://vite.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [react(), tailwindcss()], assetsInclude: ["**/*.ttf"], resolve: { alias: { From bfb1eccfc1d4c06e3f9bf56ec90ef08e5ed8d25c Mon Sep 17 00:00:00 2001 From: PC2502 Date: Tue, 15 Jul 2025 17:23:30 +0900 Subject: [PATCH 04/14] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8,=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Login_SignupPage/LoginPage.tsx | 72 +++++++++------- src/pages/Login_SignupPage/SignupPage.tsx | 69 ++++++++------- .../components/FieldUncontrolled.tsx | 85 ++++++++++++++++++- src/utils/validations.ts | 19 ++++- 4 files changed, 175 insertions(+), 70 deletions(-) diff --git a/src/pages/Login_SignupPage/LoginPage.tsx b/src/pages/Login_SignupPage/LoginPage.tsx index f365d87f..6e666a10 100644 --- a/src/pages/Login_SignupPage/LoginPage.tsx +++ b/src/pages/Login_SignupPage/LoginPage.tsx @@ -1,20 +1,26 @@ import logo from "@/assets/logo/logo.svg"; -import { Link } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; import FieldUncontrolled from "./components/FieldUncontrolled"; import SocialLogin from "./components/SocialLogin"; import { useEffect, useRef, useState } from "react"; import clsx from "clsx"; +import { + validateEmail, + validatePassword, + validatePasswordCheck, +} from "@/utils/validations"; export interface ILoginData { email: string; password: string; } -export const FIELD_MAP = { +export const fieldMap = { email: { id: "email", label: "이메일", type: "email", + validator: validateEmail, }, nickname: { id: "nickname", @@ -25,61 +31,61 @@ export const FIELD_MAP = { id: "password", label: "비밀번호", type: "password", + validator: validatePassword, }, passwordCheck: { id: "passwordCheck", label: "비밀번호 확인", type: "password", + validator: validatePasswordCheck, }, } as const; const LoginPage = () => { - // const [isDisabled, setIsDisabled] = useState(true); + const [isValidating, setIsValidating] = useState(false); + const [hasError, setHasError] = useState(false); const loginData = useRef({ email: "", password: "" }); + const isMount = useRef(true); + const navigate = useNavigate(); + const commonFieldProps = { + formData: loginData.current, + validationTrigger: isValidating, + onCheckValidating: setIsValidating, + errorSetter: setHasError, + }; - console.log(loginData.current); + const handleStartValidation = () => { + //검증 시작할 때 hasError->false, 각 필드에서 하나라도 에러있으면 true + setIsValidating(true); + setHasError(false); + }; - // useEffect(() => { - // for (const key in loginData.current) { - // console.log(key); - // if (!loginData.current[key as keyof ILoginData]) { - // console.log(key, loginData.current[key as keyof ILoginData]); - // setIsDisabled(true); - // return; - // } - // } - // setIsDisabled(false); - // }, [loginData.current.email, loginData.current.password]); + useEffect(() => { + if (isMount.current) { + isMount.current = false; + return; + } - const handleValidateInputs = () => {}; + if (isValidating || hasError === true) { + return; + } + + navigate("/"); + }, [isValidating, hasError]); return (
판다마켓 로고 - - + + diff --git a/src/pages/Login_SignupPage/SignupPage.tsx b/src/pages/Login_SignupPage/SignupPage.tsx index 56603996..b9b14e35 100644 --- a/src/pages/Login_SignupPage/SignupPage.tsx +++ b/src/pages/Login_SignupPage/SignupPage.tsx @@ -1,9 +1,9 @@ import logo from "@/assets/logo/logo.svg"; -import { Link } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; import FieldUncontrolled from "./components/FieldUncontrolled"; import SocialLogin from "./components/SocialLogin"; -import { FIELD_MAP } from "./LoginPage"; -import { useRef, useState } from "react"; +import { fieldMap } from "./LoginPage"; +import { useEffect, useRef, useState } from "react"; import { type ILoginData } from "./LoginPage"; import clsx from "clsx"; @@ -13,52 +13,61 @@ interface ISignupData extends ILoginData { } const SignupPage = () => { - // const [isDisabled, setIsDisabled] = useState(true); + const [isValidating, setIsValidating] = useState(false); + const [hasError, setHasError] = useState(false); const signupData = useRef({ email: "", nickname: "", password: "", passwordCheck: "", }); + const isMount = useRef(true); + const navigate = useNavigate(); + const commonFieldProps = { + formData: signupData.current, + validationTrigger: isValidating, + onCheckValidating: setIsValidating, + errorSetter: setHasError, + }; + + const handleStartValidation = () => { + //검증 시작할 때 hasError->false, 각 필드에서 하나라도 에러있으면 true + setIsValidating(true); + setHasError(false); + }; + + useEffect(() => { + if (isMount.current) { + isMount.current = false; + return; + } + + if (isValidating || hasError === true) { + return; + } + + navigate("/login"); + }, [isValidating, hasError]); - console.log(signupData.current); return (
판다마켓 로고 + + + - - - diff --git a/src/pages/Login_SignupPage/components/FieldUncontrolled.tsx b/src/pages/Login_SignupPage/components/FieldUncontrolled.tsx index 98bde9b1..78b1194b 100644 --- a/src/pages/Login_SignupPage/components/FieldUncontrolled.tsx +++ b/src/pages/Login_SignupPage/components/FieldUncontrolled.tsx @@ -1,19 +1,48 @@ import visibilityOn from "@/assets/icons/ic_visibility_on.svg"; import visibilityOff from "@/assets/icons/ic_visibility_off.svg"; +import isEmpty from "@/utils/isEmpty"; import { type ILoginData } from "../LoginPage"; -import { useRef, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; + +type SingleParamValidator = (value: string) => { + isValid: boolean; + errorMsg: string | null; +}; +type TwoParamValidator = ( + value: string, + comparisonValue: string +) => { isValid: boolean; errorMsg: string | null }; interface Props { id: string; label: string; type: "email" | "text" | "password"; + validator?: SingleParamValidator | TwoParamValidator; formData: ILoginData; + validationTrigger: boolean; + onCheckValidating: React.Dispatch>; + errorSetter: React.Dispatch>; + //비밀번호 확인을 위한 비밀번호 값 + comparisonValue?: string; } -const FieldUncontrolled = ({ id, label, type, formData }: Props) => { +const FieldUncontrolled = ({ + id, + label, + type, + validator, + formData, + validationTrigger, + onCheckValidating, + errorSetter, + comparisonValue, +}: Props) => { + const [errorMsg, setErrorMsg] = useState(null); const [isVisibilityOn, setIsVisibilityOn] = useState(false); const inputRef = useRef(null); + const isMount = useRef(true); const isPassword = type === "password"; + const placeholderMsg = `${label}을(를) 입력해주세요`; const handleChangeValue = () => { if (id in formData && inputRef.current) { @@ -21,6 +50,55 @@ const FieldUncontrolled = ({ id, label, type, formData }: Props) => { } }; + useEffect(() => { + if (isMount.current) { + isMount.current = false; + return; + } + + //의존성 배열에 다른 것을 포함시키면서 trigger가 true일 때에만 실행하도록 + if (!validationTrigger) { + return; + } + + const value = inputRef.current?.value; + + if (isEmpty(value)) { + errorSetter(true); + setErrorMsg(placeholderMsg); + onCheckValidating(false); + return; + } + + if (validator && id === "passwordCheck") { + const { isValid, errorMsg } = validator( + value as string, + comparisonValue as string + ); + + if (!isValid) { + errorSetter(true); + setErrorMsg(errorMsg); + onCheckValidating(false); + return; + } + } else if (validator) { + const { isValid, errorMsg } = (validator as SingleParamValidator)( + value as string + ); + + if (!isValid) { + errorSetter(true); + setErrorMsg(errorMsg); + onCheckValidating(false); + return; + } + } + + setErrorMsg(null); + onCheckValidating(false); + }, [validationTrigger, isEmpty, validator]); + return (
@@ -29,7 +107,7 @@ const FieldUncontrolled = ({ id, label, type, formData }: Props) => { id={id} ref={inputRef} type={isPassword && isVisibilityOn ? "text" : type} - placeholder={`${label}을 입력해주세요`} + placeholder={placeholderMsg} onChange={handleChangeValue} /> {isPassword ? ( @@ -44,6 +122,7 @@ const FieldUncontrolled = ({ id, label, type, formData }: Props) => { ) : undefined}
+ {errorMsg ?
{errorMsg}
: undefined} ); }; diff --git a/src/utils/validations.ts b/src/utils/validations.ts index 1246e18c..32174bca 100644 --- a/src/utils/validations.ts +++ b/src/utils/validations.ts @@ -1,17 +1,28 @@ const EMAIL_REG = /^[A-Za-z0-9_\.\-]+@[A-Za-z0-9\-]+\.[A-Za-z0-9\-]+/; -export const validateEmail = (targetEmail: string) => { - if (EMAIL_REG.test(targetEmail)) { +export const validateEmail = (emailValue: string) => { + if (EMAIL_REG.test(emailValue)) { return { isValid: true, errorMsg: null }; } else { return { isValid: false, errorMsg: "잘못된 이메일 형식입니다" }; } }; -export const validatePassword = (targetPassword: string) => { - if (targetPassword.length < 8) { +export const validatePassword = (passwordValue: string) => { + if (passwordValue.length < 8) { return { isValid: false, errorMsg: "비밀번호를 8자 이상 입력해주세요" }; } else { return { isValid: true, errorMsg: null }; } }; + +export const validatePasswordCheck = ( + passwordValue: string, + passwordCheckValue: string +) => { + if (passwordValue !== passwordCheckValue) { + return { isValid: false, errorMsg: "비밀번호가 일치하지 않습니다" }; + } else { + return { isValid: true, errorMsg: null }; + } +}; From 8aff358516d2930883b5349065735487cfbfa8e7 Mon Sep 17 00:00:00 2001 From: PC2502 Date: Tue, 15 Jul 2025 19:22:45 +0900 Subject: [PATCH 05/14] =?UTF-8?q?style:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8,=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=20css=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.css | 9 ---- src/pages/Login_SignupPage/LoginPage.tsx | 30 ++++++++---- src/pages/Login_SignupPage/SignupPage.tsx | 22 ++++++--- .../components/FieldUncontrolled.tsx | 46 +++++++++++++++---- .../components/SocialLogin.tsx | 4 +- .../MainPage/components/Introduction.tsx | 2 +- 6 files changed, 79 insertions(+), 34 deletions(-) diff --git a/src/index.css b/src/index.css index 04e7efdb..028f08dd 100644 --- a/src/index.css +++ b/src/index.css @@ -33,10 +33,6 @@ --spacing-md: 16px; --spacing-lg: 24px; --spacing-xl: 32px; - - /* Fonts */ - --font-weight-bold: 700; - --font-weight-regular: 400; } @font-face { @@ -104,8 +100,3 @@ body { font-family: var(--font-primary); word-break: keep-all; } - -a { - color: unset; - text-decoration: none; -} diff --git a/src/pages/Login_SignupPage/LoginPage.tsx b/src/pages/Login_SignupPage/LoginPage.tsx index 6e666a10..30c86931 100644 --- a/src/pages/Login_SignupPage/LoginPage.tsx +++ b/src/pages/Login_SignupPage/LoginPage.tsx @@ -20,30 +20,34 @@ export const fieldMap = { id: "email", label: "이메일", type: "email", + placeholder: "이메일을 입력해주세요", validator: validateEmail, }, nickname: { id: "nickname", label: "닉네임", type: "text", + placeholder: "닉네임을 입력해주세요", }, password: { id: "password", label: "비밀번호", type: "password", + placeholder: "비밀번호를 입력해주세요", validator: validatePassword, }, passwordCheck: { id: "passwordCheck", label: "비밀번호 확인", type: "password", + placeholder: "비밀번호를 입력해주세요", validator: validatePasswordCheck, }, } as const; const LoginPage = () => { const [isValidating, setIsValidating] = useState(false); - const [hasError, setHasError] = useState(false); + const [hasError, setHasError] = useState(true); const loginData = useRef({ email: "", password: "" }); const isMount = useRef(true); const navigate = useNavigate(); @@ -74,15 +78,23 @@ const LoginPage = () => { }, [isValidating, hasError]); return ( -
+
- 판다마켓 로고 + 판다마켓 로고 - - 판다마켓이 처음이신가요? - 회원가입 - +
+ 판다마켓이 처음이신가요?  + + 회원가입 + +
); }; diff --git a/src/pages/Login_SignupPage/SignupPage.tsx b/src/pages/Login_SignupPage/SignupPage.tsx index b9b14e35..bed768e1 100644 --- a/src/pages/Login_SignupPage/SignupPage.tsx +++ b/src/pages/Login_SignupPage/SignupPage.tsx @@ -14,7 +14,7 @@ interface ISignupData extends ILoginData { const SignupPage = () => { const [isValidating, setIsValidating] = useState(false); - const [hasError, setHasError] = useState(false); + const [hasError, setHasError] = useState(true); const signupData = useRef({ email: "", nickname: "", @@ -50,9 +50,17 @@ const SignupPage = () => { }, [isValidating, hasError]); return ( -
+
- 판다마켓 로고 + 판다마켓 로고 @@ -72,10 +80,12 @@ const SignupPage = () => { 회원가입 - +
이미 회원이신가요? - 로그인 - + + 로그인 + +
); }; diff --git a/src/pages/Login_SignupPage/components/FieldUncontrolled.tsx b/src/pages/Login_SignupPage/components/FieldUncontrolled.tsx index 78b1194b..2f2a7151 100644 --- a/src/pages/Login_SignupPage/components/FieldUncontrolled.tsx +++ b/src/pages/Login_SignupPage/components/FieldUncontrolled.tsx @@ -3,6 +3,7 @@ import visibilityOff from "@/assets/icons/ic_visibility_off.svg"; import isEmpty from "@/utils/isEmpty"; import { type ILoginData } from "../LoginPage"; import React, { useEffect, useRef, useState } from "react"; +import clsx from "clsx"; type SingleParamValidator = (value: string) => { isValid: boolean; @@ -17,6 +18,7 @@ interface Props { id: string; label: string; type: "email" | "text" | "password"; + placeholder: string; validator?: SingleParamValidator | TwoParamValidator; formData: ILoginData; validationTrigger: boolean; @@ -30,6 +32,7 @@ const FieldUncontrolled = ({ id, label, type, + placeholder, validator, formData, validationTrigger, @@ -42,7 +45,6 @@ const FieldUncontrolled = ({ const inputRef = useRef(null); const isMount = useRef(true); const isPassword = type === "password"; - const placeholderMsg = `${label}을(를) 입력해주세요`; const handleChangeValue = () => { if (id in formData && inputRef.current) { @@ -65,7 +67,7 @@ const FieldUncontrolled = ({ if (isEmpty(value)) { errorSetter(true); - setErrorMsg(placeholderMsg); + setErrorMsg(placeholder); onCheckValidating(false); return; } @@ -101,28 +103,56 @@ const FieldUncontrolled = ({ return (
- -
+ +
{ + setErrorMsg(null); + }} /> {isPassword ? ( ) : undefined}
- {errorMsg ?
{errorMsg}
: undefined} + {errorMsg ? ( +
+ {errorMsg} +
+ ) : undefined}
); }; diff --git a/src/pages/Login_SignupPage/components/SocialLogin.tsx b/src/pages/Login_SignupPage/components/SocialLogin.tsx index 26c78464..69d788c4 100644 --- a/src/pages/Login_SignupPage/components/SocialLogin.tsx +++ b/src/pages/Login_SignupPage/components/SocialLogin.tsx @@ -3,9 +3,9 @@ import kakaoIcon from "@/assets/icons/ic_kakaotalk.svg"; const SocialLogin = () => { return ( -
+
간편 로그인하기 -
+
구글 아이콘 카카오 아이콘
diff --git a/src/pages/MainPage/components/Introduction.tsx b/src/pages/MainPage/components/Introduction.tsx index 31a080ba..aa0461fd 100644 --- a/src/pages/MainPage/components/Introduction.tsx +++ b/src/pages/MainPage/components/Introduction.tsx @@ -41,7 +41,7 @@ const Introduction = ({ {title}
{description} From 2085d9734aace94288d3895e17d3aee1dd1e7e55 Mon Sep 17 00:00:00 2001 From: PC2502 Date: Tue, 15 Jul 2025 20:14:59 +0900 Subject: [PATCH 06/14] =?UTF-8?q?style:=20=EC=9D=B4=EB=AF=B8=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EC=9D=B4=EC=8B=A0=EA=B0=80=EC=9A=94=20=EB=AC=B8?= =?UTF-8?q?=EA=B5=AC=20=EC=97=AC=EB=B0=B1=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Login_SignupPage/SignupPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Login_SignupPage/SignupPage.tsx b/src/pages/Login_SignupPage/SignupPage.tsx index bed768e1..c9157972 100644 --- a/src/pages/Login_SignupPage/SignupPage.tsx +++ b/src/pages/Login_SignupPage/SignupPage.tsx @@ -81,7 +81,7 @@ const SignupPage = () => {
- 이미 회원이신가요? + 이미 회원이신가요?  로그인 From 0111ab0740fec5823549453672d994d35741aba1 Mon Sep 17 00:00:00 2001 From: YJH <91662247+khuyjh@users.noreply.github.com> Date: Tue, 15 Jul 2025 20:20:13 +0900 Subject: [PATCH 07/14] Rename Img_home_01.png to img_home_01.png --- src/assets/img/{Img_home_01.png => img_home_01.png} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename src/assets/img/{Img_home_01.png => img_home_01.png} (100%) diff --git a/src/assets/img/Img_home_01.png b/src/assets/img/img_home_01.png similarity index 100% rename from src/assets/img/Img_home_01.png rename to src/assets/img/img_home_01.png From 3341d3d6d264322b2a5f7827ccdfa9509ea62ffa Mon Sep 17 00:00:00 2001 From: YJH <91662247+khuyjh@users.noreply.github.com> Date: Tue, 15 Jul 2025 20:20:26 +0900 Subject: [PATCH 08/14] Rename Img_home_top.png to img_home_top.png --- .../img/{Img_home_top.png => img_home_top.png} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename src/assets/img/{Img_home_top.png => img_home_top.png} (100%) diff --git a/src/assets/img/Img_home_top.png b/src/assets/img/img_home_top.png similarity index 100% rename from src/assets/img/Img_home_top.png rename to src/assets/img/img_home_top.png From 05ae279c6671a8c0a51962f196646a004772d904 Mon Sep 17 00:00:00 2001 From: YJH <91662247+khuyjh@users.noreply.github.com> Date: Tue, 15 Jul 2025 20:20:37 +0900 Subject: [PATCH 09/14] Rename Img_home_bottom.png to img_home_bottom.png --- .../{Img_home_bottom.png => img_home_bottom.png} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename src/assets/img/{Img_home_bottom.png => img_home_bottom.png} (100%) diff --git a/src/assets/img/Img_home_bottom.png b/src/assets/img/img_home_bottom.png similarity index 100% rename from src/assets/img/Img_home_bottom.png rename to src/assets/img/img_home_bottom.png From e750a958b42802a2052512842895d13e0c870adf Mon Sep 17 00:00:00 2001 From: YJH <91662247+khuyjh@users.noreply.github.com> Date: Tue, 15 Jul 2025 20:20:48 +0900 Subject: [PATCH 10/14] Rename Img_home_03.png to img_home_03.png --- src/assets/img/{Img_home_03.png => img_home_03.png} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename src/assets/img/{Img_home_03.png => img_home_03.png} (100%) diff --git a/src/assets/img/Img_home_03.png b/src/assets/img/img_home_03.png similarity index 100% rename from src/assets/img/Img_home_03.png rename to src/assets/img/img_home_03.png From 0bf26604c98ac1fab2601e456a35d9ea3bddce17 Mon Sep 17 00:00:00 2001 From: YJH <91662247+khuyjh@users.noreply.github.com> Date: Tue, 15 Jul 2025 20:20:59 +0900 Subject: [PATCH 11/14] Rename Img_home_02.png to img_home_02.png --- src/assets/img/{Img_home_02.png => img_home_02.png} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename src/assets/img/{Img_home_02.png => img_home_02.png} (100%) diff --git a/src/assets/img/Img_home_02.png b/src/assets/img/img_home_02.png similarity index 100% rename from src/assets/img/Img_home_02.png rename to src/assets/img/img_home_02.png From 68d8a65b1910f38279404fd200f1927af8ea7393 Mon Sep 17 00:00:00 2001 From: PC2502 Date: Tue, 15 Jul 2025 20:22:54 +0900 Subject: [PATCH 12/14] =?UTF-8?q?chore:=20=EC=82=AC=EC=9D=B4=ED=8A=B8=20ti?= =?UTF-8?q?tle=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index f90e9e0d..fe212c23 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - items + 판다마켓
From 388e29434957780c73ce9bbc8504abe049fa94c6 Mon Sep 17 00:00:00 2001 From: PC2502 Date: Tue, 15 Jul 2025 23:00:23 +0900 Subject: [PATCH 13/14] =?UTF-8?q?fix:Link=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=EC=99=80=20button=ED=83=9C=EA=B7=B8=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layouts/Nav.jsx | 14 ++++++++------ src/pages/MainPage/components/Hero.tsx | 16 +++++++++------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/layouts/Nav.jsx b/src/layouts/Nav.jsx index 9ced315d..181d1133 100644 --- a/src/layouts/Nav.jsx +++ b/src/layouts/Nav.jsx @@ -33,12 +33,14 @@ const Nav = () => { 중고마켓
- + + +
diff --git a/src/pages/MainPage/components/Hero.tsx b/src/pages/MainPage/components/Hero.tsx index 3b7a7a8f..1ad32803 100644 --- a/src/pages/MainPage/components/Hero.tsx +++ b/src/pages/MainPage/components/Hero.tsx @@ -22,15 +22,17 @@ const Hero = () => { 일상의 모든 물건을
거래해보세요 - + type="button" + aria-label="중고마켓 페이지 이동 버튼" + > + 구경하러 가기 + +
Date: Wed, 16 Jul 2025 13:45:51 +0900 Subject: [PATCH 14/14] =?UTF-8?q?fix:=20validatePasswordCheck=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=EA=B3=BC=20FiledUncontrolled=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=20=ED=83=80=EC=9E=85=20=EC=9D=BC=EC=B9=98?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Login_SignupPage/LoginPage.tsx | 2 +- .../components/FieldUncontrolled.tsx | 15 +++++---------- src/utils/validations.ts | 7 ++++--- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/pages/Login_SignupPage/LoginPage.tsx b/src/pages/Login_SignupPage/LoginPage.tsx index 30c86931..8edbe62c 100644 --- a/src/pages/Login_SignupPage/LoginPage.tsx +++ b/src/pages/Login_SignupPage/LoginPage.tsx @@ -40,7 +40,7 @@ export const fieldMap = { id: "passwordCheck", label: "비밀번호 확인", type: "password", - placeholder: "비밀번호를 입력해주세요", + placeholder: "비밀번호를 다시 입력해주세요", validator: validatePasswordCheck, }, } as const; diff --git a/src/pages/Login_SignupPage/components/FieldUncontrolled.tsx b/src/pages/Login_SignupPage/components/FieldUncontrolled.tsx index 2f2a7151..f060036b 100644 --- a/src/pages/Login_SignupPage/components/FieldUncontrolled.tsx +++ b/src/pages/Login_SignupPage/components/FieldUncontrolled.tsx @@ -11,7 +11,7 @@ type SingleParamValidator = (value: string) => { }; type TwoParamValidator = ( value: string, - comparisonValue: string + comparisonValue: string | undefined ) => { isValid: boolean; errorMsg: string | null }; interface Props { @@ -63,7 +63,7 @@ const FieldUncontrolled = ({ return; } - const value = inputRef.current?.value; + const value = inputRef.current!.value; if (isEmpty(value)) { errorSetter(true); @@ -73,10 +73,7 @@ const FieldUncontrolled = ({ } if (validator && id === "passwordCheck") { - const { isValid, errorMsg } = validator( - value as string, - comparisonValue as string - ); + const { isValid, errorMsg } = validator(value, comparisonValue); if (!isValid) { errorSetter(true); @@ -85,9 +82,7 @@ const FieldUncontrolled = ({ return; } } else if (validator) { - const { isValid, errorMsg } = (validator as SingleParamValidator)( - value as string - ); + const { isValid, errorMsg } = (validator as SingleParamValidator)(value); if (!isValid) { errorSetter(true); @@ -128,7 +123,7 @@ const FieldUncontrolled = ({ /> {isPassword ? (