diff --git a/.env b/.env
new file mode 100644
index 00000000..5e4542e7
--- /dev/null
+++ b/.env
@@ -0,0 +1,4 @@
+# .env.local (또는 .env)
+NEXT_PUBLIC_API_URL=https://panda-market-api.vercel.app
+# NEXT_PUBLIC_MAPBOX_KEY=abc123xyz
+# SECRET_JWT_KEY=super_secret_key
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..03025a5e
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,16 @@
+{
+ "cSpell.words": [
+ "bootcamp",
+ "choicenews",
+ "chosun",
+ "codeit",
+ "Favorited",
+ "gstatic",
+ "hanatour",
+ "pinimg",
+ "pixabay",
+ "Pretendard",
+ "updatedtag",
+ "wccftech"
+ ]
+}
\ No newline at end of file
diff --git a/next.config.js b/next.config.js
index a843cbee..6fb7ac30 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,6 +1,61 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
-}
+ experimental: {
+ appDir: true, // ✅ App Router 활성화 (Next 13 이상)
+ },
+ images: {
+ remotePatterns: [
+ {
+ protocol: "https",
+ hostname: "cdn.wccftech.com",
+ },
+ {
+ protocol: "https",
+ hostname: "example.com",
+ },
+ {
+ protocol: "https",
+ hostname: "image.hanatour.com",
+ },
+ {
+ protocol: "https",
+ hostname: "cdn.choicenews.co.kr",
+ },
+ {
+ protocol: "https",
+ hostname: "sprint-fe-project.s3.ap-northeast-2.amazonaws.com",
+ },
+ {
+ protocol: "https",
+ hostname: "cdn.pixabay.com",
+ },
+ {
+ protocol: "https",
+ hostname: "i.pinimg.com",
+ },
+ {
+ protocol: "https",
+ hostname: "upload.wikimedia.org",
+ },
+ {
+ protocol: "https",
+ hostname: "encrypted-tbn0.gstatic.com",
+ },
+ {
+ protocol: "https",
+ hostname: "health.chosun.com",
+ },
+ {
+ protocol: "https",
+ hostname: "via.placeholder.com",
+ },
+ {
+ protocol: "https",
+ hostname: "images.unsplash.com",
+ },
+ ],
+ },
+};
-module.exports = nextConfig
+module.exports = nextConfig;
diff --git a/package-lock.json b/package-lock.json
index baa2b665..a7b7efcf 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,16 +8,23 @@
"name": "fe-weekly-mission",
"version": "0.1.0",
"dependencies": {
- "next": "13.5.6",
- "react": "^18",
- "react-dom": "^18"
+ "@tanstack/react-query": "^5.74.4",
+ "axios": "^1.8.4",
+ "clsx": "^2.1.1",
+ "framer-motion": "^12.7.4",
+ "next": "^15.3.1",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
},
"devDependencies": {
- "@types/node": "^20",
- "@types/react": "^18",
- "@types/react-dom": "^18",
+ "@types/node": "^20.17.30",
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
+ "autoprefixer": "^10.4.21",
"eslint": "^8",
"eslint-config-next": "13.5.6",
+ "postcss": "^8.5.3",
+ "tailwindcss": "^3.4.17",
"typescript": "^5"
}
},
@@ -30,6 +37,19 @@
"node": ">=0.10.0"
}
},
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/@babel/runtime": {
"version": "7.23.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.4.tgz",
@@ -42,6 +62,16 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@emnapi/runtime": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz",
+ "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -131,10 +161,488 @@
"integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==",
"dev": true
},
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.34.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.1.tgz",
+ "integrity": "sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-arm64": "1.1.0"
+ }
+ },
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.34.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.1.tgz",
+ "integrity": "sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-x64": "1.1.0"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.1.0.tgz",
+ "integrity": "sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-x64": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.1.0.tgz",
+ "integrity": "sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.1.0.tgz",
+ "integrity": "sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm64": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.1.0.tgz",
+ "integrity": "sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-ppc64": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.1.0.tgz",
+ "integrity": "sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-s390x": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.1.0.tgz",
+ "integrity": "sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-x64": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.1.0.tgz",
+ "integrity": "sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.1.0.tgz",
+ "integrity": "sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.1.0.tgz",
+ "integrity": "sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm": {
+ "version": "0.34.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.1.tgz",
+ "integrity": "sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.1.0"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.34.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.1.tgz",
+ "integrity": "sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.1.0"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.34.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.1.tgz",
+ "integrity": "sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.1.0"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.34.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.1.tgz",
+ "integrity": "sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.1.0"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.34.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.1.tgz",
+ "integrity": "sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.1.0"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.34.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.1.tgz",
+ "integrity": "sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.1.0"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.34.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.1.tgz",
+ "integrity": "sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==",
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.4.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.34.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.1.tgz",
+ "integrity": "sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.34.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.1.tgz",
+ "integrity": "sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.8",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
+ "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/set-array": "^1.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "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"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.25",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
"node_modules/@next/env": {
- "version": "13.5.6",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-13.5.6.tgz",
- "integrity": "sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw=="
+ "version": "15.3.1",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.1.tgz",
+ "integrity": "sha512-cwK27QdzrMblHSn9DZRV+DQscHXRuJv6MydlJRpFSqJWZrTYMLzKDeyueJNN9MGd8NNiUKzDQADAf+dMLXX7YQ==",
+ "license": "MIT"
},
"node_modules/@next/eslint-plugin-next": {
"version": "13.5.6",
@@ -146,12 +654,13 @@
}
},
"node_modules/@next/swc-darwin-arm64": {
- "version": "13.5.6",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.6.tgz",
- "integrity": "sha512-5nvXMzKtZfvcu4BhtV0KH1oGv4XEW+B+jOfmBdpFI3C7FrB/MfujRpWYSBBO64+qbW8pkZiSyQv9eiwnn5VIQA==",
+ "version": "15.3.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.1.tgz",
+ "integrity": "sha512-hjDw4f4/nla+6wysBL07z52Gs55Gttp5Bsk5/8AncQLJoisvTBP0pRIBK/B16/KqQyH+uN4Ww8KkcAqJODYH3w==",
"cpu": [
"arm64"
],
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
@@ -161,12 +670,13 @@
}
},
"node_modules/@next/swc-darwin-x64": {
- "version": "13.5.6",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.6.tgz",
- "integrity": "sha512-6cgBfxg98oOCSr4BckWjLLgiVwlL3vlLj8hXg2b+nDgm4bC/qVXXLfpLB9FHdoDu4057hzywbxKvmYGmi7yUzA==",
+ "version": "15.3.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.1.tgz",
+ "integrity": "sha512-q+aw+cJ2ooVYdCEqZVk+T4Ni10jF6Fo5DfpEV51OupMaV5XL6pf3GCzrk6kSSZBsMKZtVC1Zm/xaNBFpA6bJ2g==",
"cpu": [
"x64"
],
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
@@ -176,12 +686,13 @@
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
- "version": "13.5.6",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.6.tgz",
- "integrity": "sha512-txagBbj1e1w47YQjcKgSU4rRVQ7uF29YpnlHV5xuVUsgCUf2FmyfJ3CPjZUvpIeXCJAoMCFAoGnbtX86BK7+sg==",
+ "version": "15.3.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.1.tgz",
+ "integrity": "sha512-wBQ+jGUI3N0QZyWmmvRHjXjTWFy8o+zPFLSOyAyGFI94oJi+kK/LIZFJXeykvgXUk1NLDAEFDZw/NVINhdk9FQ==",
"cpu": [
"arm64"
],
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -191,12 +702,13 @@
}
},
"node_modules/@next/swc-linux-arm64-musl": {
- "version": "13.5.6",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.6.tgz",
- "integrity": "sha512-cGd+H8amifT86ZldVJtAKDxUqeFyLWW+v2NlBULnLAdWsiuuN8TuhVBt8ZNpCqcAuoruoSWynvMWixTFcroq+Q==",
+ "version": "15.3.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.1.tgz",
+ "integrity": "sha512-IIxXEXRti/AulO9lWRHiCpUUR8AR/ZYLPALgiIg/9ENzMzLn3l0NSxVdva7R/VDcuSEBo0eGVCe3evSIHNz0Hg==",
"cpu": [
"arm64"
],
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -206,12 +718,13 @@
}
},
"node_modules/@next/swc-linux-x64-gnu": {
- "version": "13.5.6",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.6.tgz",
- "integrity": "sha512-Mc2b4xiIWKXIhBy2NBTwOxGD3nHLmq4keFk+d4/WL5fMsB8XdJRdtUlL87SqVCTSaf1BRuQQf1HvXZcy+rq3Nw==",
+ "version": "15.3.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.1.tgz",
+ "integrity": "sha512-bfI4AMhySJbyXQIKH5rmLJ5/BP7bPwuxauTvVEiJ/ADoddaA9fgyNNCcsbu9SlqfHDoZmfI6g2EjzLwbsVTr5A==",
"cpu": [
"x64"
],
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -221,12 +734,13 @@
}
},
"node_modules/@next/swc-linux-x64-musl": {
- "version": "13.5.6",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.6.tgz",
- "integrity": "sha512-CFHvP9Qz98NruJiUnCe61O6GveKKHpJLloXbDSWRhqhkJdZD2zU5hG+gtVJR//tyW897izuHpM6Gtf6+sNgJPQ==",
+ "version": "15.3.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.1.tgz",
+ "integrity": "sha512-FeAbR7FYMWR+Z+M5iSGytVryKHiAsc0x3Nc3J+FD5NVbD5Mqz7fTSy8CYliXinn7T26nDMbpExRUI/4ekTvoiA==",
"cpu": [
"x64"
],
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -236,27 +750,13 @@
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
- "version": "13.5.6",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.6.tgz",
- "integrity": "sha512-aFv1ejfkbS7PUa1qVPwzDHjQWQtknzAZWGTKYIAaS4NMtBlk3VyA6AYn593pqNanlicewqyl2jUhQAaFV/qXsg==",
+ "version": "15.3.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.1.tgz",
+ "integrity": "sha512-yP7FueWjphQEPpJQ2oKmshk/ppOt+0/bB8JC8svPUZNy0Pi3KbPx2Llkzv1p8CoQa+D2wknINlJpHf3vtChVBw==",
"cpu": [
"arm64"
],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-win32-ia32-msvc": {
- "version": "13.5.6",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.6.tgz",
- "integrity": "sha512-XqqpHgEIlBHvzwG8sp/JXMFkLAfGLqkbVsyN+/Ih1mR8INb6YCc2x/Mbwi6hsAgUnqQztz8cvEbHJUbSl7RHDg==",
- "cpu": [
- "ia32"
- ],
+ "license": "MIT",
"optional": true,
"os": [
"win32"
@@ -266,12 +766,13 @@
}
},
"node_modules/@next/swc-win32-x64-msvc": {
- "version": "13.5.6",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.6.tgz",
- "integrity": "sha512-Cqfe1YmOS7k+5mGu92nl5ULkzpKuxJrP3+4AEuPmrpFZ3BHxTY3TnHmU1On3bFmFFs6FbTcdF58CCUProGpIGQ==",
+ "version": "15.3.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.1.tgz",
+ "integrity": "sha512-3PMvF2zRJAifcRNni9uMk/gulWfWS+qVI/pagd+4yLF5bcXPZPPH2xlYRYOsUjmCJOXSTAC2PjRzbhsRzR2fDQ==",
"cpu": [
"x64"
],
+ "license": "MIT",
"optional": true,
"os": [
"win32"
@@ -312,7 +813,18 @@
"fastq": "^1.6.0"
},
"engines": {
- "node": ">= 8"
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
}
},
"node_modules/@rushstack/eslint-patch": {
@@ -321,12 +833,45 @@
"integrity": "sha512-2/U3GXA6YiPYQDLGwtGlnNgKYBSwCFIHf8Y9LUY5VATHdtbLlU0Y1R3QoBnT0aB4qv/BEiVVsj7LJXoQCgJ2vA==",
"dev": true
},
+ "node_modules/@swc/counter": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
+ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
+ "license": "Apache-2.0"
+ },
"node_modules/@swc/helpers": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz",
- "integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==",
+ "version": "0.5.15",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
+ "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
+ "license": "Apache-2.0",
"dependencies": {
- "tslib": "^2.4.0"
+ "tslib": "^2.8.0"
+ }
+ },
+ "node_modules/@tanstack/query-core": {
+ "version": "5.74.4",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.74.4.tgz",
+ "integrity": "sha512-YuG0A0+3i9b2Gfo9fkmNnkUWh5+5cFhWBN0pJAHkHilTx6A0nv8kepkk4T4GRt4e5ahbtFj2eTtkiPcVU1xO4A==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-query": {
+ "version": "5.74.4",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.74.4.tgz",
+ "integrity": "sha512-mAbxw60d4ffQ4qmRYfkO1xzRBPUEf/72Dgo3qqea0J66nIKuDTLEqQt0ku++SDFlMGMnB6uKDnEG1xD/TDse4Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-core": "5.74.4"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19"
}
},
"node_modules/@types/json5": {
@@ -336,12 +881,13 @@
"dev": true
},
"node_modules/@types/node": {
- "version": "20.9.4",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.4.tgz",
- "integrity": "sha512-wmyg8HUhcn6ACjsn8oKYjkN/zUzQeNtMy44weTJSM6p4MMzEOuKbA3OjJ267uPCOW7Xex9dyrNTful8XTQYoDA==",
+ "version": "20.17.30",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.30.tgz",
+ "integrity": "sha512-7zf4YyHA+jvBNfVrk2Gtvs6x7E8V+YDW05bNfG2XkWDJfYRXrTiP/DsB2zSYTaHX0bGIujTBQdMVAhb+j7mwpg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "undici-types": "~5.26.4"
+ "undici-types": "~6.19.2"
}
},
"node_modules/@types/prop-types": {
@@ -351,10 +897,11 @@
"dev": true
},
"node_modules/@types/react": {
- "version": "18.2.38",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.38.tgz",
- "integrity": "sha512-cBBXHzuPtQK6wNthuVMV6IjHAFkdl/FOPFIlkd81/Cd1+IqkHu/A+w4g43kaQQoYHik/ruaQBDL72HyCy1vuMw==",
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.0.tgz",
+ "integrity": "sha512-0FLj93y5USLHdnhIhABk83rm8XEGA7kH3cr+YUlvxoUGp1xNt/DINUMvqPxLyOQMzLmZe8i4RTHbvb8MC7NmrA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@@ -362,10 +909,11 @@
}
},
"node_modules/@types/react-dom": {
- "version": "18.2.17",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.17.tgz",
- "integrity": "sha512-rvrT/M7Df5eykWFxn6MYt5Pem/Dbyc1N8Y0S9Mrkw2WFCRiqUgw9P7ul2NpwsXCSM1DVdENzdG9J5SreqfAIWg==",
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.0.tgz",
+ "integrity": "sha512-8yQrvS6sMpSwIovhPOwfyNf2Wz6v/B62LFSVYQ85+Rq3tLsBIG7rP5geMxaijTUxSkrO6RzN/IRuIAADYQsleA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@types/react": "*"
}
@@ -545,6 +1093,34 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -705,6 +1281,50 @@
"has-symbols": "^1.0.3"
}
},
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.4.21",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
+ "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.24.4",
+ "caniuse-lite": "^1.0.30001702",
+ "fraction.js": "^4.3.7",
+ "normalize-range": "^0.1.2",
+ "picocolors": "^1.1.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
"node_modules/available-typed-arrays": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
@@ -726,6 +1346,17 @@
"node": ">=4"
}
},
+ "node_modules/axios": {
+ "version": "1.8.4",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
+ "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
"node_modules/axobject-query": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz",
@@ -741,6 +1372,19 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -752,17 +1396,51 @@
}
},
"node_modules/braces": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
- "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "fill-range": "^7.0.1"
+ "fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
}
},
+ "node_modules/browserslist": {
+ "version": "4.24.4",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
+ "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001688",
+ "electron-to-chromium": "^1.5.73",
+ "node-releases": "^2.0.19",
+ "update-browserslist-db": "^1.1.1"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -788,6 +1466,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -797,10 +1488,20 @@
"node": ">=6"
}
},
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/caniuse-lite": {
- "version": "1.0.30001564",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001564.tgz",
- "integrity": "sha512-DqAOf+rhof+6GVx1y+xzbFPeOumfQnhYzVnZD6LAXijR77yPtm9mfOcqOnT3mpnJiZVT+kwLAFnRlZcIz+c6bg==",
+ "version": "1.0.30001715",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz",
+ "integrity": "sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==",
"funding": [
{
"type": "opencollective",
@@ -814,7 +1515,8 @@
"type": "github",
"url": "https://github.com/sponsors/ai"
}
- ]
+ ],
+ "license": "CC-BY-4.0"
},
"node_modules/chalk": {
"version": "4.1.2",
@@ -832,16 +1534,78 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
- "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
+ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
+ "license": "MIT"
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/color": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
+ "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "color-convert": "^2.0.1",
+ "color-string": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=12.5.0"
+ }
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"color-name": "~1.1.4"
},
@@ -853,7 +1617,40 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
+ "devOptional": true
+ },
+ "node_modules/color-string": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.2"
+ }
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
},
"node_modules/concat-map": {
"version": "0.0.1",
@@ -862,10 +1659,11 @@
"dev": true
},
"node_modules/cross-spawn": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
- "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
@@ -875,6 +1673,19 @@
"node": ">= 8"
}
},
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/csstype": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
@@ -941,6 +1752,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@@ -950,6 +1770,23 @@
"node": ">=6"
}
},
+ "node_modules/detect-libc": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
+ "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -962,6 +1799,13 @@
"node": ">=8"
}
},
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -974,6 +1818,34 @@
"node": ">=6.0.0"
}
},
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.139",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.139.tgz",
+ "integrity": "sha512-GGnRYOTdN5LYpwbIr0rwP/ZHOQSvAF6TG0LSzp28uCBb9JiXHJGmaaKw29qjNJc5bGnnp6kXJqRnGMQoELwi5w==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@@ -1046,6 +1918,24 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/es-iterator-helpers": {
"version": "1.0.15",
"resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz",
@@ -1068,15 +1958,28 @@
"safe-array-concat": "^1.0.1"
}
},
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/es-set-tostringtag": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz",
- "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==",
- "dev": true,
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
"dependencies": {
- "get-intrinsic": "^1.2.2",
- "has-tostringtag": "^1.0.0",
- "hasown": "^2.0.0"
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
@@ -1108,6 +2011,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -1598,10 +2511,11 @@
}
},
"node_modules/fill-range": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
- "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -1645,13 +2559,106 @@
"integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==",
"dev": true
},
- "node_modules/for-each": {
- "version": "0.3.3",
- "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
- "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
+ "node_modules/follow-redirects": {
+ "version": "1.15.9",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/for-each": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
+ "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
+ "dev": true,
+ "dependencies": {
+ "is-callable": "^1.1.3"
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
+ "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fraction.js": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
+ "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
"dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "patreon",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/framer-motion": {
+ "version": "12.7.4",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.7.4.tgz",
+ "integrity": "sha512-jX0bPsTmU0oPZTYz/dVyD0dmOyEOEJvdn0TaZBE5I8g2GvVnnQnW9f65cJnoVfUkY3WZWNXGXnPbVA9YnaIfVA==",
+ "license": "MIT",
"dependencies": {
- "is-callable": "^1.1.3"
+ "motion-dom": "^12.7.4",
+ "motion-utils": "^12.7.2",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
}
},
"node_modules/fs.realpath": {
@@ -1660,11 +2667,25 @@
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true
},
+ "node_modules/fsevents": {
+ "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,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -1697,20 +2718,42 @@
}
},
"node_modules/get-intrinsic": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz",
- "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==",
- "dev": true,
- "dependencies": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
- "has-proto": "^1.0.1",
- "has-symbols": "^1.0.3",
- "hasown": "^2.0.0"
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/get-symbol-description": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
@@ -1771,11 +2814,6 @@
"node": ">=10.13.0"
}
},
- "node_modules/glob-to-regexp": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
- "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="
- },
"node_modules/globals": {
"version": "13.23.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz",
@@ -1827,12 +2865,12 @@
}
},
"node_modules/gopd": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
- "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
- "dev": true,
- "dependencies": {
- "get-intrinsic": "^1.1.3"
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -1841,7 +2879,8 @@
"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=="
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true
},
"node_modules/graphemer": {
"version": "1.4.0",
@@ -1892,10 +2931,10 @@
}
},
"node_modules/has-symbols": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
- "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
- "dev": true,
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
"engines": {
"node": ">= 0.4"
},
@@ -1904,12 +2943,12 @@
}
},
"node_modules/has-tostringtag": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
- "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
- "dev": true,
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
"dependencies": {
- "has-symbols": "^1.0.2"
+ "has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
@@ -1919,10 +2958,10 @@
}
},
"node_modules/hasown": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
- "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
- "dev": true,
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
@@ -2008,6 +3047,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-arrayish": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
+ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/is-async-function": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz",
@@ -2035,6 +3081,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-boolean-object": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
@@ -2111,6 +3170,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-generator-function": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
@@ -2164,6 +3233,7 @@
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=0.12.0"
}
@@ -2333,6 +3403,32 @@
"set-function-name": "^2.0.1"
}
},
+ "node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "1.21.7",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
+ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "bin/jiti.js"
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -2435,6 +3531,26 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/lilconfig": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -2468,15 +3584,19 @@
}
},
"node_modules/lru-cache": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
- "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true,
- "dependencies": {
- "yallist": "^4.0.0"
- },
+ "license": "ISC"
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
"engines": {
- "node": ">=10"
+ "node": ">= 0.4"
}
},
"node_modules/merge2": {
@@ -2489,18 +3609,40 @@
}
},
"node_modules/micromatch": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
- "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "braces": "^3.0.2",
+ "braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=8.6"
}
},
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -2522,22 +3664,60 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/motion-dom": {
+ "version": "12.7.4",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.7.4.tgz",
+ "integrity": "sha512-1ZUHAoSUMMxP6jPqyxlk9XUfb6NxMsnWPnH2YGhrOhTURLcXWbETi6eemoKb60Pe32NVJYduL4B62VQSO5Jq8Q==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-utils": "^12.7.2"
+ }
+ },
+ "node_modules/motion-utils": {
+ "version": "12.7.2",
+ "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.7.2.tgz",
+ "integrity": "sha512-XhZwqctxyJs89oX00zn3OGCuIIpVevbTa+u82usWBC6pSHUd2AoNWiYa7Du8tJxJy9TFbZ82pcn5t7NOm1PHAw==",
+ "license": "MIT"
+ },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
"node_modules/nanoid": {
- "version": "3.3.7",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
- "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@@ -2552,50 +3732,114 @@
"dev": true
},
"node_modules/next": {
- "version": "13.5.6",
- "resolved": "https://registry.npmjs.org/next/-/next-13.5.6.tgz",
- "integrity": "sha512-Y2wTcTbO4WwEsVb4A8VSnOsG1I9ok+h74q0ZdxkwM3EODqrs4pasq7O0iUxbcS9VtWMicG7f3+HAj0r1+NtKSw==",
- "dependencies": {
- "@next/env": "13.5.6",
- "@swc/helpers": "0.5.2",
+ "version": "15.3.1",
+ "resolved": "https://registry.npmjs.org/next/-/next-15.3.1.tgz",
+ "integrity": "sha512-8+dDV0xNLOgHlyBxP1GwHGVaNXsmp+2NhZEYrXr24GWLHtt27YrBPbPuHvzlhi7kZNYjeJNR93IF5zfFu5UL0g==",
+ "license": "MIT",
+ "dependencies": {
+ "@next/env": "15.3.1",
+ "@swc/counter": "0.1.3",
+ "@swc/helpers": "0.5.15",
"busboy": "1.6.0",
- "caniuse-lite": "^1.0.30001406",
+ "caniuse-lite": "^1.0.30001579",
"postcss": "8.4.31",
- "styled-jsx": "5.1.1",
- "watchpack": "2.4.0"
+ "styled-jsx": "5.1.6"
},
"bin": {
"next": "dist/bin/next"
},
"engines": {
- "node": ">=16.14.0"
+ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
},
"optionalDependencies": {
- "@next/swc-darwin-arm64": "13.5.6",
- "@next/swc-darwin-x64": "13.5.6",
- "@next/swc-linux-arm64-gnu": "13.5.6",
- "@next/swc-linux-arm64-musl": "13.5.6",
- "@next/swc-linux-x64-gnu": "13.5.6",
- "@next/swc-linux-x64-musl": "13.5.6",
- "@next/swc-win32-arm64-msvc": "13.5.6",
- "@next/swc-win32-ia32-msvc": "13.5.6",
- "@next/swc-win32-x64-msvc": "13.5.6"
+ "@next/swc-darwin-arm64": "15.3.1",
+ "@next/swc-darwin-x64": "15.3.1",
+ "@next/swc-linux-arm64-gnu": "15.3.1",
+ "@next/swc-linux-arm64-musl": "15.3.1",
+ "@next/swc-linux-x64-gnu": "15.3.1",
+ "@next/swc-linux-x64-musl": "15.3.1",
+ "@next/swc-win32-arm64-msvc": "15.3.1",
+ "@next/swc-win32-x64-msvc": "15.3.1",
+ "sharp": "^0.34.1"
},
"peerDependencies": {
"@opentelemetry/api": "^1.1.0",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
+ "@playwright/test": "^1.41.2",
+ "babel-plugin-react-compiler": "*",
+ "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
+ "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
"sass": "^1.3.0"
},
"peerDependenciesMeta": {
"@opentelemetry/api": {
"optional": true
},
+ "@playwright/test": {
+ "optional": true
+ },
+ "babel-plugin-react-compiler": {
+ "optional": true
+ },
"sass": {
"optional": true
}
}
},
+ "node_modules/next/node_modules/postcss": {
+ "version": "8.4.31",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.6",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.19",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -2605,6 +3849,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/object-inspect": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
@@ -2770,6 +4024,13 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0"
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -2815,6 +4076,23 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@@ -2825,9 +4103,10 @@
}
},
"node_modules/picocolors": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
- "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.1",
@@ -2841,10 +4120,31 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/postcss": {
- "version": "8.4.31",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
- "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
+ "version": "8.5.3",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
+ "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
+ "dev": true,
"funding": [
{
"type": "opencollective",
@@ -2859,15 +4159,101 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.8",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-import": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-js": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
+ "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "postcss-selector-parser": "^6.1.1"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
+ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
- "nanoid": "^3.3.6",
- "picocolors": "^1.0.0",
- "source-map-js": "^1.0.2"
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
},
"engines": {
- "node": "^10 || ^12 || >=14"
+ "node": ">=4"
}
},
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -2888,6 +4274,12 @@
"react-is": "^16.13.1"
}
},
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -2921,6 +4313,7 @@
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+ "license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
},
@@ -2932,6 +4325,7 @@
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+ "license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.0"
@@ -2946,6 +4340,29 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
},
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
"node_modules/reflect.getprototypeof": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz",
@@ -3113,13 +4530,11 @@
}
},
"node_modules/semver": {
- "version": "7.5.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
- "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
- "dev": true,
- "dependencies": {
- "lru-cache": "^6.0.0"
- },
+ "version": "7.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
+ "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
+ "devOptional": true,
+ "license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
@@ -3156,6 +4571,47 @@
"node": ">= 0.4"
}
},
+ "node_modules/sharp": {
+ "version": "0.34.1",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.1.tgz",
+ "integrity": "sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "color": "^4.2.3",
+ "detect-libc": "^2.0.3",
+ "semver": "^7.7.1"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-darwin-arm64": "0.34.1",
+ "@img/sharp-darwin-x64": "0.34.1",
+ "@img/sharp-libvips-darwin-arm64": "1.1.0",
+ "@img/sharp-libvips-darwin-x64": "1.1.0",
+ "@img/sharp-libvips-linux-arm": "1.1.0",
+ "@img/sharp-libvips-linux-arm64": "1.1.0",
+ "@img/sharp-libvips-linux-ppc64": "1.1.0",
+ "@img/sharp-libvips-linux-s390x": "1.1.0",
+ "@img/sharp-libvips-linux-x64": "1.1.0",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.1.0",
+ "@img/sharp-libvips-linuxmusl-x64": "1.1.0",
+ "@img/sharp-linux-arm": "0.34.1",
+ "@img/sharp-linux-arm64": "0.34.1",
+ "@img/sharp-linux-s390x": "0.34.1",
+ "@img/sharp-linux-x64": "0.34.1",
+ "@img/sharp-linuxmusl-arm64": "0.34.1",
+ "@img/sharp-linuxmusl-x64": "0.34.1",
+ "@img/sharp-wasm32": "0.34.1",
+ "@img/sharp-win32-ia32": "0.34.1",
+ "@img/sharp-win32-x64": "0.34.1"
+ }
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -3191,6 +4647,29 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/simple-swizzle": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
+ "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "is-arrayish": "^0.3.1"
+ }
+ },
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@@ -3201,9 +4680,10 @@
}
},
"node_modules/source-map-js": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
- "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "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==",
+ "license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
@@ -3216,6 +4696,76 @@
"node": ">=10.0.0"
}
},
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/string-width/node_modules/ansi-regex": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/string-width/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
"node_modules/string.prototype.matchall": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz",
@@ -3293,6 +4843,20 @@
"node": ">=8"
}
},
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
@@ -3315,9 +4879,10 @@
}
},
"node_modules/styled-jsx": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
- "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==",
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
+ "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
+ "license": "MIT",
"dependencies": {
"client-only": "0.0.1"
},
@@ -3325,7 +4890,7 @@
"node": ">= 12.0.0"
},
"peerDependencies": {
- "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0"
+ "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
},
"peerDependenciesMeta": {
"@babel/core": {
@@ -3336,6 +4901,76 @@
}
}
},
+ "node_modules/sucrase": {
+ "version": "3.35.0",
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
+ "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "glob": "^10.3.10",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/sucrase/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/sucrase/node_modules/glob": {
+ "version": "10.4.5",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
+ "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/sucrase/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -3360,6 +4995,80 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/tailwindcss": {
+ "version": "3.4.17",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
+ "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.6.0",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.3.2",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.21.6",
+ "lilconfig": "^3.1.3",
+ "micromatch": "^4.0.8",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.1.1",
+ "postcss": "^8.4.47",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.2",
+ "postcss-nested": "^6.2.0",
+ "postcss-selector-parser": "^6.1.2",
+ "resolve": "^1.22.8",
+ "sucrase": "^3.35.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tailwindcss/node_modules/postcss-load-config": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
+ "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "lilconfig": "^3.0.0",
+ "yaml": "^2.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ },
+ "peerDependencies": {
+ "postcss": ">=8.0.9",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "postcss": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
"node_modules/tapable": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
@@ -3375,11 +5084,35 @@
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true
},
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
@@ -3399,6 +5132,13 @@
"typescript": ">=4.2.0"
}
},
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
"node_modules/tsconfig-paths": {
"version": "3.14.2",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz",
@@ -3412,9 +5152,10 @@
}
},
"node_modules/tslib": {
- "version": "2.6.2",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
- "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
},
"node_modules/type-check": {
"version": "0.4.0",
@@ -3534,10 +5275,42 @@
}
},
"node_modules/undici-types": {
- "version": "5.26.5",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
- "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
- "dev": true
+ "version": "6.19.8",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
+ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
},
"node_modules/uri-js": {
"version": "4.4.1",
@@ -3548,17 +5321,12 @@
"punycode": "^2.1.0"
}
},
- "node_modules/watchpack": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
- "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==",
- "dependencies": {
- "glob-to-regexp": "^0.4.1",
- "graceful-fs": "^4.1.2"
- },
- "engines": {
- "node": ">=10.13.0"
- }
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/which": {
"version": "2.0.2",
@@ -3651,17 +5419,125 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-regex": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
- "node_modules/yallist": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true
+ "node_modules/yaml": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz",
+ "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
},
"node_modules/yocto-queue": {
"version": "0.1.0",
diff --git a/package.json b/package.json
index 1ce24924..d53c60e7 100644
--- a/package.json
+++ b/package.json
@@ -9,16 +9,23 @@
"lint": "next lint"
},
"dependencies": {
- "react": "^18",
- "react-dom": "^18",
- "next": "13.5.6"
+ "@tanstack/react-query": "^5.74.4",
+ "axios": "^1.8.4",
+ "clsx": "^2.1.1",
+ "framer-motion": "^12.7.4",
+ "next": "^15.3.1",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
},
"devDependencies": {
- "typescript": "^5",
- "@types/node": "^20",
- "@types/react": "^18",
- "@types/react-dom": "^18",
+ "@types/node": "^20.17.30",
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
+ "autoprefixer": "^10.4.21",
"eslint": "^8",
- "eslint-config-next": "13.5.6"
+ "eslint-config-next": "13.5.6",
+ "postcss": "^8.5.3",
+ "tailwindcss": "^3.4.17",
+ "typescript": "^5"
}
}
diff --git a/pages/_app.tsx b/pages/_app.tsx
deleted file mode 100644
index 021681f4..00000000
--- a/pages/_app.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import '@/styles/globals.css'
-import type { AppProps } from 'next/app'
-
-export default function App({ Component, pageProps }: AppProps) {
- return
-}
diff --git a/pages/_document.tsx b/pages/_document.tsx
deleted file mode 100644
index 54e8bf3e..00000000
--- a/pages/_document.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Html, Head, Main, NextScript } from 'next/document'
-
-export default function Document() {
- return (
-
-
-
-
-
-
- )
-}
diff --git a/pages/api/hello.ts b/pages/api/hello.ts
deleted file mode 100644
index f8bcc7e5..00000000
--- a/pages/api/hello.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
-import type { NextApiRequest, NextApiResponse } from 'next'
-
-type Data = {
- name: string
-}
-
-export default function handler(
- req: NextApiRequest,
- res: NextApiResponse
-) {
- res.status(200).json({ name: 'John Doe' })
-}
diff --git a/pages/index.tsx b/pages/index.tsx
deleted file mode 100644
index 02c4dee0..00000000
--- a/pages/index.tsx
+++ /dev/null
@@ -1,114 +0,0 @@
-import Head from 'next/head'
-import Image from 'next/image'
-import { Inter } from 'next/font/google'
-import styles from '@/styles/Home.module.css'
-
-const inter = Inter({ subsets: ['latin'] })
-
-export default function Home() {
- return (
- <>
-
- Create Next App
-
-
-
-
-
-
-
- Get started by editing
- pages/index.tsx
-
-
-
-
-
-
-
-
-
-
- >
- )
-}
diff --git a/postcss.config.js b/postcss.config.js
new file mode 100644
index 00000000..33ad091d
--- /dev/null
+++ b/postcss.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/public/assets/6176.png b/public/assets/6176.png
new file mode 100644
index 00000000..16a36b74
Binary files /dev/null and b/public/assets/6176.png differ
diff --git a/public/assets/Editor_Icon.png b/public/assets/Editor_Icon.png
new file mode 100644
index 00000000..c4ffa747
Binary files /dev/null and b/public/assets/Editor_Icon.png differ
diff --git a/public/assets/Group_2.png b/public/assets/Group_2.png
new file mode 100644
index 00000000..0b8595f8
Binary files /dev/null and b/public/assets/Group_2.png differ
diff --git a/public/assets/Group_3.png b/public/assets/Group_3.png
new file mode 100644
index 00000000..393b7754
Binary files /dev/null and b/public/assets/Group_3.png differ
diff --git a/public/assets/Img_home_01.png b/public/assets/Img_home_01.png
new file mode 100644
index 00000000..b6495aa8
Binary files /dev/null and b/public/assets/Img_home_01.png differ
diff --git a/public/assets/Img_home_02.png b/public/assets/Img_home_02.png
new file mode 100644
index 00000000..eda9ee15
Binary files /dev/null and b/public/assets/Img_home_02.png differ
diff --git a/public/assets/Img_home_03.png b/public/assets/Img_home_03.png
new file mode 100644
index 00000000..ded1b586
Binary files /dev/null and b/public/assets/Img_home_03.png differ
diff --git a/public/assets/Img_home_bottom.png b/public/assets/Img_home_bottom.png
new file mode 100644
index 00000000..f8b16d55
Binary files /dev/null and b/public/assets/Img_home_bottom.png differ
diff --git a/public/assets/Img_home_top.png b/public/assets/Img_home_top.png
new file mode 100644
index 00000000..dcb2304e
Binary files /dev/null and b/public/assets/Img_home_top.png differ
diff --git a/public/assets/Vector.png b/public/assets/Vector.png
new file mode 100644
index 00000000..fd11075b
Binary files /dev/null and b/public/assets/Vector.png differ
diff --git a/public/assets/eye_1.svg b/public/assets/eye_1.svg
new file mode 100644
index 00000000..43a5af17
--- /dev/null
+++ b/public/assets/eye_1.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/assets/eye_2.svg b/public/assets/eye_2.svg
new file mode 100644
index 00000000..43cfd033
--- /dev/null
+++ b/public/assets/eye_2.svg
@@ -0,0 +1,11 @@
+
diff --git a/public/assets/gg_icon.png b/public/assets/gg_icon.png
new file mode 100644
index 00000000..f75dc761
Binary files /dev/null and b/public/assets/gg_icon.png differ
diff --git a/public/assets/google.png b/public/assets/google.png
new file mode 100644
index 00000000..0c4b3886
Binary files /dev/null and b/public/assets/google.png differ
diff --git a/public/assets/heart_1.svg b/public/assets/heart_1.svg
new file mode 100644
index 00000000..7b994fbe
--- /dev/null
+++ b/public/assets/heart_1.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/assets/heart_2.svg b/public/assets/heart_2.svg
new file mode 100644
index 00000000..a896bef5
--- /dev/null
+++ b/public/assets/heart_2.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/assets/ic_3_01.png b/public/assets/ic_3_01.png
new file mode 100644
index 00000000..4c16d540
Binary files /dev/null and b/public/assets/ic_3_01.png differ
diff --git a/public/assets/ic_X.svg b/public/assets/ic_X.svg
new file mode 100644
index 00000000..f6674f7f
--- /dev/null
+++ b/public/assets/ic_X.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/assets/ic_arrow_down.svg b/public/assets/ic_arrow_down.svg
new file mode 100644
index 00000000..8308690f
--- /dev/null
+++ b/public/assets/ic_arrow_down.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/assets/ic_back.svg b/public/assets/ic_back.svg
new file mode 100644
index 00000000..8db5377e
--- /dev/null
+++ b/public/assets/ic_back.svg
@@ -0,0 +1,4 @@
+
diff --git a/public/assets/ic_check.svg b/public/assets/ic_check.svg
new file mode 100644
index 00000000..baa4aa3b
--- /dev/null
+++ b/public/assets/ic_check.svg
@@ -0,0 +1,4 @@
+
diff --git a/public/assets/ic_facebook.svg b/public/assets/ic_facebook.svg
new file mode 100644
index 00000000..b9c9d493
--- /dev/null
+++ b/public/assets/ic_facebook.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/assets/ic_instagram.svg b/public/assets/ic_instagram.svg
new file mode 100644
index 00000000..0b9337b0
--- /dev/null
+++ b/public/assets/ic_instagram.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/assets/ic_kebab.svg b/public/assets/ic_kebab.svg
new file mode 100644
index 00000000..dd7ed7f5
--- /dev/null
+++ b/public/assets/ic_kebab.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/assets/ic_plus.svg b/public/assets/ic_plus.svg
new file mode 100644
index 00000000..5bb9abf5
--- /dev/null
+++ b/public/assets/ic_plus.svg
@@ -0,0 +1,4 @@
+
diff --git a/public/assets/ic_search.svg b/public/assets/ic_search.svg
new file mode 100644
index 00000000..52241e6d
--- /dev/null
+++ b/public/assets/ic_search.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/assets/ic_search_darker.svg b/public/assets/ic_search_darker.svg
new file mode 100644
index 00000000..6750ee04
--- /dev/null
+++ b/public/assets/ic_search_darker.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/assets/ic_sort.svg b/public/assets/ic_sort.svg
new file mode 100644
index 00000000..657b44f9
--- /dev/null
+++ b/public/assets/ic_sort.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/assets/ic_twitter.svg b/public/assets/ic_twitter.svg
new file mode 100644
index 00000000..14a6069a
--- /dev/null
+++ b/public/assets/ic_twitter.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/assets/ic_youtube.svg b/public/assets/ic_youtube.svg
new file mode 100644
index 00000000..699b5380
--- /dev/null
+++ b/public/assets/ic_youtube.svg
@@ -0,0 +1,10 @@
+
diff --git a/public/assets/image_01.png b/public/assets/image_01.png
new file mode 100644
index 00000000..2e6b3845
Binary files /dev/null and b/public/assets/image_01.png differ
diff --git a/public/assets/image_02.png b/public/assets/image_02.png
new file mode 100644
index 00000000..89cf79cc
Binary files /dev/null and b/public/assets/image_02.png differ
diff --git a/public/assets/image_03.png b/public/assets/image_03.png
new file mode 100644
index 00000000..8e5b495a
Binary files /dev/null and b/public/assets/image_03.png differ
diff --git a/public/assets/image_04.png b/public/assets/image_04.png
new file mode 100644
index 00000000..8a860c5b
Binary files /dev/null and b/public/assets/image_04.png differ
diff --git a/public/assets/image_05.png b/public/assets/image_05.png
new file mode 100644
index 00000000..a84b2160
Binary files /dev/null and b/public/assets/image_05.png differ
diff --git a/public/assets/img/Img_inquiry_empty.png b/public/assets/img/Img_inquiry_empty.png
new file mode 100644
index 00000000..e947ab0b
Binary files /dev/null and b/public/assets/img/Img_inquiry_empty.png differ
diff --git a/public/assets/img/Img_inquiry_empty_2x.png b/public/assets/img/Img_inquiry_empty_2x.png
new file mode 100644
index 00000000..7b7510c6
Binary files /dev/null and b/public/assets/img/Img_inquiry_empty_2x.png differ
diff --git a/public/assets/img/img_1.jpg b/public/assets/img/img_1.jpg
new file mode 100644
index 00000000..a038f9b7
Binary files /dev/null and b/public/assets/img/img_1.jpg differ
diff --git a/public/assets/img/img_2.jpg b/public/assets/img/img_2.jpg
new file mode 100644
index 00000000..c03b4e20
Binary files /dev/null and b/public/assets/img/img_2.jpg differ
diff --git a/public/assets/img/img_3.jpg b/public/assets/img/img_3.jpg
new file mode 100644
index 00000000..15bde269
Binary files /dev/null and b/public/assets/img/img_3.jpg differ
diff --git a/public/assets/img/img_4.jpg b/public/assets/img/img_4.jpg
new file mode 100644
index 00000000..5eb5f123
Binary files /dev/null and b/public/assets/img/img_4.jpg differ
diff --git a/public/assets/img/img_default.png b/public/assets/img/img_default.png
new file mode 100644
index 00000000..7b3ca0b2
Binary files /dev/null and b/public/assets/img/img_default.png differ
diff --git a/public/assets/img/img_default.svg b/public/assets/img/img_default.svg
new file mode 100644
index 00000000..67a2f224
--- /dev/null
+++ b/public/assets/img/img_default.svg
@@ -0,0 +1,16 @@
+
diff --git a/public/assets/img/img_default_2x.png b/public/assets/img/img_default_2x.png
new file mode 100644
index 00000000..2c07c28d
Binary files /dev/null and b/public/assets/img/img_default_2x.png differ
diff --git a/public/assets/img/img_default_3x.png b/public/assets/img/img_default_3x.png
new file mode 100644
index 00000000..879255da
Binary files /dev/null and b/public/assets/img/img_default_3x.png differ
diff --git a/public/assets/img_badge.svg b/public/assets/img_badge.svg
new file mode 100644
index 00000000..8470f48f
--- /dev/null
+++ b/public/assets/img_badge.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/assets/kakao_icon.png b/public/assets/kakao_icon.png
new file mode 100644
index 00000000..bd767800
Binary files /dev/null and b/public/assets/kakao_icon.png differ
diff --git a/public/assets/logo_01.svg b/public/assets/logo_01.svg
new file mode 100644
index 00000000..f290e713
--- /dev/null
+++ b/public/assets/logo_01.svg
@@ -0,0 +1,14 @@
+
diff --git a/public/assets/logo_02.ico b/public/assets/logo_02.ico
new file mode 100644
index 00000000..65e81f0f
Binary files /dev/null and b/public/assets/logo_02.ico differ
diff --git a/public/assets/logo_03.png b/public/assets/logo_03.png
new file mode 100644
index 00000000..66bb5d0e
Binary files /dev/null and b/public/assets/logo_03.png differ
diff --git a/public/assets/logo_03.svg b/public/assets/logo_03.svg
new file mode 100644
index 00000000..43590bb7
--- /dev/null
+++ b/public/assets/logo_03.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/assets/logo_04.png b/public/assets/logo_04.png
new file mode 100644
index 00000000..8c27efed
Binary files /dev/null and b/public/assets/logo_04.png differ
diff --git a/public/assets/status_active-1.svg b/public/assets/status_active-1.svg
new file mode 100644
index 00000000..0ad718ef
--- /dev/null
+++ b/public/assets/status_active-1.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/assets/status_active.svg b/public/assets/status_active.svg
new file mode 100644
index 00000000..4b110c20
--- /dev/null
+++ b/public/assets/status_active.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/assets/status_inactive-1.svg b/public/assets/status_inactive-1.svg
new file mode 100644
index 00000000..764302b6
--- /dev/null
+++ b/public/assets/status_inactive-1.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/assets/status_inactive.svg b/public/assets/status_inactive.svg
new file mode 100644
index 00000000..1daeca5c
--- /dev/null
+++ b/public/assets/status_inactive.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/assets/status_white-1.svg b/public/assets/status_white-1.svg
new file mode 100644
index 00000000..c3d3f7d4
--- /dev/null
+++ b/public/assets/status_white-1.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/assets/status_white.svg b/public/assets/status_white.svg
new file mode 100644
index 00000000..3ec2fc77
--- /dev/null
+++ b/public/assets/status_white.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/favicon.ico b/public/favicon.ico
deleted file mode 100644
index 718d6fea..00000000
Binary files a/public/favicon.ico and /dev/null differ
diff --git a/public/next.svg b/public/next.svg
deleted file mode 100644
index 5174b28c..00000000
--- a/public/next.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/public/vercel.svg b/public/vercel.svg
deleted file mode 100644
index d2f84222..00000000
--- a/public/vercel.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/app/Privacy/page.tsx b/src/app/Privacy/page.tsx
new file mode 100644
index 00000000..053ca9f0
--- /dev/null
+++ b/src/app/Privacy/page.tsx
@@ -0,0 +1,12 @@
+'use client';
+import React from 'react';
+
+function Privacy() {
+ return (
+ <>
+ privacy
+ >
+ );
+}
+
+export default Privacy;
\ No newline at end of file
diff --git a/src/app/boards/page.tsx b/src/app/boards/page.tsx
new file mode 100644
index 00000000..69b3cc18
--- /dev/null
+++ b/src/app/boards/page.tsx
@@ -0,0 +1,14 @@
+
+
+
+import React from 'react';
+
+function Boards() {
+ return (
+ <>
+ Boards
+ >
+ );
+}
+
+export default Boards;
\ No newline at end of file
diff --git a/src/app/faq/page.tsx b/src/app/faq/page.tsx
new file mode 100644
index 00000000..ae9957dd
--- /dev/null
+++ b/src/app/faq/page.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+function Faq() {
+ return (
+ <>
+ faq
+ >
+ );
+}
+
+export default Faq;
\ No newline at end of file
diff --git a/src/app/global.css b/src/app/global.css
new file mode 100644
index 00000000..27bab466
--- /dev/null
+++ b/src/app/global.css
@@ -0,0 +1,181 @@
+@font-face {
+ font-family: "Pretendard-Regular";
+ src: url("https://fastly.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Regular.woff")
+ format("woff");
+ font-weight: 400;
+ font-style: normal;
+}
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+* {
+ box-sizing: border-box;
+ margin: 0;
+}
+
+body {
+ font-family: "Pretendard-Regular";
+ font-size: 16px;
+ min-width: 355px;
+ min-height: 100vh;
+ font-weight: 400;
+ overflow-x: hidden;
+ color: var(--Secondary);
+}
+a {
+ text-decoration: none;
+}
+
+input,
+textarea,
+textarea::placeholder {
+ font-size: 16px;
+ font-weight: 400;
+ font-family: "Pretendard-Regular";
+}
+input::placeholder,
+textarea::placeholder {
+ color: #9ca3af;
+}
+
+input:hover,
+input:focus {
+ outline: 1px solid #3692ff;
+}
+input::placeholder {
+ color: #9ca3af;
+}
+input[type="submit"] {
+ outline: 0;
+}
+
+ul {
+ list-style: none;
+ padding-left: 0;
+}
+
+:root {
+ --Secondary: #374151;
+ --Secondary_200: #e5e7eb;
+
+ --Primary: #3692ff;
+ --Primary_200: #1967d6;
+ --Primary_300: #1251aa;
+ --Cool_Gray_900: #111827;
+ --Cool_Gray_800: #1f2937;
+ --Cool_Gray_700: #374151;
+ --Cool_Gray_600: #4b5563;
+ --Cool_Gray_500: #6b7280;
+ --Cool_Gray_400: #9ca3af;
+ --Cool_Gray_200: #e5e7eb;
+ --Cool_Gray_100: #f3f4f6;
+ --Cool_Gray_50: #f9fafb;
+ --Error_red_50: #f74747;
+
+ --padding-24: 24px;
+ --padding-16: 16px;
+
+ --min-width: 325px;
+ --max-width: 1200px;
+}
+
+/* Background color classes */
+.bg-secondary {
+ background-color: var(--Secondary);
+}
+.bg-secondary-200 {
+ background-color: var(--Secondary_200);
+}
+
+.bg-primary {
+ background-color: var(--Primary);
+}
+.bg-primary-200 {
+ background-color: var(--Primary_200);
+}
+.bg-primary-300 {
+ background-color: var(--Primary_300);
+}
+
+.bg-gray-900 {
+ background-color: var(--Cool_Gray_900);
+}
+.bg-gray-800 {
+ background-color: var(--Cool_Gray_800);
+}
+.bg-gray-700 {
+ background-color: var(--Cool_Gray_700);
+}
+.bg-gray-600 {
+ background-color: var(--Cool_Gray_600);
+}
+.bg-gray-500 {
+ background-color: var(--Cool_Gray_500);
+}
+.bg-gray-400 {
+ background-color: var(--Cool_Gray_400);
+}
+.bg-gray-200 {
+ background-color: var(--Cool_Gray_200);
+}
+.bg-gray-100 {
+ background-color: var(--Cool_Gray_100);
+}
+.bg-gray-50 {
+ background-color: var(--Cool_Gray_50);
+}
+
+.bg-error-red {
+ background-color: var(--Error_red_50);
+}
+
+/* Text color classes */
+.text-secondary {
+ color: var(--Secondary);
+}
+.text-secondary-200 {
+ color: var(--Secondary_200);
+}
+
+.text-primary {
+ color: var(--Primary);
+}
+.text-primary-200 {
+ color: var(--Primary_200);
+}
+.text-primary-300 {
+ color: var(--Primary_300);
+}
+
+.text-gray-900 {
+ color: var(--Cool_Gray_900);
+}
+.text-gray-800 {
+ color: var(--Cool_Gray_800);
+}
+.text-gray-700 {
+ color: var(--Cool_Gray_700);
+}
+.text-gray-600 {
+ color: var(--Cool_Gray_600);
+}
+.text-gray-500 {
+ color: var(--Cool_Gray_500);
+}
+.text-gray-400 {
+ color: var(--Cool_Gray_400);
+}
+.text-gray-200 {
+ color: var(--Cool_Gray_200);
+}
+.text-gray-100 {
+ color: var(--Cool_Gray_100);
+}
+.text-gray-50 {
+ color: var(--Cool_Gray_50);
+}
+
+.text-error-red {
+ color: var(--Error_red_50);
+}
diff --git a/src/app/items/ItemsBox.module.css b/src/app/items/ItemsBox.module.css
new file mode 100644
index 00000000..4f60c230
--- /dev/null
+++ b/src/app/items/ItemsBox.module.css
@@ -0,0 +1,84 @@
+.title {
+ font-size: 20px;
+ line-height: 42px;
+ font-weight: 700;
+}
+.bestProdListTitle {
+ margin-top: 24px;
+ font-size: 20px;
+ margin-bottom: 16px;
+ font-weight: 700;
+}
+.prodListTitle {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-top: 40px;
+ margin-bottom: 24px;
+ z-index: 5;
+}
+
+.prodListTitle > div {
+ display: flex;
+ gap: 12px;
+}
+
+.prodSearchWrap {
+ position: relative;
+}
+.prodSearch input[type="text"] {
+ border: 0;
+ line-height: 26px;
+ height: 42px;
+ width: 325px;
+ background-color: #f3f4f6;
+ padding: 9px 24px;
+ padding-left: 44px;
+ font-size: 16px;
+ border-radius: 12px;
+ border: 0;
+}
+.prodSearch input[type="text"]:focus,
+.prodSearch input[type="text"]:hover,
+.prodSearch input[type="text"]:active {
+ background-color: #eaedf1;
+ border: 0;
+ outline: 0;
+}
+
+.prodSearch img {
+ position: absolute;
+ left: 16px;
+ top: 10px;
+ width: 24px;
+}
+
+/* tablet */
+@media (max-width: 1199px) {
+}
+/* Mobile */
+@media (max-width: 767px) {
+ .prodListTitle {
+ position: relative;
+ align-items: flex-start;
+ flex-direction: column;
+ gap: 8px;
+ }
+ .prodListTitle > div {
+ width: 100%;
+ }
+ .prodSearch {
+ width: 100%;
+ }
+ .prodSearchWrap {
+ width: 100%;
+ }
+ .prodAddBtn {
+ position: absolute;
+ top: 0;
+ right: 0;
+ }
+ .prodSearch input[type="text"] {
+ width: 100%;
+ }
+}
diff --git a/src/app/items/[id]/ItemsDetail.module.css b/src/app/items/[id]/ItemsDetail.module.css
new file mode 100644
index 00000000..c75d5e57
--- /dev/null
+++ b/src/app/items/[id]/ItemsDetail.module.css
@@ -0,0 +1,3 @@
+.items_detail {
+ margin-bottom: 150px;
+}
diff --git a/src/app/items/[id]/page.tsx b/src/app/items/[id]/page.tsx
new file mode 100644
index 00000000..237bb5e4
--- /dev/null
+++ b/src/app/items/[id]/page.tsx
@@ -0,0 +1,17 @@
+'use client';
+import React from 'react';
+import styles from './ItemsDetail.module.css';
+import ProductDetails from '@/components/ItemsDetail/ProductDetails';
+
+function ItemsDetail() {
+
+ return (
+ <>
+
+ >
+ );
+}
+
+export default ItemsDetail;
\ No newline at end of file
diff --git a/src/app/items/apply/Additem.module.css b/src/app/items/apply/Additem.module.css
new file mode 100644
index 00000000..fb6edd0b
--- /dev/null
+++ b/src/app/items/apply/Additem.module.css
@@ -0,0 +1,6 @@
+.formBox {
+ display: flex;
+ flex-direction: column;
+ gap: 32px;
+ margin-bottom: 69px;
+}
diff --git a/src/app/items/apply/page.tsx b/src/app/items/apply/page.tsx
new file mode 100644
index 00000000..04609a53
--- /dev/null
+++ b/src/app/items/apply/page.tsx
@@ -0,0 +1,67 @@
+'use client';
+
+import React, { useEffect } from 'react';
+import { useState } from 'react';
+import styles from './Additem.module.css';
+import Container from 'components/layout/Container';
+import Button from 'components/ui/Button';
+import Title from 'components/ui/Title';
+import { InputField, TextAreaField } from '@/components/ui/form/InputBox';
+import TagBox from '@/components/ui/TagBox';
+import { CreateProductRequest, ProductSummary, usePostProduct } from '@/hooks/useItems';
+import ImageFileBox from '@/components/ui/form/ImageFileBox';
+import { useConfirmModal } from '@/hooks/useModal';
+import ConfirmModal from '@/components/ui/ConfirmModal';
+import { useRouter } from 'next/navigation';
+
+const INITIAL_PRODUCT: CreateProductRequest = {
+ images: [],
+ name: '',
+ description: '',
+ price: 0,
+ tags: [],
+};
+
+
+function Additem() {
+ const router = useRouter();
+ const { isConfirmOpen, confirmMessage, openConfirmModal, closeConfirmModal } = useConfirmModal();
+ const [addProduct, setAddProduct] = useState(INITIAL_PRODUCT);
+
+ const { mutate: postProduct} = usePostProduct(openConfirmModal,router);
+
+ function handleInputBlur(e: React.FocusEvent){
+ const value = e.target.value;
+ setAddProduct((prev) => ({
+ ...prev,
+ [e.target.id]: value
+ }));
+ }
+
+ const handleCreateProduct = () => {
+ postProduct(addProduct);
+ }
+
+ return (
+
+
+
+
+
+
+
+
+ );
+}
+
+export default Additem;
\ No newline at end of file
diff --git a/src/app/items/page.tsx b/src/app/items/page.tsx
new file mode 100644
index 00000000..8a49f4f1
--- /dev/null
+++ b/src/app/items/page.tsx
@@ -0,0 +1,23 @@
+'use client';
+
+import React from 'react';
+import Container from 'components/layout/Container';
+import Title from 'components/ui/Title';
+import { BestItems } from '@/components/Product/BestItems';
+import { AllItems } from '@/components/Product/AllItems';
+
+
+function ItemsBox() {
+
+ return (
+ <>
+
+
+
+
+
+ >
+ );
+}
+
+export default ItemsBox;
\ No newline at end of file
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
new file mode 100644
index 00000000..936ff0d4
--- /dev/null
+++ b/src/app/layout.tsx
@@ -0,0 +1,50 @@
+
+'use client';
+
+import './global.css';
+import { ReactNode, useEffect, useRef } from 'react';
+import QueryProvider from '@/components/layout/providers/query-provider';
+import Nav from '@/components/layout/Nav';
+import Footer from '@/components/layout/Footer';
+import { usePathname, useSelectedLayoutSegments } from 'next/navigation';
+import ProductNav from '@/components/layout/ProductNav';
+
+
+export default function RootLayout({ children }: { children: ReactNode }) {
+
+ const segments = useSelectedLayoutSegments(); // ex: ['login']
+ const isAuthPage = segments[0] === 'login' || segments[0] === 'signup';
+ const isItemsPage = segments[0] === 'items' || segments[0] === 'boards';
+
+ const pathname = usePathname();
+ const prevPathnameRef = useRef(null);
+
+ useEffect(() => {
+ if ( pathname !== '/login' && pathname !== prevPathnameRef.current) {
+ sessionStorage.setItem('redirectPath', pathname);
+ prevPathnameRef.current = pathname;
+ }
+ }, [pathname]);
+
+
+ return (
+
+
+
+ {!isAuthPage ?
+ !isItemsPage ?
+ :
+
+ : null
+ }
+
+ {children}
+
+ {!isAuthPage && }
+
+
+
+
+ );
+}
+
diff --git a/src/app/login/Login.module.css b/src/app/login/Login.module.css
new file mode 100644
index 00000000..ee77d19c
--- /dev/null
+++ b/src/app/login/Login.module.css
@@ -0,0 +1,119 @@
+.login_body {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+}
+.login_wrap {
+ width: 100%;
+ max-width: 640px;
+ margin: 0 auto;
+ text-align: center;
+}
+
+.login_box {
+ text-align: left;
+}
+.login_box label {
+ position: relative;
+ display: block;
+ font-weight: 700;
+ font-size: 18px;
+ margin-bottom: 24px;
+}
+.login_box input[type="email"],
+.login_box input[type="password"],
+.login_box input[type="text"] {
+ display: block;
+ width: 100%;
+ margin-top: 16px;
+ padding: 15px 24px;
+ background-color: var(--Cool_Gray_100);
+ border-radius: 12px;
+ border: 0;
+}
+
+/* error */
+.error_box input[type="email"],
+.error_box input[type="password"],
+.error_box input[type="text"] {
+ outline: 1px solid #f74747;
+}
+
+.error {
+ display: block;
+ color: #f74747;
+ font-size: 14px;
+ font-weight: 500;
+ line-height: 24px;
+ margin-left: 16px;
+ margin-top: 8px;
+}
+
+.login_box .eye {
+ position: absolute;
+ right: 18px;
+ top: 48px;
+ cursor: pointer;
+}
+
+.login_box #submit {
+ display: block;
+ width: 100%;
+ border-radius: 9999px;
+ outline: 0;
+ padding: 12px 0;
+ color: var(--Cool_Gray_100);
+ font-size: 20px;
+ font-weight: 600;
+ line-height: 32px;
+ background-color: var(--Primary);
+ border: 0;
+ cursor: pointer;
+}
+.login_box #submit:disabled {
+ background-color: #9ca3af;
+}
+
+.login_wrap .member_sub_box span {
+ font-size: 14px;
+}
+.login_wrap .member_sub_box a {
+ color: var(--Primary);
+ text-decoration: underline;
+}
+.submit {
+ display: block;
+ width: 100%;
+ border-radius: 9999px;
+ outline: 0;
+ padding: 12px 0;
+ color: var(--Cool_Gray_100);
+ font-size: 20px;
+ font-weight: 600;
+ line-height: 32px;
+ background-color: var(--Primary);
+ border: 0;
+ cursor: pointer;
+}
+.submit:disabled {
+ background-color: #9ca3af;
+}
+
+/* Mobile */
+@media (max-width: 767px) {
+ .login_body {
+ padding: 0 16px;
+ }
+ .login_wrap {
+ width: 100%;
+ max-width: 400px;
+ }
+
+ .login_box label {
+ font-size: 14px;
+ }
+
+ .login_box .eye {
+ top: 42px;
+ }
+}
diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx
new file mode 100644
index 00000000..a4a427f3
--- /dev/null
+++ b/src/app/login/page.tsx
@@ -0,0 +1,91 @@
+
+'use client';
+import React from 'react';
+import { useState ,useEffect, useMemo } from 'react';
+import Link from 'next/link';
+import styles from './Login.module.css';
+import { memberCheck } from 'utils/auth';
+import Button from 'components/ui/Button';
+import MembersLogo from '@/components/members/MembersLogo';
+import SnsLogin from '@/components/members/SnsLogin';
+import { useLoginMutation } from '@/hooks/useAuth';
+import { useConfirmModal, useModal } from '@/hooks/useModal';
+import ConfirmModal from '@/components/ui/ConfirmModal';
+import FormField from '@/components/ui/form/FormField';
+
+function Login() {
+ const [email, setEmail] = useState('user@mail.com');
+ const [password, setPassword] = useState('12345678');
+ const [passwordBoxType, setPasswordBoxType] = useState(true);
+ const [errorCase, setErrorCase] = useState({ email:'', password:'' });
+
+ const { isConfirmOpen, confirmMessage, openConfirmModal, closeConfirmModal } = useConfirmModal();
+ const { mutate: login, isPending } = useLoginMutation(openConfirmModal);
+
+ const handleLogin = () => {
+ login({ email, password });
+ };
+
+ // input이 Blur될때 email,password state 변경 및 UserChecked state 표시
+ const handleInputBlur = (e: React.FocusEvent) => {
+ if(e.target.id === 'login_email') {
+ setEmail(e.target.value);
+ } else if(e.target.id === 'login_pwd') {
+ setPassword(e.target.value);
+ }
+ }
+
+ const idCheck = useMemo(() => memberCheck.EmailChecked(email), [email]);
+ const passwordCheck = useMemo(() => memberCheck.passwordChecked(password), [password]);
+
+ useEffect(() => {
+ setErrorCase({
+ email: idCheck,
+ password: passwordCheck,
+ });
+ }, [idCheck, passwordCheck]);
+
+ const handleEyeClick = () => setPasswordBoxType(!passwordBoxType);
+
+ const isFormValid = email && password && errorCase.email === '' && errorCase.password === '';
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 판다마켓은 처음이신가요? 회원가입
+
+
+
+
+ );
+}
+
+export default Login;
\ No newline at end of file
diff --git a/src/app/page.tsx b/src/app/page.tsx
new file mode 100644
index 00000000..e5ef4c96
--- /dev/null
+++ b/src/app/page.tsx
@@ -0,0 +1,77 @@
+'use client';
+
+import React from 'react';
+import Button from '@/components/ui/Button';
+import Image from 'next/image';
+import { MotionSelection, VisualSelection } from '@/components/ui/mainSelection';
+import { imgHome1, imgHome2, imgHome3, imgHome_bottom, imgHome_top } from '@/lib/imageAssets';
+
+function HomePage() {
+
+ return (
+ <>
+
+
+
+
+
+
+ 일상의 모든 물건을
거래해 보세요
+
+
+
+
+
+
+
+
+
+
Hot item
+
인기 상품을
확인해 보세요
+
가장 HOT한 중고거래 물품을
판다 마켓에서 확인해 보세요
+
+
+
+
+
+
+
+
Search
+
구매를 원하는
상품을 검색하세요
+
구매하고 싶은 물품은 검색해서
쉽게 찾아보세요
+
+
+
+
+
+
+
+
Register
+
판매를 원하는
상품을 등록하세요
+
어떤 물건이든 판매하고 싶은 상품을
쉽게 등록하세요
+
+
+
+
+
+
+
+
+ 믿을 수 있는
+ 판다마켓 중고 거래
+
+
+
+ >
+ );
+}
+
+export default HomePage;
diff --git a/src/app/signup/page.tsx b/src/app/signup/page.tsx
new file mode 100644
index 00000000..8560e56b
--- /dev/null
+++ b/src/app/signup/page.tsx
@@ -0,0 +1,142 @@
+
+'use client';
+import React from 'react';
+import Link from 'next/link';
+import { useState ,useEffect,useMemo } from 'react';
+import styles from '../login/Login.module.css';
+import { memberCheck } from 'utils/auth';
+import Button from 'components/ui/Button';
+import MembersLogo from '@/components/members/MembersLogo';
+import SnsLogin from '@/components/members/SnsLogin';
+import FormField from '@/components/ui/form/FormField';
+import { useSignUp } from '@/hooks/useAuth';
+import { useConfirmModal, useModal } from '@/hooks/useModal';
+import ConfirmModal from '@/components/ui/ConfirmModal';
+
+function Login() {
+ const [email, setEmail] = useState('');
+ const [nickname, setNickname] = useState('');
+ const [password, setPassword] = useState('');
+ const [pwdCheck, setPwdCheck] = useState('');
+ const [passwordBoxType, setPasswordBoxType] = useState(true);
+ const [pwdCheckBoxType, setPwdCheckBoxType] = useState(true);
+ const [errorCase, setErrorCase] = useState({ email:'', name:'', password:'',pwdCheck:'' });
+
+ const { isConfirmOpen, confirmMessage, openConfirmModal, closeConfirmModal } = useConfirmModal();
+ const { mutate: signUp, isPending } = useSignUp(openConfirmModal);
+
+const handleSignUp = () => {
+ signUp({
+ email,
+ nickname,
+ password,
+ passwordConfirmation: pwdCheck,
+ });
+};
+ const setters: Record>> = {
+ login_email: setEmail,
+ login_name: setNickname,
+ login_pwd: setPassword,
+ login_pwd_check: setPwdCheck,
+ };
+
+ // input이 Blur될때 email,password state 변경 및 UserChecked state 표시
+ const handleInputBlur = (e: React.FocusEvent) => {
+ const { id, value } = e.target;
+
+ const setter = setters[id];
+ if (setter) setter(value);
+
+ let error = '';
+ if (id === 'login_email') error = memberCheck.EmailChecked(value);
+ else if (id === 'login_name') error = memberCheck.NameChecked(value);
+ else if (id === 'login_pwd') error = memberCheck.passwordChecked(value);
+ else if (id === 'login_pwd_check') error = memberCheck.passwordDoubleChecked(password, value); // password 상태 사용
+
+ setErrorCase(prev => ({
+ ...prev,
+ [id.replace('login_', '')]: error, // email, name, password, pwdCheck에 매핑
+ }));
+ };
+
+
+ const handleEyeClick = () => setPasswordBoxType(!passwordBoxType);
+ const handleEyePwdCheck = () => setPwdCheckBoxType(!pwdCheckBoxType);
+
+ const isFormValid =
+ email &&
+ nickname &&
+ password &&
+ pwdCheck &&
+ errorCase.email === '' &&
+ errorCase.name === '' &&
+ errorCase.password === '' &&
+ errorCase.pwdCheck === '';
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 이미 회원이신가요? 로그인
+
+
+
+
+ );
+}
+
+export default Login;
\ No newline at end of file
diff --git a/src/components/FallbackImage/FallbackImage.tsx b/src/components/FallbackImage/FallbackImage.tsx
new file mode 100644
index 00000000..b66e6c57
--- /dev/null
+++ b/src/components/FallbackImage/FallbackImage.tsx
@@ -0,0 +1,87 @@
+"use client";
+
+import { useState } from "react";
+import Image, { ImageProps } from "next/image";
+import { allowedImageDomains, defaultImg, imageExtensionRegex } from "@/lib/imageAssets";
+
+interface ImageWithFadeProps extends ImageProps {
+ fallbackSrc?: string;
+ blurDataURL?: string;
+}
+
+export const FallbackImage = ({
+ src,
+ alt,
+ fallbackSrc = defaultImg,
+ blurDataURL = defaultImg,
+ className = "",
+ ...props
+}: ImageWithFadeProps) => {
+ const [isLoaded, setIsLoaded] = useState(false);
+ const [hasError, setHasError] = useState(false);
+
+ // console.log("src", src);
+ // string 타입 보장
+ const url = typeof src === "string" ? src : "";
+
+ // 유효성 검사
+ let isValid = false;
+ try {
+ const parsed = new URL(url);
+ const isExtensionOk = imageExtensionRegex.test(parsed.pathname);
+ const isDomainOk = allowedImageDomains.includes(parsed.hostname);
+ isValid = isExtensionOk && isDomainOk;
+ } catch {
+ isValid = false;
+ }
+
+ // 최종 표시할 이미지 src
+ const finalSrc = hasError || !isValid ? defaultImg : src;
+
+ return (
+
+
+ setIsLoaded(true)}
+ sizes="sm:100vw, 33vw"
+ className={`object-cover transition-opacity duration-700 ${
+ isLoaded ? "opacity-100" : "opacity-0"
+ } ${className}`}
+ {...props}
+ />
+
+
+ // setIsLoaded(true)}
+ // onError={() => setHasError(true)}
+ // placeholder={blurDataURL ? "blur" : undefined}
+ // blurDataURL={blurDataURL}
+ // fill
+ // priority
+ // unoptimized
+ // sizes="(max-width: 768px) 100vw, 33vw"
+ // className={`transition-opacity duration-700 ease-in-out ${
+ // isLoaded ? "opacity-100" : "opacity-0"
+ // } ${className}`}
+ // {...props}
+ // />
+ );
+};
+
diff --git a/src/components/ItemsDetail/ProductDescription.module.css b/src/components/ItemsDetail/ProductDescription.module.css
new file mode 100644
index 00000000..c5270af1
--- /dev/null
+++ b/src/components/ItemsDetail/ProductDescription.module.css
@@ -0,0 +1,65 @@
+.description {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+}
+.title {
+ padding-bottom: 16px;
+ margin-bottom: 24px;
+ border-bottom: 1px solid var(--Cool_Gray_200);
+}
+
+.title h2 {
+ font-weight: 700;
+ margin-bottom: 16px;
+}
+
+.title h3 {
+ font-weight: 700;
+}
+.description > div > ul {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+}
+
+.description > div > ul > li > h4 {
+ margin-bottom: 16px;
+ font-size: 16px;
+}
+
+.description > div > ul > li > div {
+ display: flex;
+ gap: 8px;
+}
+
+.description > div > ul > li > div p {
+ color: var(--Cool_Gray_800);
+ background-color: var(--Cool_Gray_100);
+ border-radius: 26px;
+ padding: 5px 16px;
+}
+
+.description > div > ul > li > div p::before {
+ content: "#";
+}
+.UserInfo {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+.likeBtnBox {
+ padding-left: 24px;
+ border-left: 1px solid var(--Cool_Gray_200);
+}
+
+/* Tablet */
+@media (max-width: 1199px) {
+ .description > div > ul > li > div {
+ flex-wrap: wrap;
+ }
+}
+/* Mobile */
+@media (max-width: 767px) {
+}
diff --git a/src/components/ItemsDetail/ProductDescription.tsx b/src/components/ItemsDetail/ProductDescription.tsx
new file mode 100644
index 00000000..72ef83c9
--- /dev/null
+++ b/src/components/ItemsDetail/ProductDescription.tsx
@@ -0,0 +1,66 @@
+import React, { useState } from 'react';
+import styles from './ProductDescription.module.css';
+import UserInfo from 'components/ui/UserInfo';
+import { formatDate } from 'utils/date';
+import Icon from 'components/ui/Icon';
+import Button from 'components/ui/Button';
+import clsx from 'clsx';
+import { ProductDetail } from '@/hooks/useProductsDetail';
+import LikeButton from '../ui/LikeButton';
+import { useGetUserFavorites } from '@/hooks/useUser';
+
+function ProductDescription(detailData:ProductDetail) {
+
+ const {
+ createdAt,
+ description,
+ favoriteCount,
+ // isFavorite,
+ name,
+ ownerNickname,
+ price,
+ tags
+ } = detailData;
+
+ const { data } = useGetUserFavorites({});
+ const isFavorite = data?.list.some((item) => item.id === detailData.id) ?? false;
+
+ // '2025-04-08T01:00:06+09:00' '2025-04-07T01:00:06+09:00'
+ const createdAtString = formatDate(createdAt);
+ // console.log(createdAtString);
+
+ return (
+
+
+
+
{name}
+ {price?.toLocaleString()}원
+
+
+ -
+
상품 소개
+ {description}
+
+ {tags?.length > 0 && (
+ -
+
상품 태그
+
+ {tags.map((tag, index) => (
+
{tag}
+ ))}
+
+
+ )}
+
+
+
+
+ );
+}
+
+export default ProductDescription;
\ No newline at end of file
diff --git a/src/components/ItemsDetail/ProductDetails.module.css b/src/components/ItemsDetail/ProductDetails.module.css
new file mode 100644
index 00000000..219667a9
--- /dev/null
+++ b/src/components/ItemsDetail/ProductDetails.module.css
@@ -0,0 +1,8 @@
+.container {
+ display: flex;
+ gap: 24px;
+ margin-top: 29px;
+ border-bottom: 1px solid var(--Cool_Gray_200);
+ padding-bottom: 40px;
+ margin-bottom: 40px;
+}
diff --git a/src/components/ItemsDetail/ProductDetails.tsx b/src/components/ItemsDetail/ProductDetails.tsx
new file mode 100644
index 00000000..20e8123e
--- /dev/null
+++ b/src/components/ItemsDetail/ProductDetails.tsx
@@ -0,0 +1,36 @@
+'use client';
+
+import React, { useState } from 'react';
+import ProductOverview from './ProductOverview';
+import ProductDescription from './ProductDescription';
+import Container from 'components/layout/Container';
+import CommentSection from './comment/CommentSection';
+import { useProductsDetails } from '@/hooks/useProductsDetail';
+import { useParams } from 'next/navigation';
+
+
+function ProductDetails( ) {
+
+ const { id } = useParams(); // URL에서 [id] 추출
+ const productId = Number(id);
+
+ const { data, isLoading, isError } = useProductsDetails(productId);
+
+ return (
+
+
+ { data && (
+ <>
+
+
+ >
+ )}
+
+
+
+
+
+ );
+}
+
+export default ProductDetails;
\ No newline at end of file
diff --git a/src/components/ItemsDetail/ProductOverview.module.css b/src/components/ItemsDetail/ProductOverview.module.css
new file mode 100644
index 00000000..739296e6
--- /dev/null
+++ b/src/components/ItemsDetail/ProductOverview.module.css
@@ -0,0 +1,18 @@
+.overview {
+ min-width: 486px;
+}
+.overview > div {
+ position: relative;
+ height: 0;
+ width: 100%;
+ padding-bottom: 100%;
+}
+.overview img {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ border-radius: 16px;
+ object-fit: cover;
+}
diff --git a/src/components/ItemsDetail/ProductOverview.tsx b/src/components/ItemsDetail/ProductOverview.tsx
new file mode 100644
index 00000000..f1c595a2
--- /dev/null
+++ b/src/components/ItemsDetail/ProductOverview.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import styles from './ProductOverview.module.css';
+import Image from 'next/image';
+import { defaultImg } from '@/lib/imageAssets';
+import { FallbackImage } from '../FallbackImage/FallbackImage';
+
+
+interface ProductOverviewProps {
+ img: string[];
+}
+
+function ProductOverview({ img }: ProductOverviewProps) {
+ return (
+
+ );
+}
+
+export default ProductOverview;
\ No newline at end of file
diff --git a/src/components/ItemsDetail/comment/CommentForm.tsx b/src/components/ItemsDetail/comment/CommentForm.tsx
new file mode 100644
index 00000000..4dc27869
--- /dev/null
+++ b/src/components/ItemsDetail/comment/CommentForm.tsx
@@ -0,0 +1,39 @@
+
+import Button from 'components/ui/Button';
+import { TextAreaBox } from '@/components/ui/form/InputBox';
+import React, { useState } from 'react';
+import { usePostProductComment } from '@/hooks/useProductsComments';
+import { useConfirmModal, useModal } from '@/hooks/useModal';
+import ConfirmModal from '@/components/ui/ConfirmModal';
+
+type CommentFormProps = {
+ productId: number;
+};
+
+function CommentForm({productId}: CommentFormProps) {
+
+ const [requestCommentValue, setRequestCommentValue] = useState('');
+ const { isConfirmOpen, confirmMessage, openConfirmModal, closeConfirmModal } = useConfirmModal();
+ const { mutate: postComment } = usePostProductComment(productId, openConfirmModal);
+
+ const handleClick = () => {
+ postComment(requestCommentValue);
+ setRequestCommentValue('');
+ };
+
+ return (
+
+
문의하기
+
) => setRequestCommentValue(e.target.value)}
+ />
+
+
+
+
+
+ );
+}
+export default CommentForm;
\ No newline at end of file
diff --git a/src/components/ItemsDetail/comment/CommentItem.tsx b/src/components/ItemsDetail/comment/CommentItem.tsx
new file mode 100644
index 00000000..df2e88ff
--- /dev/null
+++ b/src/components/ItemsDetail/comment/CommentItem.tsx
@@ -0,0 +1,109 @@
+
+import React, { useState } from 'react';
+import { formatDate } from 'utils/date';
+import UserInfo from 'components/ui/UserInfo';
+import clsx from 'clsx';
+import { TextAreaBox } from '@/components/ui/form/InputBox';
+import Button from 'components/ui/Button';
+import DropdownMenu from 'components/ui/DropdownMenu';
+import Modal from '@/components/ui/Modal';
+import { CommentItemUnit, useDeleteCommentMutation, usePatchProductComment } from '@/hooks/useProductsComments';
+import { useConfirmModal, useModal } from '@/hooks/useModal';
+import ConfirmModal from '@/components/ui/ConfirmModal';
+import { useAuth } from '@/contexts/AuthContext';
+
+interface CommentItemProps {
+ productId: number;
+ commentItem: CommentItemUnit;
+}
+
+function CommentItem({productId,commentItem}:CommentItemProps) {
+ const {
+ id:commentId,
+ content,
+ updatedAt,
+ } = commentItem;
+
+ const createdAtString = formatDate(updatedAt);
+
+ const [editMode, setEditMode] = useState(false);
+ const [requestCommentValue, setRequestCommentValue] = useState(content);
+
+ const { isModalOpen, modalMessage, openModal, closeModal } = useModal();
+ const { isConfirmOpen, confirmMessage, openConfirmModal, closeConfirmModal } = useConfirmModal();
+ const { mutate: deleteProduct } = useDeleteCommentMutation(productId,openConfirmModal);
+ const { mutate: patchComment } = usePatchProductComment(productId,openConfirmModal);
+ const { user } = useAuth();
+
+ const handleOpenModal = () =>{
+ if(!user) {
+ openConfirmModal('로그인 후 이용 가능합니다.');
+ return;
+ }
+ if(commentItem.writer.id !== user?.id) {
+ openConfirmModal('본인의 댓글만 삭제할 수 있습니다.');
+ return;
+ }
+ openModal('정말 삭제하시겠습니까?');
+ };
+ const handleOpenEdit = () =>{
+ if(!user) {
+ openConfirmModal('로그인 후 이용 가능합니다.');
+ return;
+ }
+ if(commentItem.writer.id !== user?.id) {
+ openConfirmModal('본인의 댓글만 수정할 수 있습니다.');
+ return;
+ }
+ setEditMode(true);
+ };
+
+ const handleConfirmDelete = () => {
+ deleteProduct(commentId);
+ closeConfirmModal();
+ };
+ const handleUpdate = () =>{
+ patchComment({ commentId, requestCommentValue });
+ setEditMode(false)
+ };
+
+ const dropdownActions = [
+ {
+ label: '삭제하기',
+ onClick: handleOpenModal,
+ },
+ {
+ label: '수정하기',
+ onClick:handleOpenEdit,
+ },
+ ];
+
+
+ return (
+
+ {editMode === true ? (
+
+
) => setRequestCommentValue(target.value)}
+ />
+
+
+
+
+
+ ):(
+
+ {requestCommentValue}
+
+
+ )}
+
+
+
+
+ );
+}
+export default CommentItem;
\ No newline at end of file
diff --git a/src/components/ItemsDetail/comment/CommentList.tsx b/src/components/ItemsDetail/comment/CommentList.tsx
new file mode 100644
index 00000000..b0b58379
--- /dev/null
+++ b/src/components/ItemsDetail/comment/CommentList.tsx
@@ -0,0 +1,71 @@
+import React, { useEffect, useRef } from 'react';
+import { useInfiniteProductsComments } from '@/hooks/useProductsComments';
+import CommentItem from './CommentItem';
+import LoadingBox from '@/components/ui/LoadingBox';
+import EmptyBox from '@/components/ui/EmptyBox';
+
+
+interface CommentListProps {
+ productId: number;
+ className?: string;
+ [key: string]: any;
+}
+
+function CommentList({ productId,className, ...rest }: CommentListProps) {
+
+ const {
+ data,
+ fetchNextPage,
+ hasNextPage,
+ isFetchingNextPage,
+ isLoading,
+ isError,
+ } = useInfiniteProductsComments(productId);
+
+ const loadMoreRef = useRef(null);
+
+
+ useEffect(() => {
+ if (!loadMoreRef.current || !hasNextPage) return;
+
+ const observer = new IntersectionObserver(
+ (entries) => {
+ if (entries[0].isIntersecting && hasNextPage) {
+ fetchNextPage();
+ }
+ },
+ { threshold: 1.0 }
+ );
+
+ observer.observe(loadMoreRef.current);
+
+ return () => {
+ if (loadMoreRef.current) observer.unobserve(loadMoreRef.current);
+ };
+ }, [hasNextPage, fetchNextPage]);
+
+ return (
+ <>
+ {isLoading ? : data?.pages?.[0].list.length ? (
+
+ {data?.pages.map((page, i) => (
+
+ {page.list.map((comment) => (
+
+
+
+ ))}
+
+ ))}
+
+ {isFetchingNextPage &&
로딩 중...
}
+
+ ):(
+
+ )
+ }
+ >
+
+ );
+}
+export default CommentList;
diff --git a/src/components/ItemsDetail/comment/CommentSection.tsx b/src/components/ItemsDetail/comment/CommentSection.tsx
new file mode 100644
index 00000000..300a97de
--- /dev/null
+++ b/src/components/ItemsDetail/comment/CommentSection.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import Button from 'components/ui/Button';
+import Icon from 'components/ui/Icon';
+import CommentList from './CommentList';
+import CommentForm from './CommentForm';
+import { useRouter } from 'next/navigation';
+
+
+type CommentSectionProps = {
+ productId: number;
+};
+
+function CommentSection({productId}: CommentSectionProps) {
+ const router = useRouter();
+
+ const handleGoBack = () => {
+ router.back(); // ← 이전 페이지로 이동
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+ >
+ );
+}
+export default CommentSection;
diff --git a/src/components/Product/AllItems.module.css b/src/components/Product/AllItems.module.css
new file mode 100644
index 00000000..caae54af
--- /dev/null
+++ b/src/components/Product/AllItems.module.css
@@ -0,0 +1,85 @@
+.title {
+ font-size: 20px;
+ line-height: 42px;
+ font-weight: 700;
+}
+.bestProdListTitle {
+ margin-top: 24px;
+ font-size: 20px;
+ margin-bottom: 16px;
+ font-weight: 700;
+}
+.prodListTitle {
+ display: flex;
+ position: relative;
+ justify-content: space-between;
+ align-items: center;
+ margin-top: 40px;
+ margin-bottom: 24px;
+ z-index: 99;
+}
+
+.prodListTitle > div {
+ display: flex;
+ gap: 12px;
+}
+
+.prodSearchWrap {
+ position: relative;
+}
+.prodSearch input[type="text"] {
+ border: 0;
+ line-height: 26px;
+ height: 42px;
+ width: 325px;
+ background-color: #f3f4f6;
+ padding: 9px 24px;
+ padding-left: 44px;
+ font-size: 16px;
+ border-radius: 12px;
+ border: 0;
+}
+.prodSearch input[type="text"]:focus,
+.prodSearch input[type="text"]:hover,
+.prodSearch input[type="text"]:active {
+ background-color: #eaedf1;
+ border: 0;
+ outline: 0;
+}
+
+.prodSearch img {
+ position: absolute;
+ left: 16px;
+ top: 10px;
+ width: 24px;
+}
+
+/* tablet */
+@media (max-width: 1199px) {
+}
+/* Mobile */
+@media (max-width: 767px) {
+ .prodListTitle {
+ position: relative;
+ align-items: flex-start;
+ flex-direction: column;
+ gap: 8px;
+ }
+ .prodListTitle > div {
+ width: 100%;
+ }
+ .prodSearch {
+ width: 100%;
+ }
+ .prodSearchWrap {
+ width: 100%;
+ }
+ .prodAddBtn {
+ position: absolute;
+ top: 0;
+ right: 0;
+ }
+ .prodSearch input[type="text"] {
+ width: 100%;
+ }
+}
diff --git a/src/components/Product/AllItems.tsx b/src/components/Product/AllItems.tsx
new file mode 100644
index 00000000..ff4c6f06
--- /dev/null
+++ b/src/components/Product/AllItems.tsx
@@ -0,0 +1,172 @@
+'use client';
+import React, { useEffect, useLayoutEffect, useState } from "react";
+import styles from "./AllItems.module.css";
+import Container from "components/layout/Container";
+import Icon from "components/ui/Icon";
+import Button from "components/ui/Button";
+import SelectBox from "components/ui/SelectBox";
+import PageNation from "components/ui/PageNation";
+import LoadingBox from "../ui/LoadingBox";
+import { useItemService, useParsedItemQuery, useSetItemQuery } from "@/hooks/useItemQuery";
+import { useRouter, useSearchParams } from "next/navigation";
+import { ProductQuery } from "@/hooks/useItems";
+import { useScreenType } from "@/hooks/useScreenType";
+import { ProdListAll } from "./ProdListAll";
+import { useBreakpoint } from "@/hooks/useBreakpoint";
+import EmptyBox from "../ui/EmptyBox";
+import { useAuth } from "@/contexts/AuthContext";
+import { useConfirmModal } from "@/hooks/useModal";
+import ConfirmModal from "../ui/ConfirmModal";
+
+
+type orderByType = "recent" | "favorite";
+
+
+const ORDER_OPTIONS = [
+ { value: 'recent', label: '최신순' },
+ { value: 'favorite', label: '좋아요순' },
+];
+
+export function AllItems() {
+
+
+ const searchParams = useSearchParams();
+ const screenType = useScreenType(); // 0: 모바일, 1: 태블릿, 2: 데스크탑
+ const breakpoint = useBreakpoint();
+
+ const VISIBLE_ITEMS = {
+ length: {mobile:4, tablet:6, desktop:10},
+ column: {mobile:2, tablet:3, desktop:5},
+ };
+
+ const INITIAL_QUERY : ProductQuery = {
+ page: 1,
+ pageSize: VISIBLE_ITEMS.length[breakpoint],
+ orderBy: 'recent',
+ keyword: '',
+ };
+
+ const setQueryToURL = useSetItemQuery();
+
+ const parsedQuery = useParsedItemQuery(INITIAL_QUERY, searchParams);
+ const [query, setQuery] = useState(parsedQuery);
+ const { data , isLoading } = useItemService( query , searchParams);
+ const { isConfirmOpen, confirmMessage, openConfirmModal, closeConfirmModal } = useConfirmModal();
+ const { user } = useAuth();
+ const router = useRouter();
+
+ // 페이지 반응형 달라질때마다 pageSize 수정
+ useLayoutEffect(() => {
+ const pageSize = VISIBLE_ITEMS.length[breakpoint];
+ if (query.pageSize === pageSize) return;
+
+ const next = {
+ ...query,
+ page: query.page ?? 1,
+ pageSize,
+ };
+
+ setQuery(next);
+ setQueryToURL(next);
+ }, [breakpoint]);
+
+
+ // PageNation handle
+ const handlePageNationClick = (num: number) => {
+ const next = { ...query, page: (num) };
+ setQuery(next);
+ setQueryToURL(next);
+ };
+
+ // SelectBox handle
+ const handleSelectBoxClick = (value: string) => {
+ const next = { ...query, orderBy: value as orderByType, page: 1 };
+ setQuery(next);
+ setQueryToURL(next);
+ };
+
+ // Keyword handle
+ const handleKeywordChange = (keyword: string) => {
+ const next = { ...query, keyword,page: 1};
+ setQuery(next);
+ setQueryToURL(next);
+ };
+
+ const handleApplyClick = () => {
+ if(!user) {
+ openConfirmModal('로그인 후 이용 가능합니다.');
+ return;
+ }
+ router.push('items/apply');
+ };
+
+ return (
+ <>
+
+
+
+
+ {/* 🔹 로딩 중이면 LoadingBox 표시 */}
+ {isLoading ? (
+
+ ) : data?.list.length ? (
+
+ ) : (
+
+ )}
+
+ {/* 🔹 페이지네이션 */}
+
+
+ >
+ );
+}
+
diff --git a/src/components/Product/BestItems.tsx b/src/components/Product/BestItems.tsx
new file mode 100644
index 00000000..e6b1bd3e
--- /dev/null
+++ b/src/components/Product/BestItems.tsx
@@ -0,0 +1,54 @@
+'use client';
+import React, { useEffect, useState } from 'react';
+import LoadingBox from '../ui/LoadingBox';
+import { ProductQuery, useItemList } from '@/hooks/useItems';
+import { useScreenType } from '@/hooks/useScreenType';
+import { ProdListAll } from './ProdListAll';
+
+export function BestItems() {
+
+const screenType = useScreenType(); // 0: 모바일, 1: 태블릿, 2: 데스크탑
+
+// 베스트 상품 반응형 width 기준값
+const VISIBLE_ITEMS = {
+ length: [1, 2, 4], // 상품 갯수 (mobile, tablet, desktop)
+ column: { mobile: 1, tablet: 2, desktop: 4 }, // 열 갯수 (mobile, tablet, desktop)
+};
+
+// 베스트 상품 쿼리 초기값
+const INITIAL_QUERY: ProductQuery = {
+ page: 1,
+ pageSize: VISIBLE_ITEMS.length[screenType],
+ orderBy: 'favorite',
+ keyword: '',
+};
+
+ const [query, setQuery] = useState(INITIAL_QUERY);
+
+ // 페이지 반응형 달라질때마다 pageSize 수정
+ useEffect(() => {
+ setQuery((prev: typeof INITIAL_QUERY) => ({
+ ...prev,
+ pageSize: VISIBLE_ITEMS.length[screenType],
+ }));
+ }, [screenType]);
+
+
+ const { data , isLoading} = useItemList(query);
+
+ return (
+ <>
+ {isLoading ? (
+
+ ) : (
+ data && (
+
+ )
+ )}
+ >
+ );
+}
+
diff --git a/src/components/Product/ProdListAll.module.css b/src/components/Product/ProdListAll.module.css
new file mode 100644
index 00000000..8303a136
--- /dev/null
+++ b/src/components/Product/ProdListAll.module.css
@@ -0,0 +1,26 @@
+.prodList {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 24px;
+ row-gap: 40px;
+}
+
+.prodList li img {
+ width: 100%;
+}
+
+.prodList.Column_1 li {
+ flex: 0 0 100%;
+}
+.prodList.Column_2 li {
+ flex: 0 0 calc(50% - 12px);
+}
+.prodList.Column_3 li {
+ flex: 0 0 calc(33.33% - 16px);
+}
+.prodList.Column_4 li {
+ flex: 0 0 calc(25% - 18px);
+}
+.prodList.Column_5 li {
+ flex: 0 0 calc(20% - 20px);
+}
diff --git a/src/components/Product/ProdListAll.tsx b/src/components/Product/ProdListAll.tsx
new file mode 100644
index 00000000..dba93850
--- /dev/null
+++ b/src/components/Product/ProdListAll.tsx
@@ -0,0 +1,35 @@
+
+'use client';
+
+import React from 'react';
+import clsx from 'clsx';
+import ProductItem from './ProductItem'
+import styles from './ProdListAll.module.css';
+import Container from 'components/layout/Container';
+import { ProductListResponse } from '@/hooks/useItems';
+
+interface ProdListAllProps {
+ itemsData: ProductListResponse;
+ pageColumn: number;
+ className?: string;
+}
+
+export function ProdListAll({ itemsData, pageColumn, className }: ProdListAllProps) {
+
+ return (
+ <>
+
+
+ { itemsData.totalCount !== 0 && (
+ itemsData.list.map((item) => (
+
+ ))
+ )}
+
+
+ >
+ );
+}
diff --git a/src/components/Product/ProductItem.module.css b/src/components/Product/ProductItem.module.css
new file mode 100644
index 00000000..a44355fd
--- /dev/null
+++ b/src/components/Product/ProductItem.module.css
@@ -0,0 +1,34 @@
+.imgBox {
+ width: 100%;
+ position: relative;
+ overflow: hidden;
+ border-radius: 16px;
+ border: 1px solid var(--Cool_Gray_200);
+}
+.imgBox img {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ transition: transform 0.5s ease;
+}
+.imgBox:hover img {
+ transform: scale(1.1);
+}
+.description {
+ margin-top: 16px;
+}
+
+.description .name {
+ font-size: 14px;
+ line-height: 24px;
+ margin-bottom: 6px;
+}
+
+.description .price {
+ font-size: 16px;
+ line-height: 26px;
+ font-weight: 700;
+ margin-bottom: 6px;
+}
diff --git a/src/components/Product/ProductItem.tsx b/src/components/Product/ProductItem.tsx
new file mode 100644
index 00000000..ecfd7aa3
--- /dev/null
+++ b/src/components/Product/ProductItem.tsx
@@ -0,0 +1,53 @@
+'use client';
+
+import React, { useEffect, useMemo } from 'react';
+import Link from 'next/link';
+import { useState } from 'react';
+import styles from './ProductItem.module.css';
+import Icon from 'components/ui/Icon';
+import Button from 'components/ui/Button';
+import clsx from 'clsx';
+import { ProductSummary, useToggleProductFavorite } from '@/hooks/useItems';
+import { FallbackImage } from '../FallbackImage/FallbackImage';
+import { defaultImg } from '@/lib/imageAssets';
+import { useConfirmModal, useModal } from '@/hooks/useModal';
+import ConfirmModal from '../ui/ConfirmModal';
+import { useAuth } from '@/contexts/AuthContext';
+import { useGetUserFavorites } from '@/hooks/useUser';
+import LikeButton from '../ui/LikeButton';
+
+
+interface ProductItemProps { // ProductSummary 타입정의할때 옵셔널 방식을 사용함 | undefined 필요
+ productItem: ProductSummary;
+}
+
+function ProductItem({productItem}: ProductItemProps) {
+ // const randomNum = Math.floor(Math.random() * 4) + 1;
+ // const randomImg = `../img/img_1.jpg`;
+
+ const productId = productItem.id ?? 0;
+
+ const { data } = useGetUserFavorites({});
+ const isFavorite = data?.list.some((item) => item.id === productId) ?? false;
+
+ return (
+
+
+
+
+
+
+
+
{productItem.name}
+
{productItem.price?.toLocaleString()}원
+
+
+
+
+ );
+}
+
+export default ProductItem;
diff --git a/src/components/layout/Container.tsx b/src/components/layout/Container.tsx
new file mode 100644
index 00000000..d397b649
--- /dev/null
+++ b/src/components/layout/Container.tsx
@@ -0,0 +1,24 @@
+'use client';
+
+import React from 'react';
+import clsx from 'clsx';
+
+interface ContainerProps {
+ className?: string;
+ children: React.ReactNode;
+}
+
+function Container({ className, children }: ContainerProps) {
+
+
+ return (
+
+ );
+}
+
+export default Container;
diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx
new file mode 100644
index 00000000..aefa031a
--- /dev/null
+++ b/src/components/layout/Footer.tsx
@@ -0,0 +1,43 @@
+'use client';
+
+import React from 'react';
+import Container from './Container';
+import Link from 'next/link';
+import Image from 'next/image';
+import { facebookIcon, instagramIcon, twitterIcon, youtubeIcon } from '@/lib/imageAssets';
+
+
+function Footer() {
+ return (
+
+
+
+ ©codeit - 2024
+
+
+
+ Privacy Policy
+ FAQ
+
+
+
+
+
+ );
+}
+
+export default Footer;
diff --git a/src/components/layout/Nav.tsx b/src/components/layout/Nav.tsx
new file mode 100644
index 00000000..26b1e0b8
--- /dev/null
+++ b/src/components/layout/Nav.tsx
@@ -0,0 +1,51 @@
+'use client';
+
+import React from 'react';
+import Container from './Container';
+import Button from '../ui/Button';
+import Image from 'next/image';
+import Link from 'next/link';
+import { useAuth } from '@/contexts/AuthContext';
+import { logoImg1, logoImg2 } from '@/lib/imageAssets';
+import { useConfirmModal, useModal } from '@/hooks/useModal';
+import ConfirmModal from '../ui/ConfirmModal';
+
+
+function Nav() {
+ const { isConfirmOpen, confirmMessage, openConfirmModal, closeConfirmModal } = useConfirmModal();
+ const { user, logout } = useAuth();
+
+ const handleLogout = () => {
+ logout();
+ openConfirmModal('로그아웃 되었습니다.');
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ {user ? (
+
+ ) : (
+
+ )}
+
+
+
+ );
+}
+
+export default Nav;
diff --git a/src/components/layout/ProductNav.module.css b/src/components/layout/ProductNav.module.css
new file mode 100644
index 00000000..67f90ba3
--- /dev/null
+++ b/src/components/layout/ProductNav.module.css
@@ -0,0 +1,9 @@
+/* Mobile */
+@media (max-width: 767px) {
+ .category {
+ margin-left: 8px;
+ }
+ .category a {
+ padding: 0px 8px;
+ }
+}
diff --git a/src/components/layout/ProductNav.tsx b/src/components/layout/ProductNav.tsx
new file mode 100644
index 00000000..0ab2b342
--- /dev/null
+++ b/src/components/layout/ProductNav.tsx
@@ -0,0 +1,60 @@
+'use client';
+
+import React from 'react';
+import styles from './ProductNav.module.css';
+import Image from 'next/image';
+import Container from './Container';
+import Button from '../ui/Button';
+import Link from 'next/link';
+import { useSelectedLayoutSegments } from 'next/navigation';
+import { useAuth } from '@/contexts/AuthContext';
+import { logoImg1, logoImg2 } from '@/lib/imageAssets';
+import ConfirmModal from '../ui/ConfirmModal';
+import { useConfirmModal, useModal } from '@/hooks/useModal';
+
+function ProductNav() {
+ const segments = useSelectedLayoutSegments();
+ const isItems = segments[0] === 'items' ;
+ const isBoards = segments[0] === 'boards';
+ const { isConfirmOpen, confirmMessage, openConfirmModal, closeConfirmModal } = useConfirmModal();
+ const { user, logout } = useAuth();
+
+ const handleLogout = () => {
+ logout();
+ openConfirmModal('로그아웃 되었습니다.');
+ };
+
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ 자유게시판
+ 중고마켓
+
+
+ {user ? (
+
+ ) : (
+
+ )}
+
+
+
+ );
+}
+
+export default ProductNav;
diff --git a/src/components/layout/providers/query-provider.tsx b/src/components/layout/providers/query-provider.tsx
new file mode 100644
index 00000000..d00546ed
--- /dev/null
+++ b/src/components/layout/providers/query-provider.tsx
@@ -0,0 +1,17 @@
+'use client';
+
+import { AuthProvider } from '@/contexts/AuthContext';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { ReactNode, useState } from 'react';
+
+export default function QueryProvider({ children }: { children: ReactNode }) {
+ const [client] = useState(() => new QueryClient());
+
+ return(
+
+
+ {children}
+
+
+ );
+}
diff --git a/src/components/members/MembersLogo.tsx b/src/components/members/MembersLogo.tsx
new file mode 100644
index 00000000..4e7d8e28
--- /dev/null
+++ b/src/components/members/MembersLogo.tsx
@@ -0,0 +1,23 @@
+
+import React from 'react';
+import Link from 'next/link';
+import Image from 'next/image';
+import { logoImg1, logoImg2 } from '@/lib/imageAssets';
+
+
+function MembersLogo() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default MembersLogo;
diff --git a/src/components/members/SnsLogin.module.css b/src/components/members/SnsLogin.module.css
new file mode 100644
index 00000000..d4578c10
--- /dev/null
+++ b/src/components/members/SnsLogin.module.css
@@ -0,0 +1,19 @@
+.sns_login {
+ background-color: #e6f2ff;
+ color: #1f2937;
+ padding: 14px 23px;
+ margin-top: 24px;
+ margin-bottom: 24px;
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+.sns_login .sns_icon a {
+ display: inline-block;
+ margin-left: 8px;
+}
+.sns_login .sns_icon a img {
+ height: 42px;
+ width: 42px;
+}
diff --git a/src/components/members/SnsLogin.tsx b/src/components/members/SnsLogin.tsx
new file mode 100644
index 00000000..45d2b438
--- /dev/null
+++ b/src/components/members/SnsLogin.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import Link from 'next/link';
+import styles from './SnsLogin.module.css';
+import Image from 'next/image';
+import { sns_google, sns_kakao } from '@/lib/imageAssets';
+
+function SnsLogin() {
+ return (
+
+
간편 로그인하기
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default SnsLogin;
\ No newline at end of file
diff --git a/src/components/ui/Button.module.css b/src/components/ui/Button.module.css
new file mode 100644
index 00000000..9ec84004
--- /dev/null
+++ b/src/components/ui/Button.module.css
@@ -0,0 +1,214 @@
+.btn {
+ position: relative;
+ align-items: center;
+ overflow: hidden;
+}
+.btn .top {
+ position: absolute;
+ display: inline-flex;
+ top: 0;
+ left: 49.9%;
+ width: 100%;
+ text-align: center;
+ transition: opacity 0.5s ease, top 0.5s ease;
+ transform: translateX(-50%) translateY(-50%);
+ justify-content: center;
+ align-items: center;
+ opacity: 0;
+}
+.btn:hover .top {
+ top: 50%;
+ opacity: 1;
+}
+.btn:disabled .top {
+ transform: translateX(-50%) translateY(-15%) !important;
+ opacity: 0 !important;
+}
+
+.btn:hover._2 .top {
+ top: 50%;
+ opacity: 1;
+}
+.btn .front {
+ display: inline-flex;
+ transition: opacity 0.5s ease, transform 0.5s ease;
+ transform: translateY(0px);
+ align-items: center;
+ opacity: 1;
+}
+.btn:hover .front {
+ top: 100%;
+ transform: translateY(50%);
+ opacity: 0;
+}
+.btn:disabled .front {
+ top: 0 !important;
+ transform: translateY(0%) !important;
+ opacity: 1 !important;
+}
+/* btn_large */
+.btn.roundedXL {
+ display: inline-flex;
+ font-size: 20px;
+ padding: 16px 124px;
+ border-radius: 50px;
+ justify-content: center;
+}
+/* btn_large */
+.btn.roundedL {
+ display: inline-flex;
+ font-size: 18px;
+ padding: 11px 40px;
+ border-radius: 50px;
+ justify-content: center;
+}
+/* btn_medium */
+.btn.roundedM {
+ display: inline-flex;
+ font-size: 16px;
+ padding: 11px 43px;
+ border-radius: 8px;
+}
+/* btn_small_48 */
+.btn.roundedS {
+ display: inline-flex;
+ font-size: 16px;
+ padding: 11px 30px;
+ border-radius: 8px;
+}
+/* btn_small_40 */
+.btn.roundedSS {
+ display: inline-flex;
+ font-size: 16px;
+ padding: 11px 23px;
+ border-radius: 8px;
+ line-height: 1;
+}
+.btn.roundedXL,
+.btn.roundedL,
+.btn.roundedM,
+.btn.roundedS,
+.btn.roundedSS {
+ background-color: var(--Primary);
+ color: #fff;
+ cursor: pointer;
+ border: 0;
+}
+.btn.roundedXL:hover,
+.btn.roundedL:hover,
+.btn.roundedM:hover,
+.btn.roundedS:hover,
+.btn.roundedSS:hover {
+ background-color: var(--Primary_200);
+ color: #fff;
+}
+.btn.roundedXL:active,
+.btn.roundedL:active,
+.btn.roundedM:active,
+.btn.roundedS:active,
+.btn.roundedSS:active {
+ background-color: var(--Primary_300);
+ color: #fff;
+}
+.btn.roundedXL.n_act,
+.btn.roundedL.n_act,
+.btn.roundedM.n_act,
+.btn.roundedS.n_act,
+.btn.roundedSS.n_act {
+ background-color: var(--Cool_Gray_400);
+ color: #fff;
+}
+.btn.roundedXL:disabled,
+.btn.roundedL:disabled,
+.btn.roundedM:disabled,
+.btn.roundedS:disabled,
+.btn.roundedSS:disabled {
+ background-color: var(--Cool_Gray_400);
+ animation: none;
+}
+
+@keyframes color-change-2x {
+ 0% {
+ background: #19dcea;
+ }
+
+ to {
+ background: #8697ff;
+ }
+}
+.btn.none {
+ display: inline-flex;
+ font-size: 16px;
+ padding: 11px 30px;
+}
+
+/* btn_small_40 */
+.btn.lined_btn {
+ display: inline-flex;
+ font-size: 16px;
+ padding: 11px 30px;
+ border-radius: 8px;
+}
+
+.btn.lined_btn {
+ border: 1px solid var(--Primary);
+ color: var(--Primary);
+ cursor: pointer;
+}
+.btn.lined_btn:hover {
+ border-color: var(--Primary_200);
+ color: var(--Primary_200);
+}
+.btn.lined_btn:active {
+ border-color: var(--Primary_300);
+ color: var(--Primary_300);
+}
+.btn.lined_btn:disabled {
+ border-color: var(--Cool_Gray_400);
+ color: var(--Cool_Gray_400);
+}
+
+.btn.btn-heart_L {
+ color: var(--Cool_Gray_500);
+ border: 1px solid var(--Cool_Gray_500);
+ border-radius: 35px;
+ padding: 8px 13px;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 16px;
+ background-color: #fff;
+}
+.btn.btn-heart_L img {
+ height: 23px;
+}
+
+.btn.btn-heart_S {
+ font-size: 12px;
+ line-height: 18px;
+ display: flex;
+ cursor: pointer;
+}
+
+.btn.btn-heart_S img {
+ width: 16px;
+ margin-right: 4px;
+}
+
+@media (max-width: 1120px) {
+ .btn.roundedM {
+ padding: 12px 124px;
+ }
+}
+
+/* Mobile */
+@media (max-width: 767px) {
+ .btn.roundedXL {
+ font-size: 18px;
+ padding: 11px 71px;
+ }
+ .btn.roundedM {
+ font-size: 18px;
+ padding: 11px 71px;
+ }
+}
diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx
new file mode 100644
index 00000000..1b34e7e5
--- /dev/null
+++ b/src/components/ui/Button.tsx
@@ -0,0 +1,48 @@
+'use client';
+
+import React from 'react';
+import Link from 'next/link';
+import styles from './Button.module.css';
+import clsx from 'clsx';
+
+interface ButtonProps {
+ link?: string; // Made optional
+ variant: string;
+ children: React.ReactNode;
+ className?: string;
+ heightError?: any;
+ disabled?: boolean;
+ onClick?: () => void;
+ childrenClassName?: string;
+ [key: string]: any;
+}
+function Button({ variant, className, childrenClassName, link, children,heightError,disabled, ...restProps } : ButtonProps) {
+ let combinedClassName = clsx(styles.btn, styles[variant], className);
+
+ if(heightError) combinedClassName = clsx(styles.btn, styles._2, styles[variant], className, 'flex gap-2');
+
+ if (link) {
+ return (
+
+ {children}
+ {children}
+
+ );
+ }
+ return (
+
+ );
+}
+
+export default Button;
diff --git a/src/components/ui/ConfirmModal.tsx b/src/components/ui/ConfirmModal.tsx
new file mode 100644
index 00000000..6ab3b3ea
--- /dev/null
+++ b/src/components/ui/ConfirmModal.tsx
@@ -0,0 +1,55 @@
+'use client';
+
+import { useEffect, useRef, useState } from 'react';
+import { createPortal } from 'react-dom';
+import Button from './Button';
+
+interface ErrorModalProps {
+ isOpen: boolean;
+ errorMessage: string;
+ onClose: () => void;
+}
+
+export default function ConfirmModal({ isOpen, errorMessage, onClose }: ErrorModalProps) {
+ const [mounted, setMounted] = useState(false);
+ const confirmButtonRef = useRef(null);
+
+ useEffect(() => {
+ setMounted(true);
+ }, []);
+
+ useEffect(() => {
+ if (isOpen && confirmButtonRef.current) {
+ confirmButtonRef.current.focus();
+ }
+ }, [isOpen]);
+
+
+ if (!isOpen || !mounted) return null;
+
+ const modalRoot = document.getElementById('modal-root');
+ if (!modalRoot) return null;
+
+ return createPortal(
+
+
+
{errorMessage}
+
+
+
,
+ modalRoot
+ );
+}
diff --git a/src/components/ui/DropdownMenu.tsx b/src/components/ui/DropdownMenu.tsx
new file mode 100644
index 00000000..2de52585
--- /dev/null
+++ b/src/components/ui/DropdownMenu.tsx
@@ -0,0 +1,62 @@
+import React, { useEffect, useRef, useState } from 'react';
+import clsx from 'clsx';
+import Icon from './Icon';
+
+
+interface DropdownAction {
+ label: string;
+ onClick: () => void;
+}
+
+interface DropdownMenuProps {
+ dropdownActions: DropdownAction[];
+ className?: string;
+}
+
+function DropdownMenu({ dropdownActions, className }: DropdownMenuProps) {
+
+ const [isOpen, setIsOpen] = useState(false);
+ const dropdownRef = useRef(null);
+
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
+ setIsOpen(false);
+ }
+ };
+
+ if (isOpen) {
+ document.addEventListener('mousedown', handleClickOutside);
+ } else {
+ document.removeEventListener('mousedown', handleClickOutside);
+ }
+
+ // cleanup
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside);
+ };
+ }, [isOpen]);
+ return (
+
+
setIsOpen((prev) => !prev)}>
+
+
+ {isOpen && (
+
+ {dropdownActions.map((action, index) => (
+
+ ))}
+
+ )}
+
+ );
+}
+export default DropdownMenu;
diff --git a/src/components/ui/EmptyBox.tsx b/src/components/ui/EmptyBox.tsx
new file mode 100644
index 00000000..3db0a251
--- /dev/null
+++ b/src/components/ui/EmptyBox.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import Container from '../layout/Container';
+import Image from 'next/image';
+import { emptyImg } from '@/lib/imageAssets';
+import clsx from 'clsx';
+
+
+interface EmptyBoxProps {
+ context?: string;
+ className?: string;
+}
+
+function EmptyBox({context, className}: EmptyBoxProps) {
+ return (
+
+
+
+ {context}
+
+
+ );
+}
+export default EmptyBox;
diff --git a/src/components/ui/Icon.tsx b/src/components/ui/Icon.tsx
new file mode 100644
index 00000000..e926f745
--- /dev/null
+++ b/src/components/ui/Icon.tsx
@@ -0,0 +1,63 @@
+import React from 'react';
+import Image from 'next/image';
+
+const eyeOpen = '/assets/eye_1.svg';
+const eyeClose = '/assets/eye_2.svg';
+
+const heartOpen = '/assets/heart_1.svg';
+const heartClose = '/assets/heart_2.svg';
+
+const arrowDown = '/assets/ic_arrow_down.svg';
+const back = '/assets/ic_back.svg';
+const check = '/assets/ic_check.svg';
+const plus = '/assets/ic_plus.svg';
+const search = '/assets/ic_search.svg';
+const searchDarker = '/assets/ic_search_darker.svg';
+const X = '/assets/ic_X.svg';
+const sort = '/assets/ic_sort.svg';
+
+const statusActiveL = '/assets/status_active.svg';
+const statusActiveR = '/assets/status_active-1.svg';
+const statusInactiveL = '/assets/status_inactive.svg';
+const statusInactiveR = '/assets/status_inactive-1.svg';
+const statusWhiteL = '/assets/status_white.svg';
+const statusWhiteR = '/assets/status_white-1.svg';
+
+const ic_kebab = '/assets/ic_kebab.svg';
+
+const ICON = {
+ eyeOpen,
+ eyeClose,
+ heartOpen,
+ heartClose,
+ arrowDown,
+ back,
+ check,
+ plus,
+ search,
+ searchDarker,
+ X,
+ ic_kebab,
+ sort,
+ statusActiveL,
+ statusActiveR,
+ statusInactiveL,
+ statusInactiveR,
+ statusWhiteL,
+ statusWhiteR,
+};
+
+interface IconProps {
+ iconName: keyof typeof ICON;
+ alt: string;
+ [key: string]: any;
+}
+
+function Icon({ iconName, alt, width="24", height="24", ...rest }: IconProps) {
+ return (
+
+ );
+}
+
+
+export default Icon ;
\ No newline at end of file
diff --git a/src/components/ui/LikeButton.tsx b/src/components/ui/LikeButton.tsx
new file mode 100644
index 00000000..d68843ad
--- /dev/null
+++ b/src/components/ui/LikeButton.tsx
@@ -0,0 +1,49 @@
+'use client';
+
+import Button from "./Button";
+import Icon from "./Icon";
+import { useState } from "react";
+import { useToggleProductFavorite } from "@/hooks/useItems";
+import ConfirmModal from "./ConfirmModal";
+import { useConfirmModal } from "@/hooks/useModal";
+
+interface LikeButtonProps {
+ className?: string;
+ childrenClassName?: string;
+ onClick?: () => void;
+ [key: string]: any;
+}
+function LikeButton({
+ productId,
+ className,
+ childrenClassName,
+ favoriteCount,
+ isFavorite,
+ variant="btn-heart_S",
+ width = 16, height = 16 ,
+ ...restProps
+} : LikeButtonProps) {
+
+ const [isFavorited, setIsFavorited] = useState(isFavorite);
+ const [count, setCount ] = useState(favoriteCount);
+
+
+ const { isConfirmOpen, confirmMessage, openConfirmModal, closeConfirmModal } = useConfirmModal();
+ const { mutate: toggleFavorite } = useToggleProductFavorite(openConfirmModal);
+
+ const handleClick = () => {
+ toggleFavorite({ productId, isFavorited ,setIsFavorited, setCount});
+ };
+
+ return (
+ <>
+
+
+ >
+ )
+
+}
+export default LikeButton;
diff --git a/src/components/ui/LoadingBox.tsx b/src/components/ui/LoadingBox.tsx
new file mode 100644
index 00000000..c6de85ee
--- /dev/null
+++ b/src/components/ui/LoadingBox.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import styles from './LoadingBox.module.css';
+import Container from '../layout/Container';
+
+
+interface LoadingBoxProps {
+ className?: string;
+}
+
+function LoadingBox({className}: LoadingBoxProps) {
+ return (
+
+ 페이지 로딩중입니다.
+
+ );
+}
+export default LoadingBox;
diff --git a/src/components/ui/Modal.tsx b/src/components/ui/Modal.tsx
new file mode 100644
index 00000000..f3a2594a
--- /dev/null
+++ b/src/components/ui/Modal.tsx
@@ -0,0 +1,59 @@
+'use client';
+import React, { useEffect, useRef, useState } from 'react';
+import { createPortal } from 'react-dom';
+import Icon from './Icon';
+import Button from './Button';
+
+
+interface ModalProps {
+ isOpen: boolean;
+ closeModal: () => void;
+ onclick: () => void;
+ message: string;
+}
+
+const Modal: React.FC = ({ isOpen, closeModal, onclick, message }) => {
+ const [mounted, setMounted] = useState(false);
+ const confirmButtonRef = useRef(null);
+
+ useEffect(() => {
+ setMounted(true);
+ }, []);
+
+ useEffect(() => {
+ if (isOpen && confirmButtonRef.current) {
+ confirmButtonRef.current.focus();
+ }
+ }, [isOpen]);
+
+ if (!isOpen || !mounted) return null;
+
+ const modalRoot = document.getElementById('modal-root');
+ if (!modalRoot) return null;
+
+ return createPortal(
+
+
+
+
{message}
+
+
+
+
+
+
,
+ modalRoot
+ );
+};
+
+export default Modal;
\ No newline at end of file
diff --git a/src/components/ui/PageNation.module.css b/src/components/ui/PageNation.module.css
new file mode 100644
index 00000000..54f0435a
--- /dev/null
+++ b/src/components/ui/PageNation.module.css
@@ -0,0 +1,34 @@
+.pageNation {
+ display: flex;
+ justify-content: center;
+ margin: 43px auto 58px;
+ gap: 4px;
+}
+
+.pageNation li {
+ line-height: 20px;
+ padding: 10px 0;
+ height: 40px;
+ width: 40px;
+ border-radius: 9999px;
+ text-align: center;
+ border: 1px solid var(--Cool_Gray_200);
+ color: var(--Cool_Gray_500);
+ font-size: 16px;
+ cursor: pointer;
+}
+.pageNation li img {
+ margin: 0 auto;
+}
+
+.pageNation li.active {
+ background-color: #2f80ed;
+ border-color: #2f80ed;
+ color: #f9fafb;
+}
+.pageNation li.disabled {
+ cursor: default;
+}
+.pageNation li.disabled img {
+ opacity: 0.2;
+}
diff --git a/src/components/ui/PageNation.tsx b/src/components/ui/PageNation.tsx
new file mode 100644
index 00000000..001c9b22
--- /dev/null
+++ b/src/components/ui/PageNation.tsx
@@ -0,0 +1,81 @@
+import React from 'react';
+import styles from './PageNation.module.css';
+import Icon from './Icon';
+
+
+interface PageNationListProps {
+ list: number;
+ onClick: (list: number) => void;
+ current: number;
+}
+
+function PageNationList ({list, onClick, current}: PageNationListProps){
+ const handleClick = () => onClick(list);
+ return (
+ {list}
+ )
+}
+
+interface PageNationProps {
+ current: number;
+ page: number;
+ totalNum: number | undefined;
+ size: number;
+ clickEvent: (page: number) => void;
+}
+
+function PageNation({ current , page , totalNum , size , clickEvent}: PageNationProps){
+
+
+ if( totalNum === undefined || size === 0) return null;
+ const totalPages = Math.ceil(totalNum / size);
+
+ function getNearestMultiplesOfFive(num: number) {
+ if( num > 0 || num % Number(page) !== 0){
+ const lower = Math.floor((num - 1) / 5) * 5 + 1;
+ const upper = lower + 4;
+ return { lower , upper };
+ }
+ }
+
+ const getPageNation = (num: number) => {
+ const pages = getNearestMultiplesOfFive(num);
+
+ if (!pages) return [];
+
+ const first = pages?.lower;
+ const last = pages?.upper > totalPages ? totalPages : pages?.upper;
+
+ return Array.from( { length: last - first + 1 }, (_, i) => first + i);
+ }
+
+ const pageArr = getPageNation(current);
+ const pageFirst = pageArr[0];
+ const pageLast = pageArr[Number(page) - 1];
+
+ const handleClickPrev = () => {
+ if (pageFirst > 1) {
+ clickEvent(pageFirst - 1);
+ }
+ };
+
+ const handleClickNext = () => {
+ if (pageLast < totalPages) {
+ clickEvent(pageLast + 1);
+ }
+ };
+
+ return (
+ ( totalPages < 2) ? null : (
+
+ - 1 ? styles.prev : styles.disabled } onClick={handleClickPrev} >
+ {pageArr.map(list => (
+
+ ))}
+
+
+ )
+ )
+};
+
+export default PageNation;
\ No newline at end of file
diff --git a/src/components/ui/SelectBox.module.css b/src/components/ui/SelectBox.module.css
new file mode 100644
index 00000000..13410663
--- /dev/null
+++ b/src/components/ui/SelectBox.module.css
@@ -0,0 +1,78 @@
+.selectBox {
+ position: relative;
+ width: 130px;
+ z-index: 999;
+}
+.selectBox .selectBtn {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+ border: 1px solid var(--Cool_Gray_200);
+ color: var(--Secondary_800);
+ padding: 8px 18px;
+ border-radius: 12px;
+ background-color: #fff;
+ cursor: pointer;
+}
+
+.selectBox .selectBtn span {
+ line-height: 24px;
+ font-size: 16px;
+}
+.selectBox .selectBtn img {
+ height: 24px;
+}
+
+.selectBoxOption {
+ position: absolute;
+ top: 50px;
+ right: 0;
+ width: 100%;
+ border-radius: 12px;
+ border: 1px solid var(--Cool_Gray_200);
+ overflow: hidden;
+ transform: scaleY(0);
+ z-index: 99;
+}
+
+.selectBoxOption.active {
+ transform: scaleY(1);
+}
+
+.selectBoxOption li {
+ width: 100%;
+ text-align: center;
+}
+.selectBoxOption button {
+ width: 100%;
+ border: 0;
+ background-color: #fff;
+ cursor: pointer;
+ font-size: 16px;
+ padding: 8px 18px;
+ color: var(--Secondary_800);
+}
+
+.selectBoxOption li:first-child button {
+ border-bottom: 1px solid var(--Cool_Gray_200);
+}
+
+/* tablet */
+@media (max-width: 1199px) {
+}
+/* Mobile */
+@media (max-width: 767px) {
+ .selectBox {
+ width: 42px;
+ }
+ .selectBox .selectBtn {
+ padding-left: 0;
+ padding-right: 0;
+ justify-content: center;
+ }
+
+ .selectBoxOption {
+ width: 130px;
+ }
+}
diff --git a/src/components/ui/SelectBox.tsx b/src/components/ui/SelectBox.tsx
new file mode 100644
index 00000000..cab7d204
--- /dev/null
+++ b/src/components/ui/SelectBox.tsx
@@ -0,0 +1,56 @@
+import React from 'react';
+import { useState } from 'react';
+import styles from './SelectBox.module.css';
+import Icon from './Icon';
+import clsx from 'clsx';
+
+
+interface SelectBoxListProps {
+ ItemValue: string;
+ Event: (value: string) => void;
+ ItemLabel: string;
+}
+
+function SelectBoxList({ ItemValue, Event, ItemLabel }: SelectBoxListProps) {
+ const handleClick = () => Event(ItemValue);
+ return (
+
+ );
+}
+
+interface SelectBoxProps {
+ options: { value: string; label: string }[];
+ current: string;
+ clickEvent: (value: string) => void;
+ screenType: number;
+}
+
+function SelectBox({ options, current, clickEvent, screenType }: SelectBoxProps) {
+
+ const [isSelect, setIsSelect] = useState(false);
+ const handleClickToggle = () => {
+ setIsSelect((prev) => !prev); // 현재 상태를 반전시킴
+ };
+
+ return (
+
+
+
+ {options.map((item , index) => (
+
+ ))}
+
+
+ );
+}
+
+export default SelectBox;
diff --git a/src/components/ui/TagBox.module.css b/src/components/ui/TagBox.module.css
new file mode 100644
index 00000000..385c2987
--- /dev/null
+++ b/src/components/ui/TagBox.module.css
@@ -0,0 +1,34 @@
+.tagBox {
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+}
+.tagList {
+ display: flex;
+ gap: 12px;
+}
+.tagList li {
+ position: relative;
+ display: flex;
+ gap: 8px;
+ padding: 5px 16px;
+ padding-right: 44px;
+ background-color: var(--Cool_Gray_100);
+ border-radius: 999px;
+ line-height: 26px;
+}
+.tagDeleteBtn {
+ position: absolute;
+ top: 7px;
+ right: 12px;
+ width: 22px;
+ height: 22px;
+ background-color: var(--Cool_Gray_400);
+ border-radius: 999px;
+ z-index: 99;
+ cursor: pointer;
+}
+
+.tagDeleteBtn img {
+ width: 22px;
+}
diff --git a/src/components/ui/TagBox.tsx b/src/components/ui/TagBox.tsx
new file mode 100644
index 00000000..ca27b196
--- /dev/null
+++ b/src/components/ui/TagBox.tsx
@@ -0,0 +1,80 @@
+import React from 'react';
+import { useState } from "react";
+import styles from './TagBox.module.css';
+import Icon from './Icon';
+import { InputField } from './form/InputBox';
+import { CreateProductRequest, ProductSummary } from '@/hooks/useItems';
+
+
+interface TagListProps {
+ tags: string;
+ onClickDelete: (index: number) => void;
+ num: number
+}
+function TagList({tags, onClickDelete, num}: TagListProps){
+ const handleClick = () => onClickDelete(num);
+ return (
+ <>
+ #{tags}
+
+ >
+ )
+}
+
+interface TagBoxProps {
+ product: CreateProductRequest;
+ setProduct: React.Dispatch>;
+}
+
+function TagBox({product, setProduct}: TagBoxProps){
+ const [inputValue, setInputValue] = useState('');
+
+ // 엔터를 KeyDown 했을때
+ // inputValue 값을 product.tags 에 추가하고 input 박스 리셋
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ if (inputValue.trim()) { // 공백 입력
+ setProduct((prev) => ({
+ ...prev,
+ tags: Array.from(new Set([...prev.tags || [], inputValue.trim()])), // 중복 제거
+ }));
+ setInputValue('');
+ }
+ }
+ }
+
+ // 태그 미리보기 삭제
+ function handleClickTagDelete(index: number){
+ const updatedtag = [...product.tags || []];
+ updatedtag.splice(index, 1);
+
+ setProduct((prev) => ({
+ ...prev,
+ tags: updatedtag,
+ }));
+ }
+
+ return (
+
+
) => setInputValue(e.target.value)}
+ onKeyDown={handleKeyDown}
+ />
+ {product.tags?.length === 0 ? null :
+
+ {product.tags?.map((tag ,index) => (
+ -
+
+
+ ))}
+
+ }
+
+ )
+}
+export default TagBox;
\ No newline at end of file
diff --git a/src/components/ui/Title.module.css b/src/components/ui/Title.module.css
new file mode 100644
index 00000000..eeef88e3
--- /dev/null
+++ b/src/components/ui/Title.module.css
@@ -0,0 +1,11 @@
+.title {
+ display: flex;
+ justify-content: space-between;
+ margin: 24px auto;
+}
+
+.titleTag {
+ font-size: 20px;
+ line-height: 42px;
+ font-weight: 700;
+}
diff --git a/src/components/ui/Title.tsx b/src/components/ui/Title.tsx
new file mode 100644
index 00000000..81bf2c34
--- /dev/null
+++ b/src/components/ui/Title.tsx
@@ -0,0 +1,25 @@
+
+import React from 'react';
+import styles from './Title.module.css';
+
+interface TitleProps {
+ titleTag?: keyof JSX.IntrinsicElements;
+ text?: string;
+ children?: React.ReactNode;
+ [key: string]: any;
+}
+
+function Title ({ titleTag = 'h1', text, children, ...rest }: TitleProps) {
+ const TitleComponent = titleTag;
+ return (
+
+
+ {text}
+
+
+ {children}
+
+
+ )
+}
+export default Title;
\ No newline at end of file
diff --git a/src/components/ui/UserInfo.module.css b/src/components/ui/UserInfo.module.css
new file mode 100644
index 00000000..bd3dfd41
--- /dev/null
+++ b/src/components/ui/UserInfo.module.css
@@ -0,0 +1,34 @@
+.userInfo {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+}
+.userInfo > div {
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
+}
+.userInfo > span {
+ width: 40px;
+ padding-top: 40px;
+ position: relative;
+ overflow: hidden;
+ border-radius: 999px;
+ border: 1px solid var(--Cool_Gray_200);
+}
+.userInfo img {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+.userInfo > div span:first-child {
+ color: var(--Secondary_600);
+ font-size: 14px;
+}
+.userInfo > div span:last-child {
+ color: var(--Cool_Gray_400);
+ font-size: 14px;
+}
diff --git a/src/components/ui/UserInfo.tsx b/src/components/ui/UserInfo.tsx
new file mode 100644
index 00000000..8cc98acf
--- /dev/null
+++ b/src/components/ui/UserInfo.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import styles from './UserInfo.module.css';
+import clsx from 'clsx';
+import Image from 'next/image';
+import { tempUserImg } from '@/lib/imageAssets';
+
+
+interface UserInfoProps {
+ userImg?: string | null;
+ ownerNickname: string;
+ createdAtString: string;
+ fontSize?: string;
+}
+
+function UserInfo({userImg = '', ownerNickname, createdAtString, fontSize = '14px'}: UserInfoProps) {
+ const userImageSrc = userImg === '' || userImg === null ? tempUserImg : userImg;
+ return (
+
+
+
+ {ownerNickname}
+ {createdAtString}
+
+
+ );
+}
+export default UserInfo;
diff --git a/src/components/ui/form/FormField.tsx b/src/components/ui/form/FormField.tsx
new file mode 100644
index 00000000..342be083
--- /dev/null
+++ b/src/components/ui/form/FormField.tsx
@@ -0,0 +1,62 @@
+import React from 'react';
+import Image from 'next/image';
+import { eyeClose, eyeOpen } from '@/lib/imageAssets';
+
+interface FormFieldProps {
+ id: string;
+ label: string;
+ type: string;
+ placeholder: string;
+ error: string;
+ onBlur: (e: React.FocusEvent) => void;
+ withEyeToggle?: boolean;
+ eyeState?: boolean;
+ onEyeToggle?: () => void;
+ eyeIconOpen?: string;
+ eyeIconClose?: string;
+}
+
+export default function FormField({
+ id,
+ label,
+ type,
+ placeholder,
+ error,
+ onBlur,
+ withEyeToggle = false,
+ eyeState = true,
+ onEyeToggle,
+ eyeIconOpen = eyeOpen,
+ eyeIconClose = eyeClose,
+}: FormFieldProps) {
+ return (
+
+ );
+}
diff --git a/src/components/ui/form/ImageFile.module.css b/src/components/ui/form/ImageFile.module.css
new file mode 100644
index 00000000..173d34fe
--- /dev/null
+++ b/src/components/ui/form/ImageFile.module.css
@@ -0,0 +1,120 @@
+.imageFile {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+.label span {
+ line-height: 26px;
+ font-size: 18px;
+ font-weight: 700;
+}
+.inputFile {
+ position: relative;
+ display: flex;
+ height: 282px;
+ gap: 16px;
+}
+.inputBtn {
+ position: relative;
+ width: 282px;
+ height: 100%;
+ background-color: var(--Cool_Gray_100);
+ overflow: hidden;
+ border-radius: 12px;
+}
+.inputBtn input {
+ opacity: 0;
+ width: 100%;
+ height: 100%;
+ cursor: pointer;
+}
+.fakeBox {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ gap: 12px;
+ pointer-events: none;
+}
+
+.fakeBox img {
+ width: 48px;
+}
+
+.fakeBox span {
+ font-size: 16px;
+ color: var(--Cool_Gray_400);
+}
+
+.previewImg {
+ height: 100%;
+ display: flex;
+ gap: 12px;
+}
+
+.previewImg li {
+ position: relative;
+ width: 282px;
+ overflow: hidden;
+ border-radius: 16px;
+}
+
+.previewImg img {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.previewDeleteBtn {
+ position: absolute;
+ top: 12px;
+ right: 12px;
+ width: 24px;
+ height: 24px;
+ background-color: var(--Cool_Gray_400);
+ border-radius: 999px;
+ z-index: 99;
+ cursor: pointer;
+}
+.previewDeleteBtn img {
+ width: 24px;
+}
+.error {
+ display: block;
+ color: #f74747;
+ font-size: 16px;
+ font-weight: 500;
+ margin-top: 8px;
+}
+
+/* Mobile */
+@media (max-width: 767px) {
+ .inputFile {
+ height: auto;
+ }
+ .inputBtn {
+ width: 50%;
+ height: auto;
+ }
+ .inputBtn input {
+ width: 100%;
+ padding-top: 100%;
+ }
+ .previewImg {
+ width: 50%;
+ height: auto;
+ }
+
+ .previewImg li {
+ width: 100%;
+ padding-top: 100%;
+ }
+}
diff --git a/src/components/ui/form/ImageFile.tsx b/src/components/ui/form/ImageFile.tsx
new file mode 100644
index 00000000..6ac7ce1c
--- /dev/null
+++ b/src/components/ui/form/ImageFile.tsx
@@ -0,0 +1,67 @@
+
+import React from 'react';
+import styles from './ImageFile.module.css';
+import Image from 'next/image';
+import Icon from '../Icon';
+import { FallbackImage } from '@/components/FallbackImage/FallbackImage';
+
+interface ImagePreviewsProps {
+ num: number;
+ onClickDelete: (num: number) => void;
+ src: string ;
+}
+
+function ImagePreviews({ num, onClickDelete, src }: ImagePreviewsProps) {
+ const handleClick = () => onClickDelete(num);
+ console.log('src', src , typeof src);
+ return (
+ <>
+
+ >
+ )
+}
+
+interface ImageFile {
+ label: string;
+ text: string;
+ images:(string | null)[];
+ errorCase: string;
+ onChange: React.ChangeEventHandler;
+ onClickDelete: (num: number) => void;
+ [key: string]: any
+}
+function ImageFile({label, text, images, errorCase, onChange, onClickDelete, ...rest }:ImageFile) {
+
+ return (
+
+
+
+
+
+
+ {( images.map((img ,index) => (
+ img === null ? null :
+ -
+
+
+ )))}
+
+
+ { errorCase === '' ? null :
{errorCase} }
+
+
+ )
+}
+
+export default ImageFile;
\ No newline at end of file
diff --git a/src/components/ui/form/ImageFileBox.tsx b/src/components/ui/form/ImageFileBox.tsx
new file mode 100644
index 00000000..35361087
--- /dev/null
+++ b/src/components/ui/form/ImageFileBox.tsx
@@ -0,0 +1,82 @@
+import React from 'react';
+import { useState } from 'react';
+import ImageFile from './ImageFile';
+import { CreateProductRequest } from '@/hooks/useItems';
+import { useUploadImage } from '@/hooks/useUploadImage';
+import { useConfirmModal } from '@/hooks/useModal';
+import ConfirmModal from '../ConfirmModal';
+
+
+interface ImageFileBoxProps {
+ product: CreateProductRequest;
+ setProduct: React.Dispatch>;
+}
+
+function ImageFileBox({ product, setProduct }: ImageFileBoxProps) {
+
+ const [preview, setPreview] = useState<(string | null)[]>([]); // 미리보기 이미지 상태
+ const { isConfirmOpen, confirmMessage, openConfirmModal, closeConfirmModal } = useConfirmModal();
+ const [errorCase, setErrorCase] = useState('');
+ const MAX_IMAGE_COUNT = 1;
+
+ // console.log('업로드된 이미지:', product.images);
+ const { mutate: uploadImage } = useUploadImage(
+ (msg) => openConfirmModal(msg),
+ (url) => {
+ // console.log('업로드 완료 URL:', url);
+ setPreview((prev) => [...prev, url]);
+ }
+ );
+ function getFilesValue(e: React.ChangeEvent) {
+ const file = e.target.files?.[0];
+ if (!file) return;
+
+ if (preview.length >= MAX_IMAGE_COUNT) {
+ setErrorCase(`*이미지 등록은 최대 ${MAX_IMAGE_COUNT}개까지 가능합니다.`);
+ setTimeout(() => setErrorCase(''), 2000);
+ return;
+ }
+
+ // 2. 서버 업로드 요청 → 성공 시 URL 저장
+ uploadImage(file, {
+ onSuccess: (url) => {
+ setProduct((prev) => ({
+ ...prev,
+ images: [...(prev.images || []), url],
+ }));
+ },
+ onError: (msg) => {
+ setErrorCase(msg);
+ setTimeout(() => setErrorCase(''), 2000);
+ },
+ });
+}
+
+// 미리보기 & URL 동기화 삭제
+function handleClickImgDelete(index: number) {
+ if (preview.length > 0) setErrorCase('');
+
+ // 삭제 시 preview와 images 모두 index 기준 삭제
+ setPreview((prev) => prev.filter((_, i) => i !== index));
+ setProduct((prev) => ({
+ ...prev,
+ images: (prev.images || []).filter((_, i) => i !== index),
+ }));
+}
+
+ return(
+ <>
+
+
+ >
+ )
+}
+
+export default ImageFileBox;
\ No newline at end of file
diff --git a/src/components/ui/form/InputBox.module.css b/src/components/ui/form/InputBox.module.css
new file mode 100644
index 00000000..89a77713
--- /dev/null
+++ b/src/components/ui/form/InputBox.module.css
@@ -0,0 +1,39 @@
+.label {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+.label span {
+ line-height: 26px;
+ font-size: 18px;
+ font-weight: 700;
+}
+.textarea,
+.input {
+ padding: 16px 24px;
+ background-color: var(--Cool_Gray_100);
+ border-radius: 12px;
+ font-size: 16px;
+ border: 0;
+ outline: 0;
+}
+
+.textarea:hover,
+.textarea:active,
+.textarea:focus,
+.input:hover,
+.input:active,
+.input:focus {
+ border: 0;
+ outline: 0;
+}
+
+.input[type="number"]::-webkit-outer-spin-button,
+.input[type="number"]::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+}
+
+.input[type="number"] {
+ -moz-appearance: textfield;
+}
diff --git a/src/components/ui/form/InputBox.tsx b/src/components/ui/form/InputBox.tsx
new file mode 100644
index 00000000..92b25a60
--- /dev/null
+++ b/src/components/ui/form/InputBox.tsx
@@ -0,0 +1,60 @@
+
+import React from 'react';
+import styles from './InputBox.module.css';
+import clsx from 'clsx';
+
+
+interface TextAreaBoxProps {
+ placeholder?: string;
+ height?: string | number;
+ [key: string]: any;
+}
+export function InputBox({ inputBoxType='text', placeholder, ...rest }: TextAreaBoxProps) {
+ return (
+
+ );
+}
+
+export function InputField({ label, inputBoxType, placeholder, ...rest }: TextAreaBoxProps) {
+ return (
+
+ );
+}
+
+
+export function TextAreaBox({ placeholder, height, ...rest }: TextAreaBoxProps) {
+ return (
+
+ );
+}
+
+export function TextAreaField({ label, placeholder, height, ...rest }: TextAreaBoxProps) {
+ return (
+
+ );
+}
diff --git a/src/components/ui/mainSelection.tsx b/src/components/ui/mainSelection.tsx
new file mode 100644
index 00000000..31697e7d
--- /dev/null
+++ b/src/components/ui/mainSelection.tsx
@@ -0,0 +1,38 @@
+import React from 'react';
+import { motion } from 'framer-motion';
+import Container from '../layout/Container';
+import clsx from 'clsx';
+
+interface MotionSelectionProps {
+ className?: string;
+ children?: React.ReactNode;
+ [key: string]: any;
+}
+
+export function VisualSelection({ className, children }: MotionSelectionProps) {
+
+ return (
+
+ {children}
+
+ )
+}
+export function MotionSelection({ className, children }: MotionSelectionProps) {
+ return (
+ <>
+
+
+
+ {children}
+
+
+
+ >
+ );
+}
diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx
new file mode 100644
index 00000000..e089a5d0
--- /dev/null
+++ b/src/contexts/AuthContext.tsx
@@ -0,0 +1,68 @@
+'use client';
+
+import React, { createContext, useContext, useEffect, useState } from 'react';
+import { parseJwt, DecodedToken } from '@/utils/parseJwt';
+
+interface AuthUser {
+ id: number;
+ nickname: string;
+ email: string;
+ image: string | null;
+}
+
+interface AuthContextType {
+ token: DecodedToken | null;
+ user: AuthUser | null;
+ setUser: (user: AuthUser | null) => void;
+ setToken: (token: DecodedToken | null) => void;
+ logout: () => void;
+}
+
+const AuthContext = createContext(undefined);
+
+export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
+ const [token, setToken] = useState(null);
+ const [user, setUser] = useState(null);
+
+ useEffect(() => {
+ const token = localStorage.getItem('accessToken');
+ const refreshToken = localStorage.getItem('refreshToken');
+ const rawUser = localStorage.getItem('user');
+
+ console.log('token', token);
+ console.log('refreshToken', refreshToken);
+ console.log('rawUser', rawUser);
+
+ if (token) {
+ const parsedToken = parseJwt(token);
+
+ if (parsedToken && parsedToken.exp > Date.now() / 1000) {
+ if (rawUser) {
+ const user = JSON.parse(rawUser);
+ setUser(user);
+ }
+ } else {
+ localStorage.removeItem('accessToken');
+ localStorage.removeItem('user');
+ }
+ }
+ }, []);
+ const logout = () => {
+ localStorage.removeItem('accessToken');
+ localStorage.removeItem('refreshToken');
+ setUser(null);
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useAuth = () => {
+ const context = useContext(AuthContext);
+ if (!context) throw new Error('useAuth must be used within AuthProvider');
+
+ return context;
+};
diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts
new file mode 100644
index 00000000..11d32486
--- /dev/null
+++ b/src/hooks/useAuth.ts
@@ -0,0 +1,122 @@
+'use client';
+
+import { useMutation } from '@tanstack/react-query';
+import { useRouter } from 'next/navigation';
+import axios from 'axios';
+import { requestor } from '@/lib/requestor';
+import { useAuth } from '@/contexts/AuthContext';
+import { useEffect, useState } from 'react';
+import { parseJwt } from '@/utils/parseJwt';
+
+interface SignUpForm {
+ email: string;
+ nickname: string;
+ password: string;
+ passwordConfirmation: string;
+}
+export function useSignUp(openModal: (msg: string) => void) {
+ const router = useRouter();
+ const { setUser } = useAuth();
+ const [isModalOpen, setIsModalOpen] = useState(false);
+
+ const [savedPath, setSavedPath] = useState(null);
+
+ useEffect(() => {
+ const path = sessionStorage.getItem('redirectPath');
+ setSavedPath(path);
+ }, []);
+
+ const mutation = useMutation({
+ mutationFn: async (form: SignUpForm) => {
+ const res = await requestor.post('/auth/signUp', form);
+ return res.data;
+ },
+ onSuccess: (data) => {
+ const { accessToken, refreshToken, user } = data;
+
+ localStorage.setItem('accessToken', accessToken);
+ localStorage.setItem('refreshToken', refreshToken);
+ localStorage.setItem('user', JSON.stringify(user));
+ setUser(user);
+
+ openModal('회원가입 완료 되었습니다!');
+ if(savedPath){
+ router.push(savedPath);
+ } else {
+ router.push('/');
+ }
+ },
+ onError: (error) => {
+ if (axios.isAxiosError(error) && error.response) {
+ openModal(error.response.data.message || '회원가입 중 오류가 발생했습니다.');
+ } else {
+ openModal('서버 연결에 실패했습니다.');
+ }
+ },
+ });
+
+ return {
+ ...mutation,
+ isModalOpen,
+ closeModal: () => setIsModalOpen(false),
+ };
+}
+
+
+interface LoginForm {
+ email: string;
+ password: string;
+}
+
+export function useLoginMutation(openModal: (msg: string) => void) {
+ const router = useRouter();
+ const { setToken, setUser } = useAuth();
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [savedPath, setSavedPath] = useState(null);
+
+ useEffect(() => {
+ const path = sessionStorage.getItem('redirectPath');
+ setSavedPath(path);
+ }, []);
+
+ const mutation = useMutation({
+ mutationFn: async (form: LoginForm) => {
+ const res = await requestor.post('/auth/signIn', form);
+ return res.data;
+ },
+ onSuccess: (data) => {
+ const { accessToken, refreshToken, user } = data;
+
+ localStorage.setItem('accessToken', accessToken);
+ localStorage.setItem('refreshToken', refreshToken);
+ localStorage.setItem('user', JSON.stringify(user));
+
+ const decoded = parseJwt(accessToken);
+ if (decoded) {
+ setToken(decoded);
+ setUser(user);
+ }
+
+ openModal('로그인 되었습니다!');
+ if(savedPath){
+ router.push(savedPath);
+ } else {
+ router.push('/');
+ }
+ },
+ onError: (error) => {
+ if (axios.isAxiosError(error) && error.response) {
+ openModal(error.response.data.message || '로그인 실패');
+ } else {
+ openModal('네트워크 오류가 발생했습니다.');
+ }
+ },
+ });
+
+ return {
+ ...mutation,
+ isModalOpen,
+ closeModal: () => setIsModalOpen(false),
+ };
+}
+
diff --git a/src/hooks/useBreakpoint.tsx b/src/hooks/useBreakpoint.tsx
new file mode 100644
index 00000000..113e7063
--- /dev/null
+++ b/src/hooks/useBreakpoint.tsx
@@ -0,0 +1,23 @@
+import { useEffect, useState } from "react";
+
+export type Breakpoint = "mobile" | "tablet" | "desktop";
+
+export function useBreakpoint(): Breakpoint {
+ const [breakpoint, setBreakpoint] = useState("desktop");
+
+ useEffect(() => {
+ const handleResize = () => {
+ const width = window.innerWidth;
+
+ if (width <= 767) setBreakpoint("mobile");
+ else if (width <= 1199) setBreakpoint("tablet");
+ else setBreakpoint("desktop");
+ };
+
+ handleResize(); // 최초 실행
+ window.addEventListener("resize", handleResize);
+ return () => window.removeEventListener("resize", handleResize);
+ }, []);
+
+ return breakpoint;
+}
diff --git a/src/hooks/useItemQuery.tsx b/src/hooks/useItemQuery.tsx
new file mode 100644
index 00000000..847f816c
--- /dev/null
+++ b/src/hooks/useItemQuery.tsx
@@ -0,0 +1,68 @@
+import { useMemo } from "react";
+import { useRouter } from "next/navigation";
+import { ProductQuery, useItemList } from "./useItems";
+
+
+export const useItemService = (
+ defaultQuery :ProductQuery,
+ searchParams: URLSearchParams
+) => {
+ const parsedQuery = useParsedItemQuery(defaultQuery, searchParams);
+ const queryResult = useItemList(parsedQuery);
+ return {
+ ...queryResult,
+ };
+};
+
+export const useParsedItemQuery = (
+ defaultQuery :ProductQuery,
+ searchParams: URLSearchParams
+) => {
+ if (!searchParams) return defaultQuery;
+ const parsedQuery = useMemo(() => {
+ const obj: Record = {};
+
+ for (const [key, value] of searchParams.entries()) {
+ // console.log("searchParams", `${key} = ${value}`);
+ if (value === "true") obj[key] = true;
+ else if (value === "false") obj[key] = false;
+ else if (!isNaN(Number(value))) obj[key] = Number(value);
+ else obj[key] = value;
+ }
+
+ return {
+ ...defaultQuery,
+ ...obj,
+ };
+ }, [searchParams.toString()]); // URLSearchParams는 얕은 비교가 안 되므로 .toString()을 기준으로 해야 변경을 감지?
+
+ return parsedQuery;
+};
+
+export const useSetItemQuery = () => {
+ const router = useRouter();
+
+ const setQueryToURL = (query: Record) => {
+ const queryString = toQueryString(query);
+
+ if (router) {
+ router.push(`${queryString}`, { scroll: false });
+ }
+ };
+
+ return setQueryToURL;
+};
+
+export const toQueryString = (params: Record): string => {
+ const query = new URLSearchParams();
+ Object.entries(params).forEach(([key, value]) => {
+ if (value !== undefined && value !== null && value !== "") {
+ if (Array.isArray(value)) {
+ value.forEach((v) => query.append(key, String(v)));
+ } else {
+ query.append(key, String(value));
+ }
+ }
+ });
+ return `?${query.toString()}`;
+};
\ No newline at end of file
diff --git a/src/hooks/useItems.tsx b/src/hooks/useItems.tsx
new file mode 100644
index 00000000..7e59f207
--- /dev/null
+++ b/src/hooks/useItems.tsx
@@ -0,0 +1,136 @@
+import { requestor } from "@/lib/requestor";
+import { keepPreviousData, useMutation, useQuery } from "@tanstack/react-query";
+import { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime";
+import { useEffect, useState } from "react";
+
+export interface ProductQuery {
+ page: number; // 기본값 1
+ pageSize: number; // 기본값 10
+ orderBy: 'favorite' | 'recent'; // 기본값 'recent'
+ keyword?: string; // optional
+}
+export interface ProductSummary {
+ id?: number;
+ name?: string;
+ description?: string;
+ price?: number;
+ images?: string[];
+ tags?: string[];
+ ownerId?: number;
+ ownerNickname?: string;
+ favoriteCount?: number;
+ createdAt?: string; // ISO 문자열, 필요시 Date로 변환 가능
+}
+
+export interface ProductListResponse {
+ totalCount: number;
+ list: ProductSummary[];
+}
+
+export const useItemList = (query:ProductQuery) => {
+ return useQuery({
+ queryKey: ['Items', query],
+ queryFn: async () => {
+ const res = await requestor.get('/products', {
+ params: query,
+ });
+ return res.data ;
+ },
+ placeholderData: keepPreviousData,
+ });
+};
+
+// 상품 등록 요청 타입
+export interface CreateProductRequest {
+ images: string[];
+ tags: string[];
+ price: number;
+ description: string;
+ name: string;
+}
+
+// 상품 응답 타입
+export interface CreateProductResponse {
+ createdAt: string;
+ favoriteCount: number;
+ ownerNickname: string;
+ ownerId: number;
+ images: string[];
+ tags: string[];
+ price: number;
+ description: string;
+ name: string;
+ id: number;
+}
+
+// 상품 등록
+export function usePostProduct(openModal: (msg: string) => void, router: AppRouterInstance ) { // 사용에따라 router를 인자로 받음
+
+ return useMutation({
+ mutationFn: async (productData: CreateProductRequest) => {
+ const res = await requestor.post('/products', productData);
+ return res.data;
+ },
+ onSuccess: (product) => {
+ openModal('상품 등록이 완료되었습니다!');
+ router.push('/items');
+ },
+ onError: (error: any) => {
+ openModal(error?.response?.data?.message || '상품 등록 실패');
+ },
+ });
+};
+
+
+interface ProductFavoriteResponse {
+ productId: number;
+ isFavorited: boolean;
+ setIsFavorited: (value: boolean) => void,
+ setCount: (value: number | ((prev: number) => number)) => void
+}
+export const useToggleProductFavorite = (
+ openModal: (msg: string) => void
+) => {
+
+ return useMutation({
+ mutationFn: async ({ productId, isFavorited, setIsFavorited, setCount }:ProductFavoriteResponse ) => {
+ const token = localStorage.getItem('accessToken');
+ if (!token) {
+ openModal('로그인이 필요합니다.');
+ return Promise.reject('No accessToken');
+ }
+
+ setIsFavorited(!isFavorited);
+ setCount((prev: number) => isFavorited ? prev - 1 : prev + 1);
+
+ if (isFavorited) {
+ return requestor.delete(`/products/${productId}/favorite`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+ } else {
+ return requestor.post(
+ `/products/${productId}/favorite`,
+ {},
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ }
+ );
+ }
+ },
+ onSuccess: (_, variables) => {
+ openModal(variables.isFavorited ? '관심상품이 해제되었습니다!' : '관심상품이 등록되었습니다!');
+ },
+ onError: (error) => {
+ const message = (error as any)?.response?.data?.message;
+ if (message?.includes('jwt malformed')) {
+ openModal('로그인 후 등록 가능합니다!');
+ } else {
+ openModal(message || '관심상품 처리 실패');
+ }
+ },
+ });
+};
diff --git a/src/hooks/useModal.ts b/src/hooks/useModal.ts
new file mode 100644
index 00000000..b3abd132
--- /dev/null
+++ b/src/hooks/useModal.ts
@@ -0,0 +1,46 @@
+import { useState, useCallback } from 'react';
+
+export function useModal() {
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [modalMessage, setModalMessage] = useState('');
+
+ const openModal = useCallback((msg: string) => {
+ setModalMessage(msg);
+ setIsModalOpen(true);
+ }, []);
+
+ const closeModal = useCallback(() => {
+ setIsModalOpen(false);
+ setModalMessage('');
+ }, []);
+
+ return {
+ isModalOpen,
+ modalMessage,
+ openModal,
+ closeModal
+ };
+}
+
+
+export function useConfirmModal() {
+ const [isConfirmOpen, setIsConfirmOpen] = useState(false);
+ const [confirmMessage, setConfirmMessage] = useState('');
+
+ const openConfirmModal = useCallback((msg: string) => {
+ setConfirmMessage(msg);
+ setIsConfirmOpen(true);
+ }, []);
+
+ const closeConfirmModal = useCallback(() => {
+ setIsConfirmOpen(false);
+ setConfirmMessage('');
+ }, []);
+
+ return {
+ isConfirmOpen,
+ confirmMessage,
+ openConfirmModal,
+ closeConfirmModal,
+ };
+}
\ No newline at end of file
diff --git a/src/hooks/usePagination.ts b/src/hooks/usePagination.ts
new file mode 100644
index 00000000..82a2639b
--- /dev/null
+++ b/src/hooks/usePagination.ts
@@ -0,0 +1,62 @@
+import { useEffect, useMemo, useState } from 'react'
+
+interface UsePaginationProps {
+ offset?: number | undefined
+ limit?: number | undefined
+ totalCount?: number | undefined
+ pageRange?: number // 보여줄 페이지 버튼 수 (기본값: 5)
+ setQuery: (query: { offset: number; limit: number }) => void
+}
+
+export const usePagination = ({ offset = 0, limit = 10, totalCount = 0, pageRange = 5, setQuery }: UsePaginationProps) => {
+ const currentPage = Math.floor(offset / limit) + 1 // 현재페이지
+ const totalPages = Math.ceil(totalCount / limit) // 총 페이지
+
+ const [pageGroupStart, setPageGroupStart] = useState(1)
+
+ useEffect(() => {
+ const newGroupStart = Math.floor((currentPage - 1) / pageRange) * pageRange + 1
+ setPageGroupStart(newGroupStart)
+ }, [currentPage, pageRange])
+
+ const pageList = useMemo(() => {
+ const end = Math.min(pageGroupStart + pageRange - 1, totalPages)
+ return Array.from({ length: end - pageGroupStart + 1 }, (_, i) => pageGroupStart + i)
+ }, [pageGroupStart, totalPages, pageRange])
+
+
+ const goToPage = (page: number) => {
+ const newOffset = (page - 1) * limit
+ setQuery({ offset: newOffset, limit })
+ }
+
+ const goToPrev = () => {
+ const prevPage = currentPage - pageRange;
+ if (prevPage < 1) {
+ setQuery({ offset: 0, limit })
+ } else {
+ const prevOffset = (prevPage - 1) * limit
+ setQuery({ offset: prevOffset, limit })
+ }
+ }
+
+
+ const goToNext = () => {
+ const nextPage = currentPage + pageRange
+ if (nextPage > totalPages) {
+ const lastOffset = (totalPages - 1) * limit
+ setQuery({ offset: lastOffset, limit })
+ } else {
+ const nextOffset = (nextPage - 1) * limit
+ setQuery({ offset: nextOffset, limit })
+ }
+ }
+ return {
+ currentPage,
+ totalPages,
+ pageList,
+ goToPage,
+ goToNext,
+ goToPrev
+ }
+}
diff --git a/src/hooks/useProductsComments.tsx b/src/hooks/useProductsComments.tsx
new file mode 100644
index 00000000..f1ae10ff
--- /dev/null
+++ b/src/hooks/useProductsComments.tsx
@@ -0,0 +1,146 @@
+import { requestor } from '@/lib/requestor';
+import {useInfiniteQuery, useMutation, useQueryClient } from '@tanstack/react-query';
+
+
+// 댓글 작성자 정보
+export interface CommentWriter {
+ id: number;
+ nickname: string;
+ image: string | null;
+}
+
+// 댓글 아이템
+export interface CommentItemUnit {
+ id: number;
+ content: string;
+ createdAt: string;
+ updatedAt: string;
+ writer: CommentWriter;
+}
+
+// 전체 응답 타입
+export interface CommentListResponse {
+ list: CommentItemUnit[];
+ nextCursor: number;
+}
+
+export interface GetCommentsQuery {
+ limit: number;
+ cursor?: number;
+}
+
+export const useInfiniteProductsComments = (productId: number, limit = 10) => {
+ return useInfiniteQuery({
+ queryKey: ['productComments', productId],
+ queryFn: async ({ pageParam }) => {
+ const res = await requestor.get(`/products/${productId}/comments`, {
+ params: {
+ limit,
+ cursor: pageParam ?? null,
+ },
+ });
+ return res.data;
+ },
+ initialPageParam: null,
+ getNextPageParam: (lastPage) => lastPage.nextCursor ?? null,
+ });
+};
+
+export const usePostProductComment = (productId: number, openModal: (msg: string) => void) => {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (requestCommentValue: string | undefined) => {
+ const token = localStorage.getItem('accessToken');
+ if (!token) {
+ openModal('로그인이 필요합니다.');
+ return Promise.reject('No accessToken');
+ }
+ return requestor.post(
+ `/products/${productId}/comments`,
+ { content: requestCommentValue },
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ }
+ );
+ },
+ onSuccess: () => {
+ openModal('댓글이 등록되었습니다!');
+ queryClient.invalidateQueries({ queryKey: ['productComments', productId] });
+ },
+ onError: (error: any) => {
+ const message = error?.response?.data?.message;
+ if (message?.includes('jwt malformed')) {
+ openModal('로그인 후 등록 가능합니다!');
+ } else {
+ openModal(message || '댓글 등록 실패');
+ }
+ },
+ });
+};
+
+export const usePatchProductComment = (productId: number, openModal: (msg: string) => void) => {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: ({ commentId, requestCommentValue }: { commentId: number; requestCommentValue: string }) => {
+ const token = localStorage.getItem('accessToken');
+ if (!token) {
+ openModal('로그인이 필요합니다.');
+ return Promise.reject('No accessToken');
+ }
+ return requestor.patch(
+ `/comments/${commentId}`,
+ { content: requestCommentValue },
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ }
+ );
+ },
+ onSuccess: () => {
+ openModal('댓글이 수정되었습니다!');
+ queryClient.invalidateQueries({ queryKey: ['productComments', productId] });
+ },
+ onError: (error: any) => {
+ const message = error?.response?.data?.message;
+ if (message?.includes('jwt malformed')) {
+ openModal('로그인 후 수정 가능합니다!');
+ } else {
+ openModal(error?.response?.data?.message || '댓글 수정 실패');
+ }
+ },
+ });
+};
+export const useDeleteCommentMutation = (productId: number, openModal: (msg: string) => void) => {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (commentId: number) => {
+ const token = localStorage.getItem('accessToken');
+ if (!token) {
+ openModal('로그인이 필요합니다.');
+ return Promise.reject('No accessToken');
+ }
+ return requestor.delete(`/comments/${commentId}`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['productComments', productId] });
+ },
+ onError: (error: any) => {
+ const message = error?.response?.data?.message;
+ if (message?.includes('jwt malformed')) {
+ openModal('로그인 후 등록 가능합니다!');
+ } else {
+ openModal(message || '댓글 삭제 실패');
+ }
+ },
+ });
+};
diff --git a/src/hooks/useProductsDetail.tsx b/src/hooks/useProductsDetail.tsx
new file mode 100644
index 00000000..8314095b
--- /dev/null
+++ b/src/hooks/useProductsDetail.tsx
@@ -0,0 +1,27 @@
+import { requestor } from "@/lib/requestor";
+import { keepPreviousData, useQuery } from "@tanstack/react-query";
+
+export interface ProductDetail {
+ id: number;
+ name: string;
+ description: string;
+ price: number;
+ images: string[]; // 이미지 URL 리스트
+ tags: string[]; // 태그 리스트 (예: ["전자제품"])
+ isFavorite: boolean; // 사용자가 찜한 여부
+ favoriteCount: number; // 총 찜 수
+ createdAt: string; // ISO 시간 문자열
+ ownerId: number;
+ ownerNickname: string;
+}
+
+export const useProductsDetails = (productId:number) => {
+ return useQuery({
+ queryKey: ['ItemsDetails', productId],
+ queryFn: async () => {
+ const res = await requestor.get(`/products/${productId}`);
+ return res.data;
+ },
+ placeholderData: keepPreviousData,
+ });
+};
diff --git a/src/hooks/useScreenType.tsx b/src/hooks/useScreenType.tsx
new file mode 100644
index 00000000..fa560b9d
--- /dev/null
+++ b/src/hooks/useScreenType.tsx
@@ -0,0 +1,63 @@
+'use client';
+import { useState, useEffect, useCallback, useRef } from 'react';
+
+const screenTypeValue = {
+ mobile: 767,
+ tablet: 1199,
+};
+
+const getScreenType = (winWidth: number) =>
+ winWidth < screenTypeValue.mobile
+ ? 0
+ : winWidth < screenTypeValue.tablet
+ ? 1
+ : 2;
+
+export const useScreenType = () => {
+
+ const winWidth = useWindowWidth();
+
+
+ const [screenType, setScreenType] = useState(() =>
+ typeof window !== 'undefined' ? getScreenType(winWidth) : 2
+ );
+
+ const resizeTimeout = useRef(null);
+
+ const handleResize = useCallback(() => {
+ if (resizeTimeout.current) {
+ clearTimeout(resizeTimeout.current);
+ }
+
+ resizeTimeout.current = window.setTimeout(() => {
+ const width = window.innerWidth;
+ const screenCount = getScreenType(width);
+ setScreenType(screenCount);
+ }, 100);
+ }, []);
+
+ useEffect(() => {
+ window.addEventListener('resize', handleResize);
+ return () => {
+ window.removeEventListener('resize', handleResize);
+ if (resizeTimeout.current) clearTimeout(resizeTimeout.current);
+ };
+ }, [handleResize]);
+
+ return screenType;
+};
+
+export const useWindowWidth = () => {
+ const [width, setWidth] = useState(() =>
+ typeof window !== 'undefined' ? window.innerWidth : 0
+ );
+
+ useEffect(() => {
+ const handleResize = () => setWidth(window.innerWidth);
+ window.addEventListener('resize', handleResize);
+
+ return () => window.removeEventListener('resize', handleResize);
+ }, []);
+
+ return width;
+};
\ No newline at end of file
diff --git a/src/hooks/useUploadImage.ts b/src/hooks/useUploadImage.ts
new file mode 100644
index 00000000..af8fc096
--- /dev/null
+++ b/src/hooks/useUploadImage.ts
@@ -0,0 +1,29 @@
+import { useMutation } from '@tanstack/react-query';
+import { requestor } from '@/lib/requestor'; // axios 인스턴스
+
+
+
+export const useUploadImage = (openModal: (msg: string) => void ,onSuccessCallback: (url: string) => void) => {
+ return useMutation({
+ mutationFn: async ( file:File ) => {
+ const formData = new FormData();
+ formData.append('image', file);
+
+ const res = await requestor.post<{ url: string }>('/images/upload', formData, {
+ headers: { 'Content-Type': 'multipart/form-data' },
+ });
+
+ return res.data.url;
+ },
+
+ onSuccess: (url) => {
+ onSuccessCallback(url);
+ },
+
+ // 💡 openModal은 mutation 호출 시 외부에서 핸들링 권장 (mutationFn에 묶는 건 anti-pattern일 수 있음)
+ onError: (error: any) => {
+ const message = error?.response?.data?.message || '이미지 업로드 실패';
+ openModal(message);
+ },
+ });
+};
diff --git a/src/hooks/useUser.ts b/src/hooks/useUser.ts
new file mode 100644
index 00000000..87796890
--- /dev/null
+++ b/src/hooks/useUser.ts
@@ -0,0 +1,60 @@
+import { requestor } from "@/lib/requestor";
+import { keepPreviousData, useQuery } from "@tanstack/react-query";
+
+export interface UserFavorite {
+ id: number;
+ name: string;
+ description: string;
+ price: number;
+ images: string[];
+ tags: string[];
+ favoriteCount: number;
+ ownerId: number;
+ ownerNickname: string;
+ createdAt: string;
+}
+
+export interface UserFavorites {
+ totalCount: number;
+ list: UserFavorite[];
+}
+
+export interface UserFavoritesQuery {
+ page?: number;
+ pageSize?: number;
+ keyword?: string;
+}
+
+export const useGetUserFavorites = (query:UserFavoritesQuery) => {
+ const token = localStorage.getItem('accessToken');
+ return useQuery({
+ queryKey: ['UserFavorites', query],
+ queryFn: async () => {
+ const res = await requestor.get('/users/me/favorites', {
+ params: query,
+ headers: {
+ Authorization: `Bearer ${token}`
+ }
+ });
+ return res.data ;
+ },
+ placeholderData: keepPreviousData,
+ });
+};
+
+export const useGetUserProducts = (query:UserFavoritesQuery) => {
+ const token = localStorage.getItem('accessToken');
+ return useQuery({
+ queryKey: ['UserProducts', query],
+ queryFn: async () => {
+ const res = await requestor.get('/users/me/Products', {
+ params: query,
+ headers: {
+ Authorization: `Bearer ${token}`
+ }
+ });
+ return res.data ;
+ },
+ placeholderData: keepPreviousData,
+ });
+};
diff --git a/src/lib/imageAssets.ts b/src/lib/imageAssets.ts
new file mode 100644
index 00000000..e28d04cc
--- /dev/null
+++ b/src/lib/imageAssets.ts
@@ -0,0 +1,46 @@
+
+/** 허용된 외부 이미지 도메인 (next.config.ts, 클라이언트 공통 사용) */
+export const allowedImageDomains = [
+ "cdn.wccftech.com",
+ "example.com",
+ "image.hanatour.com",
+ "cdn.choicenews.co.kr",
+ "sprint-fe-project.s3.ap-northeast-2.amazonaws.com",
+ "cdn.pixabay.com",
+ "i.pinimg.com",
+ "upload.wikimedia.org",
+ "image.hanatour.com",
+ "encrypted-tbn0.gstatic.com",
+ "health.chosun.com",
+ "via.placeholder.com",
+ "images.unsplash.com",
+];
+
+/** 허용된 이미지 확장자 (정규식 검사용) */
+export const imageExtensionRegex = /\.(jpe?g|png|webp|gif)$/i;
+
+/** fallback 이미지 경로 */
+export const defaultImg = '/assets/img/img_default_2x.png';
+
+export const logoImg1 = '/assets/logo_01.svg';
+export const logoImg2 = '/assets/logo_03.svg';
+
+export const emptyImg = '/assets/img/Img_inquiry_empty_2x.png';
+export const imgHome_top = '/assets/Img_home_top.png';
+export const imgHome1 = '/assets/Img_home_01.png';
+export const imgHome2 = '/assets/Img_home_02.png';
+export const imgHome3 = '/assets/Img_home_03.png';
+export const imgHome_bottom = '/assets/Img_home_bottom.png';
+
+export const eyeOpen = '/assets/eye_1.svg';
+export const eyeClose = '/assets/eye_2.svg';
+
+export const facebookIcon = '/assets/ic_facebook.svg';
+export const twitterIcon = '/assets/ic_twitter.svg';
+export const instagramIcon = '/assets/ic_instagram.svg';
+export const youtubeIcon = '/assets/ic_youtube.svg';
+
+export const sns_google = '/assets/gg_icon.png';
+export const sns_kakao = '/assets/kakao_icon.png';
+
+export const tempUserImg = '/assets/ic_3_01.png';
\ No newline at end of file
diff --git a/src/lib/react-query.ts b/src/lib/react-query.ts
new file mode 100644
index 00000000..008af6db
--- /dev/null
+++ b/src/lib/react-query.ts
@@ -0,0 +1,10 @@
+import { QueryClient } from '@tanstack/react-query';
+
+export const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ retry: 1,
+ refetchOnWindowFocus: false,
+ },
+ },
+});
\ No newline at end of file
diff --git a/src/lib/requestor.ts b/src/lib/requestor.ts
new file mode 100644
index 00000000..5d534eb3
--- /dev/null
+++ b/src/lib/requestor.ts
@@ -0,0 +1,52 @@
+import axios from 'axios';
+
+export const requestor = axios.create({
+ baseURL: process.env.NEXT_PUBLIC_API_URL,
+ timeout: 60000,
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+ },
+});
+
+// response interceptor
+requestor.interceptors.response.use(
+ (response) => response,
+ async (error) => {
+ const originalRequest = error.config;
+ if (error.response?.status === 401 && !originalRequest._retry) {
+ originalRequest._retry = true;
+ try {
+ const refreshToken = localStorage.getItem('refreshToken');
+ if (!refreshToken) throw new Error('리프레시 토큰 없음');
+
+ const res = await requestor.post('/auth/refresh-token', { refreshToken });
+ const { accessToken, user } = res.data;
+
+ localStorage.setItem('accessToken', accessToken);
+ if (user) {
+ localStorage.setItem('user', JSON.stringify(user));
+ }
+ originalRequest.headers.Authorization = `Bearer ${accessToken}`;
+ return requestor(originalRequest);
+ } catch (refreshError) {
+ console.error('리프레시 실패 → 자동 로그아웃 필요');
+ localStorage.removeItem('accessToken');
+ localStorage.removeItem('refreshToken');
+ localStorage.removeItem('user');
+ window.location.href = '/login'; // ⬅️ 강제 로그인 페이지 이동
+ return Promise.reject(refreshError);
+ }
+ }
+ return Promise.reject(error);
+ }
+);
+
+// 요청 시 Authorization 자동 추가 (권장)
+requestor.interceptors.request.use((config) => {
+ const token = localStorage.getItem('accessToken');
+ if (token) {
+ config.headers.Authorization = `Bearer ${token}`;
+ }
+ return config;
+});
\ No newline at end of file
diff --git a/src/utils/auth.ts b/src/utils/auth.ts
new file mode 100644
index 00000000..1d8cd7df
--- /dev/null
+++ b/src/utils/auth.ts
@@ -0,0 +1,62 @@
+
+export const MEMBER_MESSAGE = {
+ enterEmail : '이메일을 입력해주세요.',
+ wrongEmail : '잘못된 이메일 형식입니다.',
+ enterName : '닉네임을 입력해주세요.',
+ enterPassword : '비밀번호를 입력해주세요.',
+ checkPassword : '비밀번호를 8자 이상 입력해주세요.',
+ wrongPassword : '비밀번호가 일치하지 않습니다.',
+}
+
+export const memberCheck = {
+ PasswordLength : 8,
+ checkEmail : (email: string) => {
+ const emailRegex = /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/;
+ return emailRegex.test(email);
+ },
+ checkPassword : (password: string) => {
+ return password.length >= memberCheck.PasswordLength;
+ },
+ EmailChecked : (email: string) => {
+ let errorMessage = '';
+ if(email === '') {
+ errorMessage = MEMBER_MESSAGE.enterEmail;
+ } else if ( !memberCheck.checkEmail(email)) {
+ errorMessage = MEMBER_MESSAGE.wrongEmail;
+ } else {
+ errorMessage = '';
+ }
+ return errorMessage;
+ },
+ NameChecked: (name: string) => {
+ let errorMessage = '';
+ if(name === '') {
+ errorMessage = MEMBER_MESSAGE.enterName;
+ } else {
+ errorMessage = '';
+ }
+ return errorMessage;
+ },
+ passwordChecked: (password: string) => {
+ let errorMessage = '';
+ if(password === '') {
+ errorMessage = MEMBER_MESSAGE.enterPassword;
+ } else if ( !memberCheck.checkPassword(password)) {
+ errorMessage = MEMBER_MESSAGE.checkPassword;
+ } else {
+ errorMessage = '';
+ }
+ return errorMessage;
+ },
+ passwordDoubleChecked: (password: string, passwordDouble: string) => {
+ let errorMessage = '';
+ if(password === '') {
+ errorMessage = MEMBER_MESSAGE.enterPassword;
+ } else if(password !== passwordDouble) {
+ errorMessage = MEMBER_MESSAGE.wrongPassword;
+ } else {
+ errorMessage = '';
+ }
+ return errorMessage;
+ },
+}
\ No newline at end of file
diff --git a/src/utils/date.ts b/src/utils/date.ts
new file mode 100644
index 00000000..537519ed
--- /dev/null
+++ b/src/utils/date.ts
@@ -0,0 +1,36 @@
+
+export function formatDate(dateStr: string) {
+ if (!dateStr) return '';
+
+ const inputDate = new Date(dateStr);
+ if (isNaN(inputDate.getTime())) return '';
+
+ const now = new Date();
+ const diffMs = now.getTime() - inputDate.getTime();
+ const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
+
+ if (diffDays === 0) {
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
+ const diffMinutes = Math.floor(diffMs / (1000 * 60));
+ // console.log(diffHours, diffMinutes);
+ if (diffHours >= 1) {
+ return `${diffHours}시간 전`;
+ } else if (diffMinutes >= 1) {
+ return `${diffMinutes}분 전`;
+ } else {
+ return '방금 전';
+ }
+ } else if (diffDays === 1) {
+ return '1일 전';
+ } else if (diffDays === 2) {
+ return '2일 전';
+ } else if (diffDays === 3) {
+ return '3일 전';
+ } else {
+ // 날짜 포맷: 2024. 01. 02
+ const yyyy = inputDate.getFullYear();
+ const mm = String(inputDate.getMonth() + 1).padStart(2, '0');
+ const dd = String(inputDate.getDate()).padStart(2, '0');
+ return `${yyyy}. ${mm}. ${dd}`;
+ }
+}
\ No newline at end of file
diff --git a/src/utils/parseJwt.ts b/src/utils/parseJwt.ts
new file mode 100644
index 00000000..603b0e37
--- /dev/null
+++ b/src/utils/parseJwt.ts
@@ -0,0 +1,18 @@
+export interface DecodedToken {
+ sub: string;
+ exp: number;
+ iat: number;
+ nickname?: string;
+ email?: string;
+ role?: 'user';
+}
+
+export const parseJwt = (token: string): DecodedToken | null => {
+ try {
+ const payload = token.split('.')[1];
+ const decoded = atob(payload);
+ return JSON.parse(decoded) as DecodedToken;
+ } catch {
+ return null;
+ }
+};
\ No newline at end of file
diff --git a/styles/Home.module.css b/styles/Home.module.css
deleted file mode 100644
index 6676d2c6..00000000
--- a/styles/Home.module.css
+++ /dev/null
@@ -1,229 +0,0 @@
-.main {
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- align-items: center;
- padding: 6rem;
- min-height: 100vh;
-}
-
-.description {
- display: inherit;
- justify-content: inherit;
- align-items: inherit;
- font-size: 0.85rem;
- max-width: var(--max-width);
- width: 100%;
- z-index: 2;
- font-family: var(--font-mono);
-}
-
-.description a {
- display: flex;
- justify-content: center;
- align-items: center;
- gap: 0.5rem;
-}
-
-.description p {
- position: relative;
- margin: 0;
- padding: 1rem;
- background-color: rgba(var(--callout-rgb), 0.5);
- border: 1px solid rgba(var(--callout-border-rgb), 0.3);
- border-radius: var(--border-radius);
-}
-
-.code {
- font-weight: 700;
- font-family: var(--font-mono);
-}
-
-.grid {
- display: grid;
- grid-template-columns: repeat(4, minmax(25%, auto));
- max-width: 100%;
- width: var(--max-width);
-}
-
-.card {
- padding: 1rem 1.2rem;
- border-radius: var(--border-radius);
- background: rgba(var(--card-rgb), 0);
- border: 1px solid rgba(var(--card-border-rgb), 0);
- transition: background 200ms, border 200ms;
-}
-
-.card span {
- display: inline-block;
- transition: transform 200ms;
-}
-
-.card h2 {
- font-weight: 600;
- margin-bottom: 0.7rem;
-}
-
-.card p {
- margin: 0;
- opacity: 0.6;
- font-size: 0.9rem;
- line-height: 1.5;
- max-width: 30ch;
-}
-
-.center {
- display: flex;
- justify-content: center;
- align-items: center;
- position: relative;
- padding: 4rem 0;
-}
-
-.center::before {
- background: var(--secondary-glow);
- border-radius: 50%;
- width: 480px;
- height: 360px;
- margin-left: -400px;
-}
-
-.center::after {
- background: var(--primary-glow);
- width: 240px;
- height: 180px;
- z-index: -1;
-}
-
-.center::before,
-.center::after {
- content: '';
- left: 50%;
- position: absolute;
- filter: blur(45px);
- transform: translateZ(0);
-}
-
-.logo {
- position: relative;
-}
-/* Enable hover only on non-touch devices */
-@media (hover: hover) and (pointer: fine) {
- .card:hover {
- background: rgba(var(--card-rgb), 0.1);
- border: 1px solid rgba(var(--card-border-rgb), 0.15);
- }
-
- .card:hover span {
- transform: translateX(4px);
- }
-}
-
-@media (prefers-reduced-motion) {
- .card:hover span {
- transform: none;
- }
-}
-
-/* Mobile */
-@media (max-width: 700px) {
- .content {
- padding: 4rem;
- }
-
- .grid {
- grid-template-columns: 1fr;
- margin-bottom: 120px;
- max-width: 320px;
- text-align: center;
- }
-
- .card {
- padding: 1rem 2.5rem;
- }
-
- .card h2 {
- margin-bottom: 0.5rem;
- }
-
- .center {
- padding: 8rem 0 6rem;
- }
-
- .center::before {
- transform: none;
- height: 300px;
- }
-
- .description {
- font-size: 0.8rem;
- }
-
- .description a {
- padding: 1rem;
- }
-
- .description p,
- .description div {
- display: flex;
- justify-content: center;
- position: fixed;
- width: 100%;
- }
-
- .description p {
- align-items: center;
- inset: 0 0 auto;
- padding: 2rem 1rem 1.4rem;
- border-radius: 0;
- border: none;
- border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
- background: linear-gradient(
- to bottom,
- rgba(var(--background-start-rgb), 1),
- rgba(var(--callout-rgb), 0.5)
- );
- background-clip: padding-box;
- backdrop-filter: blur(24px);
- }
-
- .description div {
- align-items: flex-end;
- pointer-events: none;
- inset: auto 0 0;
- padding: 2rem;
- height: 200px;
- background: linear-gradient(
- to bottom,
- transparent 0%,
- rgb(var(--background-end-rgb)) 40%
- );
- z-index: 1;
- }
-}
-
-/* Tablet and Smaller Desktop */
-@media (min-width: 701px) and (max-width: 1120px) {
- .grid {
- grid-template-columns: repeat(2, 50%);
- }
-}
-
-@media (prefers-color-scheme: dark) {
- .vercelLogo {
- filter: invert(1);
- }
-
- .logo {
- filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
- }
-}
-
-@keyframes rotate {
- from {
- transform: rotate(360deg);
- }
- to {
- transform: rotate(0deg);
- }
-}
diff --git a/styles/globals.css b/styles/globals.css
deleted file mode 100644
index d4f491e1..00000000
--- a/styles/globals.css
+++ /dev/null
@@ -1,107 +0,0 @@
-:root {
- --max-width: 1100px;
- --border-radius: 12px;
- --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono',
- 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro',
- 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace;
-
- --foreground-rgb: 0, 0, 0;
- --background-start-rgb: 214, 219, 220;
- --background-end-rgb: 255, 255, 255;
-
- --primary-glow: conic-gradient(
- from 180deg at 50% 50%,
- #16abff33 0deg,
- #0885ff33 55deg,
- #54d6ff33 120deg,
- #0071ff33 160deg,
- transparent 360deg
- );
- --secondary-glow: radial-gradient(
- rgba(255, 255, 255, 1),
- rgba(255, 255, 255, 0)
- );
-
- --tile-start-rgb: 239, 245, 249;
- --tile-end-rgb: 228, 232, 233;
- --tile-border: conic-gradient(
- #00000080,
- #00000040,
- #00000030,
- #00000020,
- #00000010,
- #00000010,
- #00000080
- );
-
- --callout-rgb: 238, 240, 241;
- --callout-border-rgb: 172, 175, 176;
- --card-rgb: 180, 185, 188;
- --card-border-rgb: 131, 134, 135;
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- --foreground-rgb: 255, 255, 255;
- --background-start-rgb: 0, 0, 0;
- --background-end-rgb: 0, 0, 0;
-
- --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
- --secondary-glow: linear-gradient(
- to bottom right,
- rgba(1, 65, 255, 0),
- rgba(1, 65, 255, 0),
- rgba(1, 65, 255, 0.3)
- );
-
- --tile-start-rgb: 2, 13, 46;
- --tile-end-rgb: 2, 5, 19;
- --tile-border: conic-gradient(
- #ffffff80,
- #ffffff40,
- #ffffff30,
- #ffffff20,
- #ffffff10,
- #ffffff10,
- #ffffff80
- );
-
- --callout-rgb: 20, 20, 20;
- --callout-border-rgb: 108, 108, 108;
- --card-rgb: 100, 100, 100;
- --card-border-rgb: 200, 200, 200;
- }
-}
-
-* {
- box-sizing: border-box;
- padding: 0;
- margin: 0;
-}
-
-html,
-body {
- max-width: 100vw;
- overflow-x: hidden;
-}
-
-body {
- color: rgb(var(--foreground-rgb));
- background: linear-gradient(
- to bottom,
- transparent,
- rgb(var(--background-end-rgb))
- )
- rgb(var(--background-start-rgb));
-}
-
-a {
- color: inherit;
- text-decoration: none;
-}
-
-@media (prefers-color-scheme: dark) {
- html {
- color-scheme: dark;
- }
-}
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 00000000..f7c6d3fc
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,46 @@
+module.exports = {
+ content: ["./src/**/*.{js,jsx,ts,tsx}"],
+ theme: {
+ extend: {
+ boxShadow: {
+ "soft-xl": "0 6px 6px rgba(0, 0, 0, 0.05)", // shadow-soft-xl
+ },
+ minWidth: {
+ base: "325px", // min-w-base
+ },
+ maxWidth: {
+ container: "1200px", // max-w-container
+ },
+ screens: {
+ desktop: "1200px", // 데스크탑: 1200px 이상
+ tablet: { max: "1199px" }, // 태블릿: 768px ~ 1199px
+ mobile: { max: "767px" }, // 모바일: 0 ~ 767px
+ },
+ colors: {
+ primary: {
+ 100: "#3692ff",
+ 200: "#1967d6",
+ 300: "#1251aa",
+ },
+
+ secondary: {
+ 10: "#FCFCFC",
+ 50: "#f9fafb",
+ 100: "#f3f4f6",
+ 200: "#e5e7eb",
+ 400: "#9ca3af",
+ 500: "#6b7280",
+ 600: "#4b5563",
+ 700: "#374151",
+ 800: "#1f2937",
+ 900: "#111827",
+ },
+
+ error_red: {
+ 50: "#f74747",
+ },
+ },
+ },
+ },
+ plugins: [],
+};
diff --git a/tsconfig.json b/tsconfig.json
index 670224f3..620c9bd9 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,7 +1,11 @@
{
"compilerOptions": {
- "target": "es5",
- "lib": ["dom", "dom.iterable", "esnext"],
+ "target": "es2015",
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@@ -13,10 +17,23 @@
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
+ "baseUrl": "src",
"paths": {
- "@/*": ["./*"]
- }
+ "@/*": ["*"]
+ },
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ]
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
- "exclude": ["node_modules"]
+ "include": [
+ "**/*.ts",
+ "**/*.tsx",
+ "next-env.d.ts",
+ ".next/types/**/*.ts"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
}