From eaf41b8041df5fd445635adf09e3dce8f8926916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=ED=97=8C?= Date: Tue, 16 Jun 2026 11:12:04 +0900 Subject: [PATCH 01/21] =?UTF-8?q?[feat]=20front:=20React=20+=20Vite=20+=20?= =?UTF-8?q?TypeScript=20=EC=B4=88=EA=B8=B0=20=EC=85=8B=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Vite(react-ts) 스캐폴딩, @/* -> src 경로 alias 설정 - 라이브러리 추가: TanStack Query, axios, zustand, slate(react/history), tailwindcss v4 - core/provider.tsx 로 QueryClientProvider 분리 및 main 연결 Co-Authored-By: Claude Opus 4.8 (1M context) --- front/.gitignore | 24 + front/README.md | 74 +- front/eslint.config.js | 22 + front/index.html | 13 + front/package-lock.json | 3562 +++++++++++++++++++++++++++++++++++ front/package.json | 38 + front/public/favicon.svg | 1 + front/public/icons.svg | 24 + front/src/App.css | 184 ++ front/src/App.tsx | 122 ++ front/src/assets/hero.png | Bin 0 -> 13057 bytes front/src/assets/react.svg | 1 + front/src/assets/vite.svg | 1 + front/src/core/provider.tsx | 16 + front/src/index.css | 113 ++ front/src/main.tsx | 13 + front/tsconfig.app.json | 30 + front/tsconfig.json | 7 + front/tsconfig.node.json | 24 + front/vite.config.ts | 14 + 20 files changed, 4282 insertions(+), 1 deletion(-) create mode 100644 front/.gitignore create mode 100644 front/eslint.config.js create mode 100644 front/index.html create mode 100644 front/package-lock.json create mode 100644 front/package.json create mode 100644 front/public/favicon.svg create mode 100644 front/public/icons.svg create mode 100644 front/src/App.css create mode 100644 front/src/App.tsx create mode 100644 front/src/assets/hero.png create mode 100644 front/src/assets/react.svg create mode 100644 front/src/assets/vite.svg create mode 100644 front/src/core/provider.tsx create mode 100644 front/src/index.css create mode 100644 front/src/main.tsx create mode 100644 front/tsconfig.app.json create mode 100644 front/tsconfig.json create mode 100644 front/tsconfig.node.json create mode 100644 front/vite.config.ts diff --git a/front/.gitignore b/front/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/front/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/front/README.md b/front/README.md index 53a5f28..7dbf7eb 100644 --- a/front/README.md +++ b/front/README.md @@ -1 +1,73 @@ -마지막 내 도리는 하자 . \ No newline at end of file +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` diff --git a/front/eslint.config.js b/front/eslint.config.js new file mode 100644 index 0000000..ef614d2 --- /dev/null +++ b/front/eslint.config.js @@ -0,0 +1,22 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + globals: globals.browser, + }, + }, +]) diff --git a/front/index.html b/front/index.html new file mode 100644 index 0000000..aee145d --- /dev/null +++ b/front/index.html @@ -0,0 +1,13 @@ + + + + + + + temp-vite + + +
+ + + diff --git a/front/package-lock.json b/front/package-lock.json new file mode 100644 index 0000000..fea6b05 --- /dev/null +++ b/front/package-lock.json @@ -0,0 +1,3562 @@ +{ + "name": "negosium-front", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "negosium-front", + "version": "0.0.0", + "dependencies": { + "@tanstack/react-query": "^5.101.0", + "axios": "^1.18.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "slate": "^0.124.1", + "slate-history": "^0.113.1", + "slate-react": "^0.124.2", + "zustand": "^5.0.14" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@tailwindcss/vite": "^4.3.1", + "@types/node": "^24.12.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^10.3.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.6.0", + "tailwindcss": "^4.3.1", + "typescript": "~6.0.2", + "typescript-eslint": "^8.59.2", + "vite": "^8.0.12" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "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/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@juggle/resize-observer": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", + "license": "Apache-2.0" + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", + "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.1.tgz", + "integrity": "sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "5.21.6", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.1" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.1.tgz", + "integrity": "sha512-yVPyo8RNkabVr3O2EhHEE0Rewu7YKzc1DhIqfL46LKveFrmu9XbDazNOJY7/GRuvw1h6u3utWnR29H/p5JPlgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.1", + "@tailwindcss/oxide-darwin-arm64": "4.3.1", + "@tailwindcss/oxide-darwin-x64": "4.3.1", + "@tailwindcss/oxide-freebsd-x64": "4.3.1", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.1", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.1", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.1", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.1", + "@tailwindcss/oxide-linux-x64-musl": "4.3.1", + "@tailwindcss/oxide-wasm32-wasi": "4.3.1", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.1", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.1" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.1.tgz", + "integrity": "sha512-SVlyf61g374l5cHyg8x9kf5xmLcOaxvOTsbsqDnSsDJaKOEFZ7GCvi84VAVGpxojYOs1+3K6M0UjXfqPU8vmOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.1.tgz", + "integrity": "sha512-hVnWLwv+e/l7c4WKyVtHVrIPvYdqWHjRB3MDIqARynzFtnQg85kmQEFCbV9Ja0VVx4xXTIiDWY60Y7iz/iNoDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.1.tgz", + "integrity": "sha512-Cf7abu0WVgbhU7ANgPUnSAvm7nCvMweusHb8FnaHlLfv/Caq4GYaEZg7ZImzzmjx4lIAfuS8q+eLIS7A7IzxIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.1.tgz", + "integrity": "sha512-ZZqzX2Y+GXtXXfqSfpJhDm60OoZfvLHLCgm+J7NVqgHHJjG/m9ugZI77RwTsVd4fnBJuCFP6Ae6kTJb71UdS8g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.1.tgz", + "integrity": "sha512-/Ah/xik0LaMYfv9DZ0S/t4pBlBNYOcqtRwusjgovHkvT8ixueWCLyJjsaF5kQIckjb4IT8Q6K6p/iPmZMixYgg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.1.tgz", + "integrity": "sha512-gqdFoVJlw444GvpnheZLHmvTzSxI/cOUUh2KSNejQjTcYkW062SVD+En0rUgD+QV91bz1XGIGtt1HJd48xUGbQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.1.tgz", + "integrity": "sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.1.tgz", + "integrity": "sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.1.tgz", + "integrity": "sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.1.tgz", + "integrity": "sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.1.tgz", + "integrity": "sha512-aiNvSq9BsVk8V513lDKlrCFAgf8qBMPZTpgEhInL+NwQqs97mYmupVMrPrgBBSL8Pv/0zXu9MrMF9rMun1ZeNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.1.tgz", + "integrity": "sha512-xDEyu1rg290472FEGaKHnzyDyh5QH+AlWvsU5hMoMtPpzmKlRI0jaYKCgSHDYtaQWZOYbMaduSyCwFwY4n1HmA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.1.tgz", + "integrity": "sha512-hItDHuIIlEV61R+faXu66s1K36aTurO/Qw0e45Vskz57gXl9pWOT6eg3zmcEui6CZXddbN7zd41bwmvag4JGwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.1", + "@tailwindcss/oxide": "4.3.1", + "tailwindcss": "4.3.1" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.101.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.0.tgz", + "integrity": "sha512-cQetA74EB+seWySv1TTKr828TnP0u39m6LykwDXIo84SNortpDkp30TMEjkqtYCNP9c40uT/iwl6MLiufEt0Ow==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.101.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.0.tgz", + "integrity": "sha512-rLlJXSpkqfizLWgkR5+eLeIk0MvTx/meEIR7LRjxic+qxiQP8zVjq7BqQkiCMNLQBlLfuOLqqr6KO5GtrDlmSg==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.101.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", + "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.1.tgz", + "integrity": "sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/type-utils": "8.61.1", + "@typescript-eslint/utils": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.61.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.1.tgz", + "integrity": "sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.1.tgz", + "integrity": "sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.61.1", + "@typescript-eslint/types": "^8.61.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.1.tgz", + "integrity": "sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.1.tgz", + "integrity": "sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.1.tgz", + "integrity": "sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/utils": "8.61.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.1.tgz", + "integrity": "sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.1.tgz", + "integrity": "sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.61.1", + "@typescript-eslint/tsconfig-utils": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.1.tgz", + "integrity": "sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.1.tgz", + "integrity": "sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz", + "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "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/axios": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.0.tgz", + "integrity": "sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.37", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.37.tgz", + "integrity": "sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "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": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "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/caniuse-lite": { + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "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/compute-scroll-into-view": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "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", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "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/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/direction": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz", + "integrity": "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==", + "license": "MIT", + "bin": { + "direction": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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/electron-to-chromium": { + "version": "1.5.372", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.372.tgz", + "integrity": "sha512-M3yhbAlilnwqC8D21t28UCDGHyitShTmmLRU/H+b74P6Ski16Nb9HONYEaVpMj/pwC7BEo5B95FpjODLCWbtfA==", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.21.6", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", + "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "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-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "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": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.5.0.tgz", + "integrity": "sha512-1y+7C+vi12bUK1IpZeaV3gsH9fHLBmPvYmPx42pvT/E9yG0IC8g3PUZZgp0+JLJl7ZDK0flc2gc+Aw9dpCvIsQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.6.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.3.tgz", + "integrity": "sha512-5EMmLCV98Pi4o/f/3DP/v/tNqLHMIc9I8LKClNDWhZ9JTho89/kQcitCXQBMG7sAfVRK0Ie3T2EDOzp1YXYiVA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "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==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "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", + "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/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz", + "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "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" + } + }, + "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==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-symbols": { + "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" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "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.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hotkey": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.2.0.tgz", + "integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==", + "license": "MIT" + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "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": ">= 0.4" + } + }, + "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": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", + "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "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.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/rolldown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slate": { + "version": "0.124.1", + "resolved": "https://registry.npmjs.org/slate/-/slate-0.124.1.tgz", + "integrity": "sha512-ii7DwezgvbLAyKtHBIunjTR1kzbNfYLCUKLMzJELlbTZkvHzX4DzN7HKIwcakf6dPxO6AoeT/P7kHOcyTym/hA==", + "license": "MIT" + }, + "node_modules/slate-dom": { + "version": "0.124.1", + "resolved": "https://registry.npmjs.org/slate-dom/-/slate-dom-0.124.1.tgz", + "integrity": "sha512-D3yVibjLZM4Oj4MmXxOEXbjrlf4wJez3OvGABBNYrAP7gXb0d96tKNtWZ0hGm/5y84idw/LHjZ7W1uTYqFR9rQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@juggle/resize-observer": "^3.4.0", + "direction": "^1.0.4", + "is-hotkey": "^0.2.0", + "is-plain-object": "^5.0.0", + "lodash": "^4.17.21", + "scroll-into-view-if-needed": "^3.1.0", + "tiny-invariant": "1.3.1" + }, + "peerDependencies": { + "slate": ">=0.121.0" + } + }, + "node_modules/slate-history": { + "version": "0.113.1", + "resolved": "https://registry.npmjs.org/slate-history/-/slate-history-0.113.1.tgz", + "integrity": "sha512-J9NSJ+UG2GxoW0lw5mloaKcN0JI0x2IA5M5FxyGiInpn+QEutxT1WK7S/JneZCMFJBoHs1uu7S7e6pxQjubHmQ==", + "license": "MIT", + "dependencies": { + "is-plain-object": "^5.0.0" + }, + "peerDependencies": { + "slate": ">=0.65.3" + } + }, + "node_modules/slate-react": { + "version": "0.124.2", + "resolved": "https://registry.npmjs.org/slate-react/-/slate-react-0.124.2.tgz", + "integrity": "sha512-2B6ZZX5qKYeISNaQ9cDuvvp0tWydnHUPuGxyVJZfjD+W00X34dGaAF/5ZyVMZt/O//sf0Glbgd7+NBHRWjiA7Q==", + "license": "MIT", + "dependencies": { + "@juggle/resize-observer": "^3.4.0", + "direction": "^1.0.4", + "is-hotkey": "^0.2.0", + "lodash": "^4.17.21", + "scroll-into-view-if-needed": "^3.1.0", + "tiny-invariant": "1.3.1" + }, + "peerDependencies": { + "react": ">=18.2.0", + "react-dom": ">=18.2.0", + "slate": ">=0.121.0", + "slate-dom": ">=0.119.1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.1.tgz", + "integrity": "sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", + "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.61.1.tgz", + "integrity": "sha512-V7PayAfJokV3pEHgN7/v03D1SpujhRfQtYLbLIiBfDDncdg4PAiRBfoS4cnCANK4jmAPncczi59QO3afiXUlNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.61.1", + "@typescript-eslint/parser": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/utils": "8.61.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "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", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/zustand": { + "version": "5.0.14", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz", + "integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/front/package.json b/front/package.json new file mode 100644 index 0000000..15c3db0 --- /dev/null +++ b/front/package.json @@ -0,0 +1,38 @@ +{ + "name": "negosium-front", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/react-query": "^5.101.0", + "axios": "^1.18.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "slate": "^0.124.1", + "slate-history": "^0.113.1", + "slate-react": "^0.124.2", + "zustand": "^5.0.14" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@tailwindcss/vite": "^4.3.1", + "@types/node": "^24.12.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^10.3.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.6.0", + "tailwindcss": "^4.3.1", + "typescript": "~6.0.2", + "typescript-eslint": "^8.59.2", + "vite": "^8.0.12" + } +} diff --git a/front/public/favicon.svg b/front/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/front/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/public/icons.svg b/front/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/front/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/front/src/App.css b/front/src/App.css new file mode 100644 index 0000000..f90339d --- /dev/null +++ b/front/src/App.css @@ -0,0 +1,184 @@ +.counter { + font-size: 16px; + padding: 5px 10px; + border-radius: 5px; + color: var(--accent); + background: var(--accent-bg); + border: 2px solid transparent; + transition: border-color 0.3s; + margin-bottom: 24px; + + &:hover { + border-color: var(--accent-border); + } + &:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; + } +} + +.hero { + position: relative; + + .base, + .framework, + .vite { + inset-inline: 0; + margin: 0 auto; + } + + .base { + width: 170px; + position: relative; + z-index: 0; + } + + .framework, + .vite { + position: absolute; + } + + .framework { + z-index: 1; + top: 34px; + height: 28px; + transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg) + scale(1.4); + } + + .vite { + z-index: 0; + top: 107px; + height: 26px; + width: auto; + transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg) + scale(0.8); + } +} + +#center { + display: flex; + flex-direction: column; + gap: 25px; + place-content: center; + place-items: center; + flex-grow: 1; + + @media (max-width: 1024px) { + padding: 32px 20px 24px; + gap: 18px; + } +} + +#next-steps { + display: flex; + border-top: 1px solid var(--border); + text-align: left; + + & > div { + flex: 1 1 0; + padding: 32px; + @media (max-width: 1024px) { + padding: 24px 20px; + } + } + + .icon { + margin-bottom: 16px; + width: 22px; + height: 22px; + } + + @media (max-width: 1024px) { + flex-direction: column; + text-align: center; + } +} + +#docs { + border-right: 1px solid var(--border); + + @media (max-width: 1024px) { + border-right: none; + border-bottom: 1px solid var(--border); + } +} + +#next-steps ul { + list-style: none; + padding: 0; + display: flex; + gap: 8px; + margin: 32px 0 0; + + .logo { + height: 18px; + } + + a { + color: var(--text-h); + font-size: 16px; + border-radius: 6px; + background: var(--social-bg); + display: flex; + padding: 6px 12px; + align-items: center; + gap: 8px; + text-decoration: none; + transition: box-shadow 0.3s; + + &:hover { + box-shadow: var(--shadow); + } + .button-icon { + height: 18px; + width: 18px; + } + } + + @media (max-width: 1024px) { + margin-top: 20px; + flex-wrap: wrap; + justify-content: center; + + li { + flex: 1 1 calc(50% - 8px); + } + + a { + width: 100%; + justify-content: center; + box-sizing: border-box; + } + } +} + +#spacer { + height: 88px; + border-top: 1px solid var(--border); + @media (max-width: 1024px) { + height: 48px; + } +} + +.ticks { + position: relative; + width: 100%; + + &::before, + &::after { + content: ''; + position: absolute; + top: -4.5px; + border: 5px solid transparent; + } + + &::before { + left: 0; + border-left-color: var(--border); + } + &::after { + right: 0; + border-right-color: var(--border); + } +} diff --git a/front/src/App.tsx b/front/src/App.tsx new file mode 100644 index 0000000..a66b5ef --- /dev/null +++ b/front/src/App.tsx @@ -0,0 +1,122 @@ +import { useState } from 'react' +import reactLogo from './assets/react.svg' +import viteLogo from './assets/vite.svg' +import heroImg from './assets/hero.png' +import './App.css' + +function App() { + const [count, setCount] = useState(0) + + return ( + <> +
+
+ + React logo + Vite logo +
+
+

Get started

+

+ Edit src/App.tsx and save to test HMR +

+
+ +
+ +
+ +
+
+ +

Documentation

+

Your questions, answered

+ +
+
+ +

Connect with us

+

Join the Vite community

+ +
+
+ +
+
+ + ) +} + +export default App diff --git a/front/src/assets/hero.png b/front/src/assets/hero.png new file mode 100644 index 0000000000000000000000000000000000000000..02251f4b956c55af2d76fd0788124d7eee2b45eb GIT binary patch literal 13057 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 P00000NkvXXu0mjft=yBf literal 0 HcmV?d00001 diff --git a/front/src/assets/react.svg b/front/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/front/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/assets/vite.svg b/front/src/assets/vite.svg new file mode 100644 index 0000000..5101b67 --- /dev/null +++ b/front/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/front/src/core/provider.tsx b/front/src/core/provider.tsx new file mode 100644 index 0000000..2749278 --- /dev/null +++ b/front/src/core/provider.tsx @@ -0,0 +1,16 @@ +import { type ReactNode } from 'react' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 60 * 1000, // 1분 + retry: 1, + refetchOnWindowFocus: false, + }, + }, +}) + +export function Provider({ children }: { children: ReactNode }) { + return {children} +} diff --git a/front/src/index.css b/front/src/index.css new file mode 100644 index 0000000..c4b94ee --- /dev/null +++ b/front/src/index.css @@ -0,0 +1,113 @@ +@import "tailwindcss"; + +:root { + --text: #6b6375; + --text-h: #08060d; + --bg: #fff; + --border: #e5e4e7; + --code-bg: #f4f3ec; + --accent: #aa3bff; + --accent-bg: rgba(170, 59, 255, 0.1); + --accent-border: rgba(170, 59, 255, 0.5); + --social-bg: rgba(244, 243, 236, 0.5); + --shadow: + rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px; + + --sans: system-ui, 'Segoe UI', Roboto, sans-serif; + --heading: system-ui, 'Segoe UI', Roboto, sans-serif; + --mono: ui-monospace, Consolas, monospace; + + font: 18px/145% var(--sans); + letter-spacing: 0.18px; + color-scheme: light dark; + color: var(--text); + background: var(--bg); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + @media (max-width: 1024px) { + font-size: 16px; + } +} + +@media (prefers-color-scheme: dark) { + :root { + --text: #9ca3af; + --text-h: #f3f4f6; + --bg: #16171d; + --border: #2e303a; + --code-bg: #1f2028; + --accent: #c084fc; + --accent-bg: rgba(192, 132, 252, 0.15); + --accent-border: rgba(192, 132, 252, 0.5); + --social-bg: rgba(47, 48, 58, 0.5); + --shadow: + rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px; + } + + #social .button-icon { + filter: invert(1) brightness(2); + } +} + +#root { + width: 1126px; + max-width: 100%; + margin: 0 auto; + text-align: center; + border-inline: 1px solid var(--border); + min-height: 100svh; + display: flex; + flex-direction: column; + box-sizing: border-box; +} + +body { + margin: 0; +} + +h1, +h2 { + font-family: var(--heading); + font-weight: 500; + color: var(--text-h); +} + +h1 { + font-size: 56px; + letter-spacing: -1.68px; + margin: 32px 0; + @media (max-width: 1024px) { + font-size: 36px; + margin: 20px 0; + } +} +h2 { + font-size: 24px; + line-height: 118%; + letter-spacing: -0.24px; + margin: 0 0 8px; + @media (max-width: 1024px) { + font-size: 20px; + } +} +p { + margin: 0; +} + +code, +.counter { + font-family: var(--mono); + display: inline-flex; + border-radius: 4px; + color: var(--text-h); +} + +code { + font-size: 15px; + line-height: 135%; + padding: 4px 8px; + background: var(--code-bg); +} diff --git a/front/src/main.tsx b/front/src/main.tsx new file mode 100644 index 0000000..40181dd --- /dev/null +++ b/front/src/main.tsx @@ -0,0 +1,13 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import { Provider } from '@/core/provider' +import './index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + + + , +) diff --git a/front/tsconfig.app.json b/front/tsconfig.app.json new file mode 100644 index 0000000..3eddea9 --- /dev/null +++ b/front/tsconfig.app.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023", "DOM"], + "module": "esnext", + "types": ["vite/client"], + "skipLibCheck": true, + + /* Path alias */ + "paths": { + "@/*": ["./src/*"] + }, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/front/tsconfig.json b/front/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/front/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/front/tsconfig.node.json b/front/tsconfig.node.json new file mode 100644 index 0000000..d3c52ea --- /dev/null +++ b/front/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023"], + "module": "esnext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/front/vite.config.ts b/front/vite.config.ts new file mode 100644 index 0000000..34e72da --- /dev/null +++ b/front/vite.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite' +import { fileURLToPath, URL } from 'node:url' +import react from '@vitejs/plugin-react' +import tailwindcss from '@tailwindcss/vite' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react(), tailwindcss()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + }, +}) From 8caaa36af24ca731c210bbdb86100d5e7d84ad66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=ED=97=8C?= Date: Tue, 16 Jun 2026 11:27:53 +0900 Subject: [PATCH 02/21] =?UTF-8?q?[feat]=20front:=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EB=B3=84=20.env=20=EC=84=A4=EC=A0=95=20=EB=B0=8F=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=20=EA=B0=80=EC=9D=B4=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - .env.sample 추가, 실제 env(local/dev/prod)는 .gitignore 처리 - package.json: mode 별 실행/빌드 스크립트(dev/dev:dev/build:dev/build:prod) 추가 - README: 기술 스택·로컬 세팅·환경 변수·실행 방법 정리, 템플릿 boilerplate 제거 Co-Authored-By: Claude Opus 4.8 (1M context) --- front/.env.sample | 11 +++++ front/.gitignore | 5 +++ front/README.md | 109 +++++++++++++++++++++------------------------ front/package.json | 11 +++-- 4 files changed, 74 insertions(+), 62 deletions(-) create mode 100644 front/.env.sample diff --git a/front/.env.sample b/front/.env.sample new file mode 100644 index 0000000..9cba6db --- /dev/null +++ b/front/.env.sample @@ -0,0 +1,11 @@ +# 환경 변수 템플릿 (git 에 커밋되는 유일한 env 파일) +# 새 환경을 세팅할 때 이 파일을 복사해서 .env.local / .env.dev / .env.prod 를 만든다. +# +# 주의: 클라이언트(브라우저) 번들에 노출되는 변수는 반드시 VITE_ 접두사를 붙여야 한다. +# 접두사 없는 변수는 빌드에 포함되지 않는다 (서버 비밀값은 여기 두지 말 것). + +# 현재 실행 환경 식별용 (local | dev | prod) +VITE_APP_ENV=local + +# 백엔드 API 베이스 URL (negosium 백엔드 기본 포트 9300) +VITE_API_BASE_URL=http://localhost:9300 diff --git a/front/.gitignore b/front/.gitignore index a547bf3..7cd12e4 100644 --- a/front/.gitignore +++ b/front/.gitignore @@ -12,6 +12,11 @@ dist dist-ssr *.local +# env (실제 env 파일은 무시하고 .env.sample 만 커밋) +.env +.env.* +!.env.sample + # Editor directories and files .vscode/* !.vscode/extensions.json diff --git a/front/README.md b/front/README.md index 7dbf7eb..eadeeae 100644 --- a/front/README.md +++ b/front/README.md @@ -1,73 +1,66 @@ -# React + TypeScript + Vite +# Negosium Front -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. +React + TypeScript + Vite 기반 프론트엔드. -Currently, two official plugins are available: +## 기술 스택 -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) +- **빌드/런타임**: Vite 8, React 19, TypeScript 6 +- **상태/데이터**: TanStack Query(서버 상태), Zustand(클라이언트 상태) +- **HTTP**: axios +- **에디터**: Slate (`slate` / `slate-react` / `slate-history`) +- **스타일**: Tailwind CSS v4 (`@tailwindcss/vite`) +- **경로 alias**: `@/*` → `src/*` -## React Compiler +## 요구 사항 -The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). +- Node.js `20.19+` 또는 `22.12+` (Vite 8 요구 사항, 개발은 24.x 기준) +- npm -## Expanding the ESLint configuration +## 로컬 세팅 -If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: +```bash +# 1. 프로젝트로 이동 +cd front -```js -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... +# 2. 의존성 설치 +npm install - // Remove tseslint.configs.recommended and replace with this - tseslint.configs.recommendedTypeChecked, - // Alternatively, use this for stricter rules - tseslint.configs.strictTypeChecked, - // Optionally, add this for stylistic rules - tseslint.configs.stylisticTypeChecked, +# 3. env 파일 준비 (.env.sample 복사 후 값 채우기) +cp .env.sample .env.local - // Other configs... - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]) +# 4. 로컬 개발 서버 실행 (기본 http://localhost:5173) +npm run dev ``` -You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: +> 백엔드 API 는 negosium 백엔드(기본 `http://localhost:9300`)를 바라본다. `.env.local` 의 `VITE_API_BASE_URL` 로 조정한다. -```js -// eslint.config.js -import reactX from 'eslint-plugin-react-x' -import reactDom from 'eslint-plugin-react-dom' +## 환경 변수 (.env) -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - // Enable lint rules for React - reactX.configs['recommended-typescript'], - // Enable lint rules for React DOM - reactDom.configs.recommended, - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]) +Vite 의 mode 기능으로 환경별 env 파일을 매핑한다. 클라이언트 번들에 노출되는 변수는 반드시 `VITE_` 접두사를 붙인다. + +| 파일 | mode | 용도 | git | +| --- | --- | --- | --- | +| `.env.sample` | - | 템플릿 (키 목록 공유용) | ✅ 커밋 | +| `.env.local` | `local` | 로컬 개발 | 🚫 ignore | +| `.env.dev` | `dev` | 개발 서버 | 🚫 ignore | +| `.env.prod` | `prod` | 운영 | 🚫 ignore | + +새 환경 세팅 시 `.env.sample` 을 복사해 환경별 파일을 만들고 값을 채운다. 코드에서는 `import.meta.env.VITE_API_BASE_URL` 처럼 접근한다. + +> 참고: Vite 는 mode 와 무관하게 `.env.local` 을 항상 함께 로드한다(개인 로컬 오버라이드 용도). 우선순위는 `.env.[mode]` > `.env.local` > `.env` 이므로, 각 env 파일에 **동일한 키 집합**을 유지하면 mode 파일 값이 항상 이긴다. 특정 mode 에만 있어야 할 키를 `.env.local` 에 두면 다른 mode 로 새어 들어갈 수 있으니 주의. + +## 실행 / 빌드 + +`--mode ` 으로 위 표의 env 파일이 선택된다. + +```bash +npm run dev # 로컬 개발 서버 (mode: local → .env.local) +npm run dev:dev # 개발 서버를 dev 환경 변수로 실행 (mode: dev → .env.dev) + +npm run build # 운영 빌드 (mode: prod → .env.prod), build:prod 와 동일 +npm run build:dev # 개발 빌드 (mode: dev → .env.dev) +npm run build:prod # 운영 빌드 (mode: prod → .env.prod) + +npm run preview # 직전 빌드 결과 로컬 미리보기 +npm run lint # ESLint ``` diff --git a/front/package.json b/front/package.json index 15c3db0..00f05d0 100644 --- a/front/package.json +++ b/front/package.json @@ -4,10 +4,13 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite", - "build": "tsc -b && vite build", - "lint": "eslint .", - "preview": "vite preview" + "dev": "vite --mode local", + "dev:dev": "vite --mode dev", + "build": "tsc -b && vite build --mode prod", + "build:dev": "tsc -b && vite build --mode dev", + "build:prod": "tsc -b && vite build --mode prod", + "preview": "vite preview", + "lint": "eslint ." }, "dependencies": { "@tanstack/react-query": "^5.101.0", From 158e7ba29a0989f5cba4b9f68a75f3c524a21831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=ED=97=8C?= Date: Tue, 16 Jun 2026 11:39:22 +0900 Subject: [PATCH 03/21] =?UTF-8?q?[feat]=20front:=20design.md=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=20=EB=94=94=EC=9E=90=EC=9D=B8=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?+=20=EB=B8=8C=EB=9E=9C=EB=93=9C=20=EC=BB=AC=EB=9F=AC=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - index.css 를 shadcn 스타일 토큰 시스템(Tailwind v4 @theme inline)으로 교체 - 다크모드/차트 토큰 제외 (프로젝트 미사용), light 전용 - 브랜드 블루 숫자 스케일 추가: --brand-500/600/700 (hover=톤다운 700) - --primary=brand-600, --ring/--sidebar-ring=brand-500 로 통일 - 폰트: Pretendard CDN(@import, dynamic-subset, 버전 고정) Co-Authored-By: Claude Opus 4.8 (1M context) --- front/design.md | 112 +++++++++++++++++++++ front/src/index.css | 232 +++++++++++++++++++++++++------------------- 2 files changed, 243 insertions(+), 101 deletions(-) create mode 100644 front/design.md diff --git a/front/design.md b/front/design.md new file mode 100644 index 0000000..095a73f --- /dev/null +++ b/front/design.md @@ -0,0 +1,112 @@ +--- + +## 색상 (Color) + +값은 `tokens.css`의 `:root`(light) / `.dark`(dark) 정의. 모두 hex (일부 8자리 hex = 알파 포함), `--background-rgb`만 raw rgb. + +| 토큰 | Tailwind 유틸 | Light | Dark | 용도 | +|---|---|---|---|---| +| `--background` | `bg-background` | `#ffffff` (흰색) | `#0a0a0a` (거의 검정) | 페이지 배경 | +| `--background-rgb` | — | `250, 250, 250` | `9, 9, 11` | rgba() 조합용 raw 값 | +| `--foreground` | `text-foreground` | `#0a0a0a` | `#fafafa` | 기본 텍스트 | +| `--card` | `bg-card` | `#ffffff` | `#171717` | 카드 배경 | +| `--card-foreground` | `text-card-foreground` | `#0a0a0a` | `#fafafa` | 카드 텍스트 | +| `--popover` | `bg-popover` | `#ffffff` | `#171717` | 팝오버 배경 | +| `--popover-foreground` | `text-popover-foreground` | `#0a0a0a` | `#fafafa` | 팝오버 텍스트 | +| `--primary` | `bg-primary` | `#171717` (다크 그레이) | `#e5e5e5` (라이트 그레이) | 주요 액션/버튼 | +| `--primary-foreground` | `text-primary-foreground` | `#fafafa` | `#171717` | primary 위 텍스트 | +| `--secondary` | `bg-secondary` | `#f5f5f5` | `#262626` | 보조 배경 | +| `--secondary-foreground` | `text-secondary-foreground` | `#171717` | `#fafafa` | 보조 텍스트 | +| `--muted` | `bg-muted` | `#f5f5f5` | `#262626` | 흐린 배경 | +| `--muted-foreground` | `text-muted-foreground` | `#737373` | `#a1a1a1` | 흐린 텍스트/캡션 | +| `--accent` | `bg-accent` | `#f5f5f5` | `#262626` | 강조 배경(hover 등) | +| `--accent-foreground` | `text-accent-foreground` | `#171717` | `#fafafa` | accent 위 텍스트 | +| `--border` | `border-border` | `#e5e5e5` | `#ffffff1a` (흰색 10%) | 테두리/구분선 | +| `--input` | `border-input` | `#e5e5e5` | `#ffffff26` (흰색 15%) | 인풋 테두리 | +| `--ring` | `ring-ring` | `#a1a1a1` | `#737373` | 포커스 링 | +| `--destructive` | `bg-destructive` | `#e7000b` (빨강) | `#ff6467` | 위험/삭제 | +| `--success` | `bg-success` | `#10b981` (초록) | `#10b981` | 성공 (light/dark 동일) | +| `--warning` | `bg-warning` | `#f59e0b` (주황) | `#f59e0b` | 경고 (light/dark 동일) | + +### 차트 (Chart) — 무채색 스케일 + +| 토큰 | Light | Dark | +|---|---|---| +| `--chart-1` | `#d4d4d4` | `#d4d4d4` | +| `--chart-2` | `#737373` | `#737373` | +| `--chart-3` | `#525252` | `#525252` | +| `--chart-4` | `#404040` | `#404040` | +| `--chart-5` | `#262626` | `#262626` | + +### 사이드바 (Sidebar) + +| 토큰 | Light | Dark | +|---|---|---| +| `--sidebar` | `#fafafa` | `#171717` | +| `--sidebar-foreground` | `#0a0a0a` | `#fafafa` | +| `--sidebar-primary` | `#171717` | `#1447e6` (보라) | +| `--sidebar-primary-foreground` | `#fafafa` | `#fafafa` | +| `--sidebar-accent` | `#f5f5f5` | `#262626` | +| `--sidebar-accent-foreground` | `#171717` | `#fafafa` | +| `--sidebar-border` | `#e5e5e5` | `#ffffff1a` | +| `--sidebar-ring` | `#a1a1a1` | `#737373` | + +--- + +## 타이포그래피 (Typography) + +`` 로 사용. 출처: [src/components/ui/typography.tsx](src/components/ui/typography.tsx). `defaultVariant`는 `body`. + +| variant | 기본 태그 | 클래스 | 용도 | +|---|---|---|---| +| `h1` | `

` | `text-4xl font-bold tracking-tight text-foreground` | 페이지 제목 | +| `h2` | `

` | `text-2xl font-bold tracking-tight text-foreground` | 섹션 제목 | +| `h3` | `

` | `text-xl font-bold text-foreground` | 서브 제목 | +| `h4` | `

` | `text-lg font-semibold text-foreground` | 소제목 | +| `body` | `

` | `text-base text-foreground` | 본문 (기본값) | +| `small` | `

` | `text-sm text-foreground` | 작은 본문 | +| `muted` | `

` | `text-sm text-muted-foreground leading-relaxed` | 보조 설명 | +| `label` | `` | `text-xs font-semibold uppercase tracking-wider text-foreground` | 폼 라벨 | +| `mono` | `` | `text-xs font-mono uppercase tracking-wider text-muted-foreground` | 콘솔/메타 캡션 | +| `caption` | `` | `text-[11px] text-muted-foreground leading-normal` | 사이드바/헤더 chrome 메타 | + +`as` prop으로 의미 태그 교체 가능(예: `label` variant를 `

` | `text-4xl font-bold tracking-tight text-foreground` | 페이지 제목 | -| `h2` | `

` | `text-2xl font-bold tracking-tight text-foreground` | 섹션 제목 | -| `h3` | `

` | `text-xl font-bold text-foreground` | 서브 제목 | -| `h4` | `

` | `text-lg font-semibold text-foreground` | 소제목 | -| `body` | `

` | `text-base text-foreground` | 본문 (기본값) | -| `small` | `

` | `text-sm text-foreground` | 작은 본문 | -| `muted` | `

` | `text-sm text-muted-foreground leading-relaxed` | 보조 설명 | -| `label` | `` | `text-xs font-semibold uppercase tracking-wider text-foreground` | 폼 라벨 | -| `mono` | `` | `text-xs font-mono uppercase tracking-wider text-muted-foreground` | 콘솔/메타 캡션 | -| `caption` | `` | `text-[11px] text-muted-foreground leading-normal` | 사이드바/헤더 chrome 메타 | - -`as` prop으로 의미 태그 교체 가능(예: `label` variant를 `

- -
-
- -

Documentation

-

Your questions, answered

- -
-
- -

Connect with us

-

Join the Vite community

- -
-
- -
-
- - ) + return } export default App diff --git a/front/src/assets/hero.png b/front/src/assets/hero.png deleted file mode 100644 index 02251f4b956c55af2d76fd0788124d7eee2b45eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13057 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 P00000NkvXXu0mjft=yBf diff --git a/front/src/assets/react.svg b/front/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/front/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/front/src/assets/vite.svg b/front/src/assets/vite.svg deleted file mode 100644 index 5101b67..0000000 --- a/front/src/assets/vite.svg +++ /dev/null @@ -1 +0,0 @@ -Vite From 7d4722714108be57819cd1c98bbc98eadc36d123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=ED=97=8C?= Date: Tue, 16 Jun 2026 14:33:23 +0900 Subject: [PATCH 09/21] =?UTF-8?q?[feat]=20front:=20Vite/=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=ED=83=80=EC=9E=85=20=EC=84=A0=EC=96=B8(vi?= =?UTF-8?q?te-env.d.ts)=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- front/src/vite-env.d.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 front/src/vite-env.d.ts diff --git a/front/src/vite-env.d.ts b/front/src/vite-env.d.ts new file mode 100644 index 0000000..0ce3143 --- /dev/null +++ b/front/src/vite-env.d.ts @@ -0,0 +1,12 @@ +/// + +interface ImportMetaEnv { + /** 현재 실행 환경 식별용 (local | dev | prod) */ + readonly VITE_APP_ENV: 'local' | 'dev' | 'prod' + /** 백엔드 API 베이스 URL */ + readonly VITE_API_BASE_URL: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} From 245c287a9dc601f24d6443b07c1cb2eda92bef81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=ED=97=8C?= Date: Tue, 16 Jun 2026 14:33:23 +0900 Subject: [PATCH 10/21] =?UTF-8?q?[refactor]=20front:=20=EA=B3=B5=ED=86=B5?= =?UTF-8?q?=20UI=EB=A5=BC=20components=20=EB=94=94=EB=A0=89=ED=84=B0?= =?UTF-8?q?=EB=A6=AC=EB=A1=9C=20=EC=A0=95=EB=A6=AC=20+=20Logo=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8/=EB=A1=9C=EA=B3=A0=20=EC=97=90?= =?UTF-8?q?=EC=85=8B=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- front/src/assets/imarketkorea-logo-white.png | Bin 0 -> 8314 bytes front/src/assets/imarketkorea-logo.png | Bin 0 -> 2883 bytes .../src/{component => components}/Button.tsx | 3 +- front/src/{component => components}/Input.tsx | 0 front/src/components/Logo.tsx | 56 ++++++++++++++++++ front/src/{component => components}/cn.ts | 0 front/src/{component => components}/index.ts | 2 + 7 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 front/src/assets/imarketkorea-logo-white.png create mode 100644 front/src/assets/imarketkorea-logo.png rename front/src/{component => components}/Button.tsx (93%) rename front/src/{component => components}/Input.tsx (100%) create mode 100644 front/src/components/Logo.tsx rename front/src/{component => components}/cn.ts (100%) rename front/src/{component => components}/index.ts (66%) diff --git a/front/src/assets/imarketkorea-logo-white.png b/front/src/assets/imarketkorea-logo-white.png new file mode 100644 index 0000000000000000000000000000000000000000..297c69c2e12abfcf044d19c593f7767d3c4f2f53 GIT binary patch literal 8314 zcmeHNZ%i9y7{7K^D@Z6uDBA+IO}ZGTTXoYhgzak6r6pU6EJG4ufRlunuoNT)5$J&_ znbj!cgBx>5)wwJYLop_Rar6jmX%x4U_@~P{cLN3$wU%hJJJ)q2!n!$z(b ztiXY$)mC;)3qPIOhBq$ITgc4|yu~Oz+>a z#r2}omH&}mt6Ki0#JF6aeWtd76UK)}lf9|)p-bidU@(!bQfst2qxHi~VN-`6zm}_v zx~Ga%rujogYc~e>zk8qe%99^F@E|mUW7B*gnn!U!`S4G1k;C+m)GeU*x42}ORja9{ zuAhgQ=bd(myi5Ygo!AzY(fEXUHL)0?`$CJc@2Y})Wv~5*d_3pdVu;M2G+z4)N1W!7 zBDdGBX@!1zo>rOq^|`q3u+zR<{`-ww9scrOQ~!k5e)kpG-~cq(hYV00P(C1Bp!$I7 zBw7y8vWV!xJ)%ip)EO-GB$-Oa!cl^D_<^VN;Q$a*Kv-@R1iqw}i1IA~iscJyNoi^J zY#_j_q~giF9!V&qX`YRPIhvX5;9}=!KLLK5Ih9TAI1y54;*Qmo?=RKWO8X`|ct!}# zGH-?0SxVsGmM=;L#T3uNLxKps!Lx0hpg2GQ2SJKL)E{H01-6efFvEhtMZ$sAP@FF( z!1YBc*wq5kvQMpXX}ni_?>EY75zqj6IF48)=|5uwPdQA6BKixRhP0RqP)HY-SB2&}X5gNy#Y z{82Y)7TL2JDItj|O1ya3p;V7YSJ&DSR9$f6tFJA91Q*FXJnOjgDN7jc9gvIT9;hL2 zU(WfP^4x98`EoGVXss`|IzPGQwJ+d!|Ew%clTe!8gwhllpg5p>K)68l0o6&g9H3

v$es#dqz83=KZ})9dg^iciV}(0b?7?$Zetx}o;1kN*g5u(Crg`-rXnjXwdzShkk{ literal 0 HcmV?d00001 diff --git a/front/src/assets/imarketkorea-logo.png b/front/src/assets/imarketkorea-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9b0d88ab8a86e16b4c3c983254d53f4761a69356 GIT binary patch literal 2883 zcmcguU1%Id9Nz}S*cugDsEE{M>w}_uyR#p+yF<^C<}T*27t`d>SV5edox5A^c4yt) z^KM_G&DX0&n_)@I;Abqe}5FdO|?1Qs=dz&;SO`3=YyE`-Y zpWpBQJ@e|s_@hJH@7d0A+)#O}ILW?W$IsvZdkfI|hJ9}H$DRr~ZpY5}*~XnXc{j%m zzTs4-qv^^)h^d=HHm#9d*Y%k+$L-tS^$~87h_8``;}wM8Kl?)99lIbrsaNERKSG+$ z*nB{y=EtjezJ)DY*#8K>uL~J~OCrQ~-L@CPZb3+JAsffblE5b-QL7*f$AtWJWr81} z0pYD25V0&PykX@~9_3XlZ=#2JMOHLPRwX$v0swUd$~vF^1V$LxbvRiZP3hRIAT*=M zhmzFkbaEXvM}vk0EX$H)MN$-zNr>T`7oo1`g?sx5MH1q`@gs+Ne2j={bS5eYOxd0l zuAjkrVd^R7GpUPw3FPG1szlJnnVdfpv=ee0OQcO);zc2o1(~ehq!A69bPee2@9PMd zcPo{QV^v*TH=_un(kwG0b;zpHVRgGj1U?G`9HDI*Fk+^i znrKm}z_J%c$it*uEC|emoa5NAhLC|&1&Bb=kf>QY5=|2j(J~EW8k%MrHtz2)Qalr9 zM!dgQCpN_lV~JJOSs5rY5M@(WL`~LA(bO1Zy}D;+u@U(f(Xa8 zQV?()XJQNKva^amHpHCn?=%Smy+QDM>>;g39Tbq^2J_uU@=IDv{}9dISbJT^H6W|1 zh%C((HBDuK1ofIo3@u*Ph=I+%^((ahcRp@X`_wtOiM$439as|9v2Sn^~4{d_?)B^k0+5CV{iebIVyW75M*GL)d-As*&khZM+qvO84(%)*x~uf< zap%iZBVQfd_QbAtuRV5b;nTAp|9$D!pH3Vt9hkoV=3jfBe&yJe=UclP3m00g_w*s;I+^OA%uf7PrkFUAq M(s=Q`1JAzv4^e`s6951J literal 0 HcmV?d00001 diff --git a/front/src/component/Button.tsx b/front/src/components/Button.tsx similarity index 93% rename from front/src/component/Button.tsx rename to front/src/components/Button.tsx index fce5fe4..6d5c995 100644 --- a/front/src/component/Button.tsx +++ b/front/src/components/Button.tsx @@ -11,7 +11,8 @@ export type ButtonSize = 'sm' | 'md' | 'lg' const base = 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium ' + - 'transition-colors cursor-pointer select-none ' + + 'transition-[color,background-color,transform] cursor-pointer select-none ' + + 'active:scale-[0.98] ' + 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ' + 'disabled:pointer-events-none disabled:opacity-50' diff --git a/front/src/component/Input.tsx b/front/src/components/Input.tsx similarity index 100% rename from front/src/component/Input.tsx rename to front/src/components/Input.tsx diff --git a/front/src/components/Logo.tsx b/front/src/components/Logo.tsx new file mode 100644 index 0000000..8278918 --- /dev/null +++ b/front/src/components/Logo.tsx @@ -0,0 +1,56 @@ +import { type ComponentProps } from 'react' +import { cn } from './cn' +import logoColor from '@/assets/imarketkorea-logo.png' +import logoWhite from '@/assets/imarketkorea-logo-white.png' + +export type LogoVariant = 'color' | 'white' +export type LogoSize = 'sm' | 'md' | 'lg' + +const sources: Record = { + color: logoColor, // 브랜드 블루 심볼 — 밝은 배경용 + white: logoWhite, // 화이트 심볼 — 어두운 배경용 +} + +// 심볼 높이에 맞춰 텍스트도 함께 스케일한다. +const sizes: Record = { + sm: { img: 'h-6', text: 'text-base', gap: 'gap-1.5' }, + md: { img: 'h-8', text: 'text-xl', gap: 'gap-2' }, + lg: { img: 'h-10', text: 'text-2xl', gap: 'gap-2.5' }, +} + +export interface LogoProps extends Omit, 'children'> { + variant?: LogoVariant + size?: LogoSize + /** 워드마크(iMarket Korea) 텍스트 노출 여부 */ + withText?: boolean + /** 심볼 이미지 alt */ + alt?: string +} + +export function Logo({ + variant = 'color', + size = 'md', + withText = true, + alt = 'iMarket Korea', + className, + ...props +}: LogoProps) { + const s = sizes[size] + + return ( +

+ {withText + {withText && ( + + iMarket Korea + + )} +
+ ) +} + +export default Logo diff --git a/front/src/component/cn.ts b/front/src/components/cn.ts similarity index 100% rename from front/src/component/cn.ts rename to front/src/components/cn.ts diff --git a/front/src/component/index.ts b/front/src/components/index.ts similarity index 66% rename from front/src/component/index.ts rename to front/src/components/index.ts index 5221b7f..c47bd32 100644 --- a/front/src/component/index.ts +++ b/front/src/components/index.ts @@ -2,3 +2,5 @@ export { cn } from './cn' export { Button } from './Button' export type { ButtonProps, ButtonVariant, ButtonSize } from './Button' export { Input } from './Input' +export { Logo } from './Logo' +export type { LogoProps, LogoVariant } from './Logo' From 09643332f2e329cffabb91842f9fa5e15820700f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=ED=97=8C?= Date: Tue, 16 Jun 2026 14:33:23 +0900 Subject: [PATCH 11/21] =?UTF-8?q?[feat]=20front:=20=EC=9D=B8=EC=A6=9D(auth?= =?UTF-8?q?)=20=ED=94=BC=EC=B2=98=20=EC=B6=94=EA=B0=80=20=E2=80=94=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=8F=BC/=ED=9B=85/=EC=9E=84?= =?UTF-8?q?=EC=8B=9C=20mutation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../features/auth/components/LoginForm.tsx | 60 +++++++++++++++++++ front/src/features/auth/hooks/useLogin.ts | 55 +++++++++++++++++ .../features/auth/hooks/useLoginMutation.ts | 20 +++++++ front/src/features/auth/index.ts | 1 + 4 files changed, 136 insertions(+) create mode 100644 front/src/features/auth/components/LoginForm.tsx create mode 100644 front/src/features/auth/hooks/useLogin.ts create mode 100644 front/src/features/auth/hooks/useLoginMutation.ts create mode 100644 front/src/features/auth/index.ts diff --git a/front/src/features/auth/components/LoginForm.tsx b/front/src/features/auth/components/LoginForm.tsx new file mode 100644 index 0000000..403c1c4 --- /dev/null +++ b/front/src/features/auth/components/LoginForm.tsx @@ -0,0 +1,60 @@ +import { Button, Input } from '@/components' +import { useLogin } from '@/features/auth/hooks/useLogin' + +const inputClassName = 'h-12 rounded-md px-4 text-base' + +export function LoginForm() { + const { + id, + password, + errorMessage, + isLoading, + onIdChange, + onPasswordChange, + onLogin, + } = useLogin() + + return ( +
{ + e.preventDefault() + onLogin() + }} + className="flex w-full flex-col justify-center gap-6" + > +
+ onIdChange(e.target.value)} + autoComplete="username" + className={inputClassName} + /> + onPasswordChange(e.target.value)} + autoComplete="current-password" + className={inputClassName} + /> +

+ {errorMessage || ' '} +

+
+ + +
+ ) +} + +export default LoginForm diff --git a/front/src/features/auth/hooks/useLogin.ts b/front/src/features/auth/hooks/useLogin.ts new file mode 100644 index 0000000..37a0470 --- /dev/null +++ b/front/src/features/auth/hooks/useLogin.ts @@ -0,0 +1,55 @@ +import { useState } from 'react' +import { useNavigate } from 'react-router' +import { useLoginMutation } from '@/features/auth/hooks/useLoginMutation' + +export function useLogin() { + const navigate = useNavigate() + const loginMutation = useLoginMutation() + const [id, setId] = useState('') + const [password, setPassword] = useState('') + const [errorMessage, setErrorMessage] = useState('') + + const onIdChange = (value: string) => { + setId(value) + if (errorMessage) setErrorMessage('') + } + + const onPasswordChange = (value: string) => { + setPassword(value) + if (errorMessage) setErrorMessage('') + } + + const onLogin = () => { + if (!id || !password) { + setErrorMessage('아이디와 비밀번호를 입력해주세요.') + return + } + + loginMutation.mutate( + { id, password }, + { + onSuccess: () => { + setErrorMessage('') + navigate('/list') + }, + onError: (error) => { + setErrorMessage( + error instanceof Error + ? error.message + : '아이디 또는 비밀번호가 올바르지 않습니다.', + ) + }, + }, + ) + } + + return { + id, + password, + errorMessage, + isLoading: loginMutation.isPending, + onIdChange, + onPasswordChange, + onLogin, + } +} diff --git a/front/src/features/auth/hooks/useLoginMutation.ts b/front/src/features/auth/hooks/useLoginMutation.ts new file mode 100644 index 0000000..d783d7f --- /dev/null +++ b/front/src/features/auth/hooks/useLoginMutation.ts @@ -0,0 +1,20 @@ +import { useMutation } from '@tanstack/react-query' + +export interface LoginParams { + id: string + password: string +} + +/** + * 로그인 mutation (임시 stub). + * + * TODO: mutationFn 을 실제 백엔드 호출로 교체. + * 현재는 API 미연동 상태라 검증만 통과하면 성공 처리한다. + */ +export function useLoginMutation() { + return useMutation({ + mutationFn: async () => { + // TODO: 실제 로그인 API 연동 + }, + }) +} diff --git a/front/src/features/auth/index.ts b/front/src/features/auth/index.ts new file mode 100644 index 0000000..ad04e7b --- /dev/null +++ b/front/src/features/auth/index.ts @@ -0,0 +1 @@ +export { LoginForm } from './components/LoginForm' From 9de140abd6378418089242fa6c78407100a095f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=ED=97=8C?= Date: Tue, 16 Jun 2026 14:33:23 +0900 Subject: [PATCH 12/21] =?UTF-8?q?[feat]=20front:=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC=ED=98=84(?= =?UTF-8?q?=EB=A1=9C=EA=B3=A0=C2=B7=ED=8F=BC=20=EC=A1=B0=EB=A6=BD,=20?= =?UTF-8?q?=EB=B0=98=EC=9D=91=ED=98=95/=EC=A0=91=EA=B7=BC=EC=84=B1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- front/src/pages/LoginPage.tsx | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/front/src/pages/LoginPage.tsx b/front/src/pages/LoginPage.tsx index 79a2514..8543179 100644 --- a/front/src/pages/LoginPage.tsx +++ b/front/src/pages/LoginPage.tsx @@ -1,5 +1,21 @@ +import { Logo } from '@/components' +import { LoginForm } from '@/features/auth' + export function LoginPage() { - return
+ return ( +
+
+
+ +

+ 로그인 +

+
+ + +
+
+ ) } export default LoginPage From 6da8976a204a03c2201aa39c0382d29e5f9fab80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=ED=97=8C?= Date: Tue, 16 Jun 2026 16:19:57 +0900 Subject: [PATCH 13/21] =?UTF-8?q?[feat]=20front:=20List/Chat=20=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=95=84=EC=9B=83=20+=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=20=EC=84=B9=EC=85=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - layouts: 공통 2분할 레이아웃 MainLayout(좌 사이드바/우 메인, 로고 헤더, 헤더 아래 콘텐츠 면) + 다크 헤더 바 MainHeaderBar 분리 - pages: ListPage/ChatPage 를 MainLayout 기반으로 구성(견적 선택/협상 잔여 시간 헤더) - features/list: 필터 섹션 추가 — ListFilter(요소)/FilterSection(컨테이너)/ filterOptions(데이터)/useListFilterStore(상태), SidebarFooter 분리 - deps: lucide-react 추가(필터 화살표·목록 이동 아이콘) - refactor: 모든 import 를 @ alias 로 통일 - fix: Button secondary variant hover(=accent 동일색) 및 필터 화살표 hover 색상 보정 Co-Authored-By: Claude Opus 4.8 (1M context) --- front/package-lock.json | 10 +++ front/package.json | 1 + front/src/components/Button.tsx | 4 +- front/src/components/Input.tsx | 2 +- front/src/components/Logo.tsx | 2 +- front/src/components/index.ts | 12 +-- front/src/features/auth/index.ts | 2 +- .../features/list/components/ListFilter.tsx | 69 +++++++++++++++++ .../list/components/SidebarFooter.tsx | 21 ++++++ .../list/containers/FilterSection.tsx | 35 +++++++++ front/src/features/list/index.ts | 4 + .../src/features/list/model/filterOptions.ts | 17 +++++ .../features/list/model/useListFilterStore.ts | 34 +++++++++ front/src/layouts/MainHeaderBar.tsx | 23 ++++++ front/src/layouts/MainLayout.tsx | 74 +++++++++++++++++++ front/src/layouts/index.ts | 4 + front/src/main.tsx | 4 +- front/src/pages/ChatPage.tsx | 44 ++++++++++- front/src/pages/ListPage.tsx | 27 ++++++- 19 files changed, 374 insertions(+), 15 deletions(-) create mode 100644 front/src/features/list/components/ListFilter.tsx create mode 100644 front/src/features/list/components/SidebarFooter.tsx create mode 100644 front/src/features/list/containers/FilterSection.tsx create mode 100644 front/src/features/list/index.ts create mode 100644 front/src/features/list/model/filterOptions.ts create mode 100644 front/src/features/list/model/useListFilterStore.ts create mode 100644 front/src/layouts/MainHeaderBar.tsx create mode 100644 front/src/layouts/MainLayout.tsx create mode 100644 front/src/layouts/index.ts diff --git a/front/package-lock.json b/front/package-lock.json index ff5dd4b..e1dc0fd 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@tanstack/react-query": "^5.101.0", "axios": "^1.18.0", + "lucide-react": "^1.18.0", "react": "^19.2.6", "react-dom": "^19.2.6", "react-router": "^7.17.0", @@ -2816,6 +2817,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.18.0.tgz", + "integrity": "sha512-LZDb7H/0YfM+RJncD0hDQRCAu+vSGODqpe35TuVI8EuXaRjkczbsx7p8dY4J87F/MUSj6bpYqeI8nw8qXaAdmA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", diff --git a/front/package.json b/front/package.json index fa76e6e..37a1496 100644 --- a/front/package.json +++ b/front/package.json @@ -15,6 +15,7 @@ "dependencies": { "@tanstack/react-query": "^5.101.0", "axios": "^1.18.0", + "lucide-react": "^1.18.0", "react": "^19.2.6", "react-dom": "^19.2.6", "react-router": "^7.17.0", diff --git a/front/src/components/Button.tsx b/front/src/components/Button.tsx index 6d5c995..949c786 100644 --- a/front/src/components/Button.tsx +++ b/front/src/components/Button.tsx @@ -1,5 +1,5 @@ import { type ComponentProps } from 'react' -import { cn } from './cn' +import { cn } from '@/components/cn' export type ButtonVariant = | 'primary' @@ -19,7 +19,7 @@ const base = // hover 는 한 단계 톤다운(brand-700) — 디자인 선호 const variantClass: Record = { primary: 'bg-primary text-primary-foreground hover:bg-brand-700', - secondary: 'bg-secondary text-secondary-foreground hover:bg-accent', + secondary: 'bg-secondary text-secondary-foreground hover:bg-border', outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', ghost: 'hover:bg-accent hover:text-accent-foreground', diff --git a/front/src/components/Input.tsx b/front/src/components/Input.tsx index d2f9703..6afa4c3 100644 --- a/front/src/components/Input.tsx +++ b/front/src/components/Input.tsx @@ -1,5 +1,5 @@ import { type ComponentProps } from 'react' -import { cn } from './cn' +import { cn } from '@/components/cn' export function Input({ type = 'text', className, ...props }: ComponentProps<'input'>) { return ( diff --git a/front/src/components/Logo.tsx b/front/src/components/Logo.tsx index 8278918..13bcf9d 100644 --- a/front/src/components/Logo.tsx +++ b/front/src/components/Logo.tsx @@ -1,5 +1,5 @@ import { type ComponentProps } from 'react' -import { cn } from './cn' +import { cn } from '@/components/cn' import logoColor from '@/assets/imarketkorea-logo.png' import logoWhite from '@/assets/imarketkorea-logo-white.png' diff --git a/front/src/components/index.ts b/front/src/components/index.ts index c47bd32..4a698d5 100644 --- a/front/src/components/index.ts +++ b/front/src/components/index.ts @@ -1,6 +1,6 @@ -export { cn } from './cn' -export { Button } from './Button' -export type { ButtonProps, ButtonVariant, ButtonSize } from './Button' -export { Input } from './Input' -export { Logo } from './Logo' -export type { LogoProps, LogoVariant } from './Logo' +export { cn } from '@/components/cn' +export { Button } from '@/components/Button' +export type { ButtonProps, ButtonVariant, ButtonSize } from '@/components/Button' +export { Input } from '@/components/Input' +export { Logo } from '@/components/Logo' +export type { LogoProps, LogoVariant } from '@/components/Logo' diff --git a/front/src/features/auth/index.ts b/front/src/features/auth/index.ts index ad04e7b..3510753 100644 --- a/front/src/features/auth/index.ts +++ b/front/src/features/auth/index.ts @@ -1 +1 @@ -export { LoginForm } from './components/LoginForm' +export { LoginForm } from '@/features/auth/components/LoginForm' diff --git a/front/src/features/list/components/ListFilter.tsx b/front/src/features/list/components/ListFilter.tsx new file mode 100644 index 0000000..9416a84 --- /dev/null +++ b/front/src/features/list/components/ListFilter.tsx @@ -0,0 +1,69 @@ +import { useState } from 'react' +import { ChevronDown } from 'lucide-react' +import { cn } from '@/components' + +export interface ListFilterProps { + title: string + items: readonly string[] + selectedItem: string | null + onItemClick: (item: string) => void + defaultOpen?: boolean +} + +export function ListFilter({ + title, + items, + selectedItem, + onItemClick, + defaultOpen = true, +}: ListFilterProps) { + const [open, setOpen] = useState(defaultOpen) + + return ( +
+
+

+ {title} +

+ +
+ + {/* grid-rows 0fr→1fr 로 높이를 부드럽게 펼침 */} +
+
+
+ {items.map((item) => ( + + ))} +
+
+
+
+ ) +} diff --git a/front/src/features/list/components/SidebarFooter.tsx b/front/src/features/list/components/SidebarFooter.tsx new file mode 100644 index 0000000..454abc0 --- /dev/null +++ b/front/src/features/list/components/SidebarFooter.tsx @@ -0,0 +1,21 @@ +import { Button } from '@/components' + +/** 좌측 사이드바 하단: 공급사명 + 로그아웃 */ +export function SidebarFooter() { + return ( +
+
+ {/* TODO: 공급사명 (auth store 연동) */} + - +
+ +
+ ) +} diff --git a/front/src/features/list/containers/FilterSection.tsx b/front/src/features/list/containers/FilterSection.tsx new file mode 100644 index 0000000..13fd808 --- /dev/null +++ b/front/src/features/list/containers/FilterSection.tsx @@ -0,0 +1,35 @@ +import { ListFilter } from '@/features/list/components/ListFilter' +import { FILTER_GROUPS, type FilterKey } from '@/features/list/model/filterOptions' +import { useListFilterStore } from '@/features/list/model/useListFilterStore' + +export function FilterSection() { + const store = useListFilterStore() + + // 필터 키 → store 상태/핸들러 매핑 + const selected: Record = { + type: store.selectedType, + status: store.selectedStatus, + deadline: store.selectedDeadline, + } + const toggle: Record void> = { + type: store.toggleType, + status: store.toggleStatus, + deadline: store.toggleDeadline, + } + + return ( +
+
+ {FILTER_GROUPS.map((group) => ( + + ))} +
+
+ ) +} diff --git a/front/src/features/list/index.ts b/front/src/features/list/index.ts new file mode 100644 index 0000000..43ad6cc --- /dev/null +++ b/front/src/features/list/index.ts @@ -0,0 +1,4 @@ +// 공개 API: 페이지에서 쓰는 것만 노출 (ListFilter/FILTER_GROUPS 등은 feature 내부 구현) +export { FilterSection } from '@/features/list/containers/FilterSection' +export { SidebarFooter } from '@/features/list/components/SidebarFooter' +export { useListFilterStore } from '@/features/list/model/useListFilterStore' diff --git a/front/src/features/list/model/filterOptions.ts b/front/src/features/list/model/filterOptions.ts new file mode 100644 index 0000000..7cdf965 --- /dev/null +++ b/front/src/features/list/model/filterOptions.ts @@ -0,0 +1,17 @@ +export type FilterKey = 'type' | 'status' | 'deadline' + +export interface FilterGroup { + key: FilterKey + title: string + items: readonly string[] +} + +export const FILTER_GROUPS: readonly FilterGroup[] = [ + { key: 'type', title: '구분', items: ['재견적', '재협상'] }, + { + key: 'status', + title: '상태', + items: ['협상생성', '협상중', '협상완료', '미참여', '협상거부'], + }, + { key: 'deadline', title: '마감일', items: ['남은 시간 적은 순', '남은 시간 긴 순'] }, +] diff --git a/front/src/features/list/model/useListFilterStore.ts b/front/src/features/list/model/useListFilterStore.ts new file mode 100644 index 0000000..27e892b --- /dev/null +++ b/front/src/features/list/model/useListFilterStore.ts @@ -0,0 +1,34 @@ +import { create } from 'zustand' + +interface ListFilterState { + selectedType: string | null + selectedStatus: string | null + selectedDeadline: string | null + currentPage: number + + toggleType: (item: string) => void + toggleStatus: (item: string) => void + toggleDeadline: (item: string) => void + setCurrentPage: (page: number) => void + resetFilters: () => void +} + +// 같은 항목을 다시 누르면 해제 +const toggle = (current: string | null, item: string) => (current === item ? null : item) + +export const useListFilterStore = create((set) => ({ + selectedType: null, + selectedStatus: null, + selectedDeadline: null, + currentPage: 1, + + toggleType: (item) => + set((s) => ({ selectedType: toggle(s.selectedType, item), currentPage: 1 })), + toggleStatus: (item) => + set((s) => ({ selectedStatus: toggle(s.selectedStatus, item), currentPage: 1 })), + toggleDeadline: (item) => + set((s) => ({ selectedDeadline: toggle(s.selectedDeadline, item), currentPage: 1 })), + setCurrentPage: (page) => set({ currentPage: page }), + resetFilters: () => + set({ selectedType: null, selectedStatus: null, selectedDeadline: null, currentPage: 1 }), +})) diff --git a/front/src/layouts/MainHeaderBar.tsx b/front/src/layouts/MainHeaderBar.tsx new file mode 100644 index 0000000..9e4652d --- /dev/null +++ b/front/src/layouts/MainHeaderBar.tsx @@ -0,0 +1,23 @@ +import { type ReactNode } from 'react' +import { cn } from '@/components' + +// 우측 상단 다크 헤더 바 +const HEADER_BASE = + 'flex w-full h-[56px] py-[18px] items-center rounded-t-[8px] bg-foreground text-background' + +const HEADER_ALIGN = { + left: 'justify-start', + center: 'justify-center', +} as const + +export interface MainHeaderBarProps { + align?: keyof typeof HEADER_ALIGN + className?: string + children: ReactNode +} + +export function MainHeaderBar({ align = 'left', className, children }: MainHeaderBarProps) { + return
{children}
+} + +export default MainHeaderBar diff --git a/front/src/layouts/MainLayout.tsx b/front/src/layouts/MainLayout.tsx new file mode 100644 index 0000000..1ae4894 --- /dev/null +++ b/front/src/layouts/MainLayout.tsx @@ -0,0 +1,74 @@ +import { type ReactNode } from 'react' +import { Logo, cn } from '@/components' + +// 좌측 폭: list=반응형 비율 / chat=고정 350px +const SIDEBAR_WIDTH = { + list: + 'w-[20%] max-w-[320px] ' + + 'max-[1520px]:w-[23%] max-[1410px]:w-[25%] ' + + 'max-[1180px]:w-[27%] max-[1024px]:w-[29%] max-[960px]:w-[33%]', + chat: 'w-[350px]', +} as const + +const styles = { + root: 'flex w-full min-h-screen max-h-screen bg-background', + scrollX: 'flex w-full min-h-screen max-h-screen overflow-x-auto overflow-y-hidden', + scrollXInner: 'flex w-full min-h-screen max-h-screen min-w-[1350px] bg-background', + sidebar: 'flex flex-col h-screen pt-2 bg-background', + logoHeader: + 'flex w-full h-[56px] pl-[32px] pr-[24px] py-[8px] items-center justify-between shrink-0', + main: 'flex flex-1 flex-col h-screen overflow-hidden pt-2 pr-2 pb-2 pl-0', + // 헤더 아래 콘텐츠 면 (다크 헤더 rounded-t 와 합쳐져 카드 형태) + content: 'flex flex-1 flex-col min-h-0 w-full overflow-hidden rounded-b-[8px] bg-muted', +} as const + +export type SidebarWidth = keyof typeof SIDEBAR_WIDTH + +export interface MainLayoutProps { + sidebarWidth?: SidebarWidth + /** 루트 가로 스크롤 + 최소폭 (chat 전용) */ + scrollX?: boolean + /** 로고 우측 컨트롤 */ + logoAction?: ReactNode + sidebar: ReactNode + header: ReactNode + children?: ReactNode +} + +export function MainLayout({ + sidebarWidth = 'list', + scrollX = false, + logoAction, + sidebar, + header, + children, +}: MainLayoutProps) { + const panes = ( + <> + + +
+ {header} +
{children}
+
+ + ) + + if (!scrollX) { + return
{panes}
+ } + + return ( +
+
{panes}
+
+ ) +} + +export default MainLayout diff --git a/front/src/layouts/index.ts b/front/src/layouts/index.ts new file mode 100644 index 0000000..412789b --- /dev/null +++ b/front/src/layouts/index.ts @@ -0,0 +1,4 @@ +export { MainLayout } from '@/layouts/MainLayout' +export type { MainLayoutProps, SidebarWidth } from '@/layouts/MainLayout' +export { MainHeaderBar } from '@/layouts/MainHeaderBar' +export type { MainHeaderBarProps } from '@/layouts/MainHeaderBar' diff --git a/front/src/main.tsx b/front/src/main.tsx index 40181dd..b0f2ed4 100644 --- a/front/src/main.tsx +++ b/front/src/main.tsx @@ -1,8 +1,8 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import { Provider } from '@/core/provider' -import './index.css' -import App from './App.tsx' +import '@/index.css' +import App from '@/App' createRoot(document.getElementById('root')!).render( diff --git a/front/src/pages/ChatPage.tsx b/front/src/pages/ChatPage.tsx index 1ab73e0..0d7cd19 100644 --- a/front/src/pages/ChatPage.tsx +++ b/front/src/pages/ChatPage.tsx @@ -1,5 +1,47 @@ +import { useNavigate } from 'react-router' +import { List } from 'lucide-react' +import { MainLayout, MainHeaderBar } from '@/layouts' + export function ChatPage() { - return
+ return ( + } + sidebar={} + header={ + + 협상 잔여 시간 : - + + } + > + {/* TODO: MainContent (ChatSection / MenuSection) */} + + ) +} + +function ListNavButton() { + const navigate = useNavigate() + return ( + + ) +} + +function Sidebar() { + return ( + <> + {/* TODO: ItemSection */} +
+ {/* TODO: FooterSection */} + + ) } export default ChatPage diff --git a/front/src/pages/ListPage.tsx b/front/src/pages/ListPage.tsx index c9ea48b..9d49500 100644 --- a/front/src/pages/ListPage.tsx +++ b/front/src/pages/ListPage.tsx @@ -1,5 +1,30 @@ +import { MainLayout, MainHeaderBar } from '@/layouts' +import { FilterSection, SidebarFooter } from '@/features/list' + export function ListPage() { - return
+ return ( + } + header={ + + 견적 선택 + + } + > + {/* TODO: Content (ButtonSection / TableSection / PageSection) */} + + ) +} + +function Sidebar() { + return ( + <> + + {/* TODO: ArchiveSection */} + + + ) } export default ListPage From 10738880734f352886bb17d5e256ca7a28a5623f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=ED=97=8C?= Date: Wed, 17 Jun 2026 10:47:25 +0900 Subject: [PATCH 14/21] =?UTF-8?q?refactor(front):=20=EA=B3=B5=EC=9A=A9=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8/=EC=9C=A0=ED=8B=B8=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EC=A0=95=EB=B9=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - cn·interactive를 components에서 lib로 분리 (컴포넌트와 순수 유틸 경계 정리) - core/provider.tsx → Provider.tsx 개명 (컴포넌트 파일 PascalCase 통일) - 불필요한 주석 정리 Co-Authored-By: Claude Opus 4.8 (1M context) --- front/src/components/Button.tsx | 20 +++++++++----------- front/src/components/Input.tsx | 2 +- front/src/components/Logo.tsx | 9 +++------ front/src/components/cn.ts | 11 ----------- front/src/components/index.ts | 1 - front/src/lib/cn.ts | 6 ++++++ front/src/lib/index.ts | 4 ++++ front/src/lib/interactive.ts | 3 +++ 8 files changed, 26 insertions(+), 30 deletions(-) delete mode 100644 front/src/components/cn.ts create mode 100644 front/src/lib/cn.ts create mode 100644 front/src/lib/index.ts create mode 100644 front/src/lib/interactive.ts diff --git a/front/src/components/Button.tsx b/front/src/components/Button.tsx index 949c786..64464a7 100644 --- a/front/src/components/Button.tsx +++ b/front/src/components/Button.tsx @@ -1,5 +1,5 @@ import { type ComponentProps } from 'react' -import { cn } from '@/components/cn' +import { cn, interactive } from '@/lib' export type ButtonVariant = | 'primary' @@ -10,20 +10,18 @@ export type ButtonVariant = export type ButtonSize = 'sm' | 'md' | 'lg' const base = - 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium ' + - 'transition-[color,background-color,transform] cursor-pointer select-none ' + - 'active:scale-[0.98] ' + - 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ' + + 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium select-none ' + + interactive + + ' focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ' + 'disabled:pointer-events-none disabled:opacity-50' -// hover 는 한 단계 톤다운(brand-700) — 디자인 선호 const variantClass: Record = { - primary: 'bg-primary text-primary-foreground hover:bg-brand-700', - secondary: 'bg-secondary text-secondary-foreground hover:bg-border', - outline: - 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', + primary: 'bg-primary text-primary-foreground', + secondary: 'bg-secondary text-secondary-foreground', + outline: 'border border-input bg-background', + // 투명 배경이라 brightness 무효 → bg 하이라이트 ghost: 'hover:bg-accent hover:text-accent-foreground', - destructive: 'bg-destructive text-white hover:opacity-90', + destructive: 'bg-destructive text-white', } const sizeClass: Record = { diff --git a/front/src/components/Input.tsx b/front/src/components/Input.tsx index 6afa4c3..cf733de 100644 --- a/front/src/components/Input.tsx +++ b/front/src/components/Input.tsx @@ -1,5 +1,5 @@ import { type ComponentProps } from 'react' -import { cn } from '@/components/cn' +import { cn } from '@/lib' export function Input({ type = 'text', className, ...props }: ComponentProps<'input'>) { return ( diff --git a/front/src/components/Logo.tsx b/front/src/components/Logo.tsx index 13bcf9d..f4efe89 100644 --- a/front/src/components/Logo.tsx +++ b/front/src/components/Logo.tsx @@ -1,5 +1,5 @@ import { type ComponentProps } from 'react' -import { cn } from '@/components/cn' +import { cn } from '@/lib' import logoColor from '@/assets/imarketkorea-logo.png' import logoWhite from '@/assets/imarketkorea-logo-white.png' @@ -7,11 +7,10 @@ export type LogoVariant = 'color' | 'white' export type LogoSize = 'sm' | 'md' | 'lg' const sources: Record = { - color: logoColor, // 브랜드 블루 심볼 — 밝은 배경용 - white: logoWhite, // 화이트 심볼 — 어두운 배경용 + color: logoColor, + white: logoWhite, } -// 심볼 높이에 맞춰 텍스트도 함께 스케일한다. const sizes: Record = { sm: { img: 'h-6', text: 'text-base', gap: 'gap-1.5' }, md: { img: 'h-8', text: 'text-xl', gap: 'gap-2' }, @@ -21,9 +20,7 @@ const sizes: Record = { export interface LogoProps extends Omit, 'children'> { variant?: LogoVariant size?: LogoSize - /** 워드마크(iMarket Korea) 텍스트 노출 여부 */ withText?: boolean - /** 심볼 이미지 alt */ alt?: string } diff --git a/front/src/components/cn.ts b/front/src/components/cn.ts deleted file mode 100644 index b86f42f..0000000 --- a/front/src/components/cn.ts +++ /dev/null @@ -1,11 +0,0 @@ -// 경량 className 병합 헬퍼 (외부 의존성 없음). -// falsy 값을 걸러 공백으로 join 한다. 조건부 클래스: cn('base', isActive && 'active'). -// -// 주의: tailwind-merge 가 아니므로 같은 속성의 Tailwind 유틸이 충돌할 때 -// (예: 'px-4' + 'px-2') 자동 정리는 하지 않는다. 전달한 className 을 -// 뒤에 두어 우선하도록 의도하지만, 최종 승자는 CSS 출력 순서를 따른다. -export type ClassValue = string | number | false | null | undefined - -export function cn(...classes: ClassValue[]): string { - return classes.filter(Boolean).join(' ') -} diff --git a/front/src/components/index.ts b/front/src/components/index.ts index 4a698d5..78e677c 100644 --- a/front/src/components/index.ts +++ b/front/src/components/index.ts @@ -1,4 +1,3 @@ -export { cn } from '@/components/cn' export { Button } from '@/components/Button' export type { ButtonProps, ButtonVariant, ButtonSize } from '@/components/Button' export { Input } from '@/components/Input' diff --git a/front/src/lib/cn.ts b/front/src/lib/cn.ts new file mode 100644 index 0000000..438021b --- /dev/null +++ b/front/src/lib/cn.ts @@ -0,0 +1,6 @@ +// className 병합. tailwind-merge 아님 — 같은 속성 충돌 시 CSS 출력 순서가 승자. +export type ClassValue = string | number | false | null | undefined + +export function cn(...classes: ClassValue[]): string { + return classes.filter(Boolean).join(' ') +} diff --git a/front/src/lib/index.ts b/front/src/lib/index.ts new file mode 100644 index 0000000..af3e974 --- /dev/null +++ b/front/src/lib/index.ts @@ -0,0 +1,4 @@ +// 전역 순수 유틸/스타일 +export { cn } from '@/lib/cn' +export type { ClassValue } from '@/lib/cn' +export { interactive } from '@/lib/interactive' diff --git a/front/src/lib/interactive.ts b/front/src/lib/interactive.ts new file mode 100644 index 0000000..90dfd4f --- /dev/null +++ b/front/src/lib/interactive.ts @@ -0,0 +1,3 @@ +// 공통 인터랙션: hover=brightness↓, active=scale↓. transform/filter 만 전환(잔상 방지). +export const interactive = + 'transition-[transform,filter] duration-150 ease-out cursor-pointer hover:brightness-[0.97] active:scale-[0.98]' From a56e2250327397886a939e66c303cc1d856a4047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=ED=97=8C?= Date: Wed, 17 Jun 2026 10:47:32 +0900 Subject: [PATCH 15/21] =?UTF-8?q?style(front):=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=95=84=EC=9B=83=EC=97=90=20=EB=B8=8C=EB=9E=9C=EB=93=9C=20?= =?UTF-8?q?=ED=86=A4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 헤더 바 bg를 brand(primary)로, 콘텐츠 면 bg를 브랜드 틴트 쿨 그레이(surface)로 변경 - 불필요한 주석 정리 Co-Authored-By: Claude Opus 4.8 (1M context) --- front/src/layouts/MainHeaderBar.tsx | 5 ++--- front/src/layouts/MainLayout.tsx | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/front/src/layouts/MainHeaderBar.tsx b/front/src/layouts/MainHeaderBar.tsx index 9e4652d..811d1f6 100644 --- a/front/src/layouts/MainHeaderBar.tsx +++ b/front/src/layouts/MainHeaderBar.tsx @@ -1,9 +1,8 @@ import { type ReactNode } from 'react' -import { cn } from '@/components' +import { cn } from '@/lib' -// 우측 상단 다크 헤더 바 const HEADER_BASE = - 'flex w-full h-[56px] py-[18px] items-center rounded-t-[8px] bg-foreground text-background' + 'flex w-full h-[56px] py-[18px] items-center rounded-t-[8px] bg-primary text-primary-foreground' const HEADER_ALIGN = { left: 'justify-start', diff --git a/front/src/layouts/MainLayout.tsx b/front/src/layouts/MainLayout.tsx index 1ae4894..76c3751 100644 --- a/front/src/layouts/MainLayout.tsx +++ b/front/src/layouts/MainLayout.tsx @@ -1,5 +1,6 @@ import { type ReactNode } from 'react' -import { Logo, cn } from '@/components' +import { Logo } from '@/components' +import { cn } from '@/lib' // 좌측 폭: list=반응형 비율 / chat=고정 350px const SIDEBAR_WIDTH = { @@ -18,8 +19,8 @@ const styles = { logoHeader: 'flex w-full h-[56px] pl-[32px] pr-[24px] py-[8px] items-center justify-between shrink-0', main: 'flex flex-1 flex-col h-screen overflow-hidden pt-2 pr-2 pb-2 pl-0', - // 헤더 아래 콘텐츠 면 (다크 헤더 rounded-t 와 합쳐져 카드 형태) - content: 'flex flex-1 flex-col min-h-0 w-full overflow-hidden rounded-b-[8px] bg-muted', + // 다크 헤더와 합쳐져 카드 형태 + content: 'flex flex-1 flex-col min-h-0 w-full overflow-hidden rounded-b-[8px] bg-surface', } as const export type SidebarWidth = keyof typeof SIDEBAR_WIDTH @@ -28,7 +29,6 @@ export interface MainLayoutProps { sidebarWidth?: SidebarWidth /** 루트 가로 스크롤 + 최소폭 (chat 전용) */ scrollX?: boolean - /** 로고 우측 컨트롤 */ logoAction?: ReactNode sidebar: ReactNode header: ReactNode From 32db1300e013b2ce0c7589750b7d144ad3d91b56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=ED=97=8C?= Date: Wed, 17 Jun 2026 10:47:38 +0900 Subject: [PATCH 16/21] =?UTF-8?q?refactor(front):=20auth=20=E2=80=94=20Sid?= =?UTF-8?q?ebarFooter=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SidebarFooter(공급사명·로그아웃)를 list에서 auth로 이동 (도메인 일치) - useLoginMutation 주석 정리 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../auth/components/SidebarFooter.tsx | 21 +++++++++++++++++++ .../features/auth/hooks/useLoginMutation.ts | 11 ++-------- front/src/features/auth/index.ts | 1 + 3 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 front/src/features/auth/components/SidebarFooter.tsx diff --git a/front/src/features/auth/components/SidebarFooter.tsx b/front/src/features/auth/components/SidebarFooter.tsx new file mode 100644 index 0000000..aeca1d5 --- /dev/null +++ b/front/src/features/auth/components/SidebarFooter.tsx @@ -0,0 +1,21 @@ +import { Button } from '@/components' + +// 사이드바 하단: 공급사명 + 로그아웃 +export function SidebarFooter() { + return ( +
+
+ {/* TODO: 공급사명 (auth store 연동) */} + - +
+ +
+ ) +} diff --git a/front/src/features/auth/hooks/useLoginMutation.ts b/front/src/features/auth/hooks/useLoginMutation.ts index d783d7f..3295d41 100644 --- a/front/src/features/auth/hooks/useLoginMutation.ts +++ b/front/src/features/auth/hooks/useLoginMutation.ts @@ -5,16 +5,9 @@ export interface LoginParams { password: string } -/** - * 로그인 mutation (임시 stub). - * - * TODO: mutationFn 을 실제 백엔드 호출로 교체. - * 현재는 API 미연동 상태라 검증만 통과하면 성공 처리한다. - */ +// 임시 stub (검증만 통과하면 성공). TODO: 로그인 API 연동 export function useLoginMutation() { return useMutation({ - mutationFn: async () => { - // TODO: 실제 로그인 API 연동 - }, + mutationFn: async () => {}, }) } diff --git a/front/src/features/auth/index.ts b/front/src/features/auth/index.ts index 3510753..2b91506 100644 --- a/front/src/features/auth/index.ts +++ b/front/src/features/auth/index.ts @@ -1 +1,2 @@ export { LoginForm } from '@/features/auth/components/LoginForm' +export { SidebarFooter } from '@/features/auth/components/SidebarFooter' From 5eb83f9d5483996e734a7456887264cf8b652242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=ED=97=8C?= Date: Wed, 17 Jun 2026 10:47:47 +0900 Subject: [PATCH 17/21] =?UTF-8?q?refactor(front):=20list=20=E2=80=94=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EC=83=89=EC=83=81=20=EC=A0=95=EB=B9=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TableSection에서 컬럼 설정(tableColumns)·날짜 유틸(datetime) 분리 - PageSection → Pagination 개명, 인라인 SVG를 paginationIcons로 분리 - 상태 색상 정비: 협상중=강조(다크), 미참여=회색, 나머지 의미색 유지 - 활성 페이지·목데이터 격리(mocks), 페이지 크기 20 복구, 주석 정리 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../list/components/ActionSection.tsx | 24 ++++ .../{ListFilter.tsx => FilterGroup.tsx} | 16 +-- .../features/list/components/Pagination.tsx | 102 ++++++++++++++++ .../list/components/SidebarFooter.tsx | 21 ---- .../features/list/components/TableSection.tsx | 79 ++++++++++++ .../list/components/paginationIcons.tsx | 32 +++++ .../features/list/components/tableColumns.tsx | 45 +++++++ .../list/containers/ContentContainer.tsx | 27 ++++ .../list/containers/FilterContainer.tsx | 52 ++++++++ .../list/containers/FilterSection.tsx | 35 ------ .../list/{model => }/filterOptions.ts | 4 +- front/src/features/list/hooks/useList.ts | 57 +++++++++ front/src/features/list/index.ts | 7 +- front/src/features/list/lib/datetime.ts | 8 ++ front/src/features/list/lib/pagination.ts | 18 +++ front/src/features/list/mocks/mockItems.ts | 115 ++++++++++++++++++ .../useListStore.ts} | 5 +- front/src/features/list/types.ts | 11 ++ 18 files changed, 585 insertions(+), 73 deletions(-) create mode 100644 front/src/features/list/components/ActionSection.tsx rename front/src/features/list/components/{ListFilter.tsx => FilterGroup.tsx} (79%) create mode 100644 front/src/features/list/components/Pagination.tsx delete mode 100644 front/src/features/list/components/SidebarFooter.tsx create mode 100644 front/src/features/list/components/TableSection.tsx create mode 100644 front/src/features/list/components/paginationIcons.tsx create mode 100644 front/src/features/list/components/tableColumns.tsx create mode 100644 front/src/features/list/containers/ContentContainer.tsx create mode 100644 front/src/features/list/containers/FilterContainer.tsx delete mode 100644 front/src/features/list/containers/FilterSection.tsx rename front/src/features/list/{model => }/filterOptions.ts (81%) create mode 100644 front/src/features/list/hooks/useList.ts create mode 100644 front/src/features/list/lib/datetime.ts create mode 100644 front/src/features/list/lib/pagination.ts create mode 100644 front/src/features/list/mocks/mockItems.ts rename front/src/features/list/{model/useListFilterStore.ts => stores/useListStore.ts} (87%) create mode 100644 front/src/features/list/types.ts diff --git a/front/src/features/list/components/ActionSection.tsx b/front/src/features/list/components/ActionSection.tsx new file mode 100644 index 0000000..36ce83c --- /dev/null +++ b/front/src/features/list/components/ActionSection.tsx @@ -0,0 +1,24 @@ +import { cn, interactive } from '@/lib' + +const PILL = + 'flex items-center justify-center w-full max-w-[130px] h-[50px] rounded-full py-4 px-6 ' + + 'text-lg font-semibold whitespace-nowrap ' + + interactive + +export function ActionSection() { + return ( +
+ {/* TODO: 협상 참여 동작 연동 */} + + {/* TODO: 거부 동작 연동 */} + +
+ ) +} diff --git a/front/src/features/list/components/ListFilter.tsx b/front/src/features/list/components/FilterGroup.tsx similarity index 79% rename from front/src/features/list/components/ListFilter.tsx rename to front/src/features/list/components/FilterGroup.tsx index 9416a84..10fbf8b 100644 --- a/front/src/features/list/components/ListFilter.tsx +++ b/front/src/features/list/components/FilterGroup.tsx @@ -1,8 +1,8 @@ import { useState } from 'react' import { ChevronDown } from 'lucide-react' -import { cn } from '@/components' +import { cn, interactive } from '@/lib' -export interface ListFilterProps { +export interface FilterGroupProps { title: string items: readonly string[] selectedItem: string | null @@ -10,13 +10,13 @@ export interface ListFilterProps { defaultOpen?: boolean } -export function ListFilter({ +export function FilterGroup({ title, items, selectedItem, onItemClick, defaultOpen = true, -}: ListFilterProps) { +}: FilterGroupProps) { const [open, setOpen] = useState(defaultOpen) return ( @@ -29,7 +29,7 @@ export function ListFilter({ type="button" aria-expanded={open} onClick={() => setOpen((v) => !v)} - className="w-10 h-10 flex items-center justify-center cursor-pointer text-muted-foreground hover:text-foreground transition-colors" + className={cn('w-10 h-10 flex items-center justify-center text-muted-foreground', interactive)} >
- {/* grid-rows 0fr→1fr 로 높이를 부드럽게 펼침 */} + {/* grid-rows 0fr→1fr 펼침 */}
onItemClick(item)} className={cn( 'h-11 px-3 flex items-center rounded-lg text-left text-base text-foreground', - 'whitespace-nowrap cursor-pointer transition-colors duration-200', - selectedItem === item ? 'bg-secondary' : 'hover:bg-secondary/60', + 'whitespace-nowrap cursor-pointer transition active:scale-[0.98]', + selectedItem === item ? 'bg-secondary' : 'hover:bg-muted', )} > {item} diff --git a/front/src/features/list/components/Pagination.tsx b/front/src/features/list/components/Pagination.tsx new file mode 100644 index 0000000..44f3893 --- /dev/null +++ b/front/src/features/list/components/Pagination.tsx @@ -0,0 +1,102 @@ +import { cn, interactive } from '@/lib' +import { buildPageItems } from '@/features/list/lib/pagination' +import { ArrowIcon, DotsIcon } from '@/features/list/components/paginationIcons' + +interface PaginationProps { + totalPages: number + currentPage: number + onPageChange: (page: number) => void +} + +export function Pagination({ totalPages, currentPage, onPageChange }: PaginationProps) { + if (totalPages === 0) { + return
+ } + + const items = buildPageItems(currentPage, totalPages) + + return ( +
+
+ 1} + onClick={() => onPageChange(currentPage - 1)} + /> + +
+
+ {items.map((item, idx) => + item === 'dots' ? ( + + ) : ( + onPageChange(item)} + /> + ), + )} +
+
+ + onPageChange(currentPage + 1)} + /> +
+
+ ) +} + +function PageNumber({ + page, + active, + onClick, +}: { + page: number + active: boolean + onClick: () => void +}) { + return ( + + ) +} + +function NavButton({ + dir, + enabled, + onClick, +}: { + dir: 'prev' | 'next' + enabled: boolean + onClick: () => void +}) { + return ( + + ) +} diff --git a/front/src/features/list/components/SidebarFooter.tsx b/front/src/features/list/components/SidebarFooter.tsx deleted file mode 100644 index 454abc0..0000000 --- a/front/src/features/list/components/SidebarFooter.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Button } from '@/components' - -/** 좌측 사이드바 하단: 공급사명 + 로그아웃 */ -export function SidebarFooter() { - return ( -
-
- {/* TODO: 공급사명 (auth store 연동) */} - - -
- -
- ) -} diff --git a/front/src/features/list/components/TableSection.tsx b/front/src/features/list/components/TableSection.tsx new file mode 100644 index 0000000..76d276b --- /dev/null +++ b/front/src/features/list/components/TableSection.tsx @@ -0,0 +1,79 @@ +import { type ReactNode } from 'react' +import { Loader2 } from 'lucide-react' +import { cn } from '@/lib' +import type { ListItem } from '@/features/list/types' +import { COLUMNS } from '@/features/list/components/tableColumns' + +interface TableSectionProps { + items: ListItem[] + selectedId?: string + isLoading?: boolean + onItemClick?: (item: ListItem) => void +} + +export function TableSection({ items, selectedId, isLoading, onItemClick }: TableSectionProps) { + return ( +
+
+ + + + {COLUMNS.map((col) => ( + + ))} + + + + + {isLoading ? ( + + + + ) : items.length === 0 ? ( + + 조회된 협상 내역이 없습니다. + + ) : ( + items.map((item) => ( + onItemClick?.(item)} + className={cn( + 'h-[53px] cursor-pointer text-lg text-foreground transition-colors', + item.session_id === selectedId ? 'bg-table-selected' : 'hover:bg-table-hover', + )} + > + {COLUMNS.map((col) => ( + + ))} + + )) + )} + +
+ {col.label} +
+ {col.render ? col.render(item) : item[col.key] || '-'} +
+
+
+ ) +} + +// 로딩/빈 상태 행 +function StateRow({ children }: { children: ReactNode }) { + return ( + + +
{children}
+ + + ) +} diff --git a/front/src/features/list/components/paginationIcons.tsx b/front/src/features/list/components/paginationIcons.tsx new file mode 100644 index 0000000..b7f8ba8 --- /dev/null +++ b/front/src/features/list/components/paginationIcons.tsx @@ -0,0 +1,32 @@ +// 페이지네이션 인라인 SVG (색 = currentColor) + +const ARROW_PATH = { + prev: 'M26.9995 30.6532L21.3457 24.9995L26.9995 19.3457L28.0532 20.3995L23.4532 24.9995L28.0532 29.5995L26.9995 30.6532Z', + next: 'M23.0005 30.6532L28.6543 24.9995L23.0005 19.3457L21.9468 20.3995L26.5468 24.9995L21.9468 29.5995L23.0005 30.6532Z', +} as const + +export function ArrowIcon({ dir }: { dir: 'prev' | 'next' }) { + return ( + + + + ) +} + +export function DotsIcon() { + return ( + + + + ) +} diff --git a/front/src/features/list/components/tableColumns.tsx b/front/src/features/list/components/tableColumns.tsx new file mode 100644 index 0000000..16f5a0f --- /dev/null +++ b/front/src/features/list/components/tableColumns.tsx @@ -0,0 +1,45 @@ +import { type ReactNode } from 'react' +import { formatDateTime } from '@/features/list/lib/datetime' +import type { ListItem } from '@/features/list/types' + +// 컬럼 설정 (렌더링은 TableSection) +export interface Column { + key: keyof ListItem + label: string + width: string + align: 'text-left' | 'text-center' + render?: (item: ListItem) => ReactNode +} + +const STATUS_COLOR: Record = { + 협상생성: 'text-info font-semibold', + 협상중: 'text-foreground font-semibold', + 협상완료: 'text-success font-semibold', + 미참여: 'text-muted-foreground font-semibold', + 협상거부: 'text-destructive font-semibold', +} + +export const COLUMNS: Column[] = [ + { key: 'item_code', label: '상품코드', width: 'w-[8.39%]', align: 'text-center' }, + { + key: 'session_status', + label: '상태', + width: 'w-[6.57%]', + align: 'text-center', + render: (item) => ( + {item.session_status || '-'} + ), + }, + { key: 'qt_number', label: '견적번호', width: 'w-[14.29%]', align: 'text-center' }, + { key: 'qt_type', label: '구분', width: 'w-[5.52%]', align: 'text-center' }, + { key: 'item_name', label: '상품명', width: 'w-[20.96%]', align: 'text-left' }, + { key: 'model_name', label: '모델명', width: 'w-[22.89%]', align: 'text-left' }, + { key: 'maker_name', label: '제조사', width: 'w-[8.67%]', align: 'text-center' }, + { + key: 'qt_end_time', + label: '마감일', + width: 'w-[13.72%]', + align: 'text-center', + render: (item) => formatDateTime(item.qt_end_time), + }, +] diff --git a/front/src/features/list/containers/ContentContainer.tsx b/front/src/features/list/containers/ContentContainer.tsx new file mode 100644 index 0000000..eb89662 --- /dev/null +++ b/front/src/features/list/containers/ContentContainer.tsx @@ -0,0 +1,27 @@ +import { useList } from '@/features/list/hooks/useList' +import { ActionSection } from '@/features/list/components/ActionSection' +import { TableSection } from '@/features/list/components/TableSection' +import { Pagination } from '@/features/list/components/Pagination' + +export function ContentContainer() { + const { items, isLoading, totalPages, currentPage, setCurrentPage, selectedId, handleItemClick } = + useList() + + return ( + // 좌우 거터(80px) 일괄 적용 +
+ + + +
+ ) +} diff --git a/front/src/features/list/containers/FilterContainer.tsx b/front/src/features/list/containers/FilterContainer.tsx new file mode 100644 index 0000000..b2bc295 --- /dev/null +++ b/front/src/features/list/containers/FilterContainer.tsx @@ -0,0 +1,52 @@ +import { useShallow } from 'zustand/react/shallow' +import { FilterGroup } from '@/features/list/components/FilterGroup' +import { FILTER_GROUPS, type FilterKey } from '@/features/list/filterOptions' +import { useListStore } from '@/features/list/stores/useListStore' + +export function FilterContainer() { + // 필터 필드만 구독 → 페이지 변경 시 리렌더 방지 + const { + selectedType, + selectedStatus, + selectedDeadline, + toggleType, + toggleStatus, + toggleDeadline, + } = useListStore( + useShallow((s) => ({ + selectedType: s.selectedType, + selectedStatus: s.selectedStatus, + selectedDeadline: s.selectedDeadline, + toggleType: s.toggleType, + toggleStatus: s.toggleStatus, + toggleDeadline: s.toggleDeadline, + })), + ) + + const selected: Record = { + type: selectedType, + status: selectedStatus, + deadline: selectedDeadline, + } + const toggle: Record void> = { + type: toggleType, + status: toggleStatus, + deadline: toggleDeadline, + } + + return ( +
+
+ {FILTER_GROUPS.map((group) => ( + + ))} +
+
+ ) +} diff --git a/front/src/features/list/containers/FilterSection.tsx b/front/src/features/list/containers/FilterSection.tsx deleted file mode 100644 index 13fd808..0000000 --- a/front/src/features/list/containers/FilterSection.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { ListFilter } from '@/features/list/components/ListFilter' -import { FILTER_GROUPS, type FilterKey } from '@/features/list/model/filterOptions' -import { useListFilterStore } from '@/features/list/model/useListFilterStore' - -export function FilterSection() { - const store = useListFilterStore() - - // 필터 키 → store 상태/핸들러 매핑 - const selected: Record = { - type: store.selectedType, - status: store.selectedStatus, - deadline: store.selectedDeadline, - } - const toggle: Record void> = { - type: store.toggleType, - status: store.toggleStatus, - deadline: store.toggleDeadline, - } - - return ( -
-
- {FILTER_GROUPS.map((group) => ( - - ))} -
-
- ) -} diff --git a/front/src/features/list/model/filterOptions.ts b/front/src/features/list/filterOptions.ts similarity index 81% rename from front/src/features/list/model/filterOptions.ts rename to front/src/features/list/filterOptions.ts index 7cdf965..bf8f38c 100644 --- a/front/src/features/list/model/filterOptions.ts +++ b/front/src/features/list/filterOptions.ts @@ -1,12 +1,12 @@ export type FilterKey = 'type' | 'status' | 'deadline' -export interface FilterGroup { +export interface FilterGroupConfig { key: FilterKey title: string items: readonly string[] } -export const FILTER_GROUPS: readonly FilterGroup[] = [ +export const FILTER_GROUPS: readonly FilterGroupConfig[] = [ { key: 'type', title: '구분', items: ['재견적', '재협상'] }, { key: 'status', diff --git a/front/src/features/list/hooks/useList.ts b/front/src/features/list/hooks/useList.ts new file mode 100644 index 0000000..dc16832 --- /dev/null +++ b/front/src/features/list/hooks/useList.ts @@ -0,0 +1,57 @@ +import { useMemo, useState } from 'react' +import { useListStore } from '@/features/list/stores/useListStore' +import { MOCK_ITEMS } from '@/features/list/mocks/mockItems' +import type { ListItem } from '@/features/list/types' + +const PAGE_SIZE = 20 + +// 목데이터 클라이언트 필터 (추후 API 조회로 교체) +export function useList() { + const { selectedType, selectedStatus, selectedDeadline, currentPage, setCurrentPage } = + useListStore() + const [selectedId, setSelectedId] = useState(null) + + const filtered = useMemo(() => { + const result = MOCK_ITEMS.filter( + (item) => + (!selectedType || item.qt_type === selectedType) && + (!selectedStatus || item.session_status === selectedStatus), + ) + + if (selectedDeadline) { + const dir = selectedDeadline === '남은 시간 적은 순' ? 1 : -1 + result.sort( + (a, b) => + (new Date(a.qt_end_time).getTime() - new Date(b.qt_end_time).getTime()) * dir, + ) + } + + return result + }, [selectedType, selectedStatus, selectedDeadline]) + + const totalPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE)) + + const items = useMemo( + () => filtered.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE), + [filtered, currentPage], + ) + + const selectedItem = useMemo( + () => items.find((item) => item.session_id === selectedId) ?? null, + [items, selectedId], + ) + + const handleItemClick = (item: ListItem) => + setSelectedId((prev) => (prev === item.session_id ? null : item.session_id)) + + return { + items, + isLoading: false, + totalPages, + currentPage, + setCurrentPage, + selectedId, + selectedItem, + handleItemClick, + } +} diff --git a/front/src/features/list/index.ts b/front/src/features/list/index.ts index 43ad6cc..44120b7 100644 --- a/front/src/features/list/index.ts +++ b/front/src/features/list/index.ts @@ -1,4 +1,3 @@ -// 공개 API: 페이지에서 쓰는 것만 노출 (ListFilter/FILTER_GROUPS 등은 feature 내부 구현) -export { FilterSection } from '@/features/list/containers/FilterSection' -export { SidebarFooter } from '@/features/list/components/SidebarFooter' -export { useListFilterStore } from '@/features/list/model/useListFilterStore' +// 공개 API (페이지에서 쓰는 것만) +export { FilterContainer } from '@/features/list/containers/FilterContainer' +export { ContentContainer } from '@/features/list/containers/ContentContainer' diff --git a/front/src/features/list/lib/datetime.ts b/front/src/features/list/lib/datetime.ts new file mode 100644 index 0000000..80da812 --- /dev/null +++ b/front/src/features/list/lib/datetime.ts @@ -0,0 +1,8 @@ +// 'YYYY-MM-DD HH:mm'. 빈 값 '-', 파싱 실패 시 원본. +export function formatDateTime(value: string): string { + if (!value) return '-' + const d = new Date(value) + if (Number.isNaN(d.getTime())) return value + const pad = (n: number) => String(n).padStart(2, '0') + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}` +} diff --git a/front/src/features/list/lib/pagination.ts b/front/src/features/list/lib/pagination.ts new file mode 100644 index 0000000..e477056 --- /dev/null +++ b/front/src/features/list/lib/pagination.ts @@ -0,0 +1,18 @@ +export type PageItem = number | 'dots' + +// 9개 이하: 전체 / 초과: 앞4·뒤4 또는 1·중앙5·끝 + 생략(...) +export function buildPageItems(currentPage: number, totalPages: number): PageItem[] { + const pages = Array.from({ length: totalPages }, (_, i) => i + 1) + if (pages.length <= 9) return pages + + const prev = pages.slice(0, 4) + const middle = pages.slice(3, pages.length - 3) + const next = pages.slice(pages.length - 4) + + if (!middle.includes(currentPage)) { + return [...prev, 'dots', ...next] + } + + const center = [currentPage - 2, currentPage - 1, currentPage, currentPage + 1, currentPage + 2] + return [1, 'dots', ...center, 'dots', totalPages] +} diff --git a/front/src/features/list/mocks/mockItems.ts b/front/src/features/list/mocks/mockItems.ts new file mode 100644 index 0000000..ece904b --- /dev/null +++ b/front/src/features/list/mocks/mockItems.ts @@ -0,0 +1,115 @@ +import type { ListItem } from '@/features/list/types' + +// 임시 목데이터 (API 연동 전) +export const MOCK_ITEMS: ListItem[] = [ + { + session_id: 's-001', + session_status: '협상생성', + qt_type: '재견적', + qt_number: 'QT-2026-000101', + qt_end_time: '2026-06-18T18:00:00', + item_code: 'IMK-10231', + item_name: '사무용 노트북 14인치', + model_name: 'NB-1400-PRO', + maker_name: '삼성전자', + }, + { + session_id: 's-002', + session_status: '협상중', + qt_type: '재협상', + qt_number: 'QT-2026-000102', + qt_end_time: '2026-06-17T12:30:00', + item_code: 'IMK-10232', + item_name: '레이저 복합기', + model_name: 'MFC-7890DW', + maker_name: '브라더', + }, + { + session_id: 's-003', + session_status: '협상완료', + qt_type: '재견적', + qt_number: 'QT-2026-000103', + qt_end_time: '2026-06-20T09:00:00', + item_code: 'IMK-10233', + item_name: '27인치 4K 모니터', + model_name: 'U2723QE', + maker_name: '델', + }, + { + session_id: 's-004', + session_status: '협상거부', + qt_type: '재협상', + qt_number: 'QT-2026-000104', + qt_end_time: '2026-06-19T15:45:00', + item_code: 'IMK-10234', + item_name: '무선 기계식 키보드', + model_name: 'MX-KEYS-M', + maker_name: '로지텍', + }, + { + session_id: 's-005', + session_status: '미참여', + qt_type: '재견적', + qt_number: 'QT-2026-000105', + qt_end_time: '2026-06-22T11:00:00', + item_code: 'IMK-10235', + item_name: 'A4 무선 레이저프린터', + model_name: 'SL-M2030', + maker_name: 'HP', + }, + { + session_id: 's-006', + session_status: '협상중', + qt_type: '재견적', + qt_number: 'QT-2026-000106', + qt_end_time: '2026-06-16T20:00:00', + item_code: 'IMK-10236', + item_name: '회의실 대형 디스플레이 65인치', + model_name: 'QM65R', + maker_name: '삼성전자', + }, + { + session_id: 's-007', + session_status: '협상생성', + qt_type: '재협상', + qt_number: 'QT-2026-000107', + qt_end_time: '2026-06-25T17:00:00', + item_code: 'IMK-10237', + item_name: '인체공학 사무용 의자', + model_name: 'ERGO-700', + maker_name: '시디즈', + }, + { + session_id: 's-008', + session_status: '협상완료', + qt_type: '재견적', + qt_number: 'QT-2026-000108', + qt_end_time: '2026-06-21T10:30:00', + item_code: 'IMK-10238', + item_name: '네트워크 스위치 24포트', + model_name: 'SG350-28', + maker_name: '시스코', + }, + { + session_id: 's-009', + session_status: '미참여', + qt_type: '재협상', + qt_number: 'QT-2026-000109', + qt_end_time: '2026-06-23T14:00:00', + item_code: 'IMK-10239', + item_name: '외장 SSD 2TB', + model_name: 'T7-Shield-2T', + maker_name: '삼성전자', + }, + { + session_id: 's-010', + session_status: '협상중', + qt_type: '재견적', + qt_number: 'QT-2026-000110', + qt_end_time: '2026-06-24T16:20:00', + item_code: 'IMK-10240', + item_name: '화상회의용 웹캠', + model_name: 'BRIO-4K', + maker_name: '로지텍', + }, +] diff --git a/front/src/features/list/model/useListFilterStore.ts b/front/src/features/list/stores/useListStore.ts similarity index 87% rename from front/src/features/list/model/useListFilterStore.ts rename to front/src/features/list/stores/useListStore.ts index 27e892b..4be6085 100644 --- a/front/src/features/list/model/useListFilterStore.ts +++ b/front/src/features/list/stores/useListStore.ts @@ -1,6 +1,6 @@ import { create } from 'zustand' -interface ListFilterState { +interface ListState { selectedType: string | null selectedStatus: string | null selectedDeadline: string | null @@ -13,10 +13,9 @@ interface ListFilterState { resetFilters: () => void } -// 같은 항목을 다시 누르면 해제 const toggle = (current: string | null, item: string) => (current === item ? null : item) -export const useListFilterStore = create((set) => ({ +export const useListStore = create((set) => ({ selectedType: null, selectedStatus: null, selectedDeadline: null, diff --git a/front/src/features/list/types.ts b/front/src/features/list/types.ts new file mode 100644 index 0000000..0c97875 --- /dev/null +++ b/front/src/features/list/types.ts @@ -0,0 +1,11 @@ +export type ListItem = { + session_id: string + session_status: string + qt_type: string + qt_number: string + qt_end_time: string + item_code: string + item_name: string + model_name: string + maker_name: string +} From a0f7a229fa48479f9a8d9c5c7238e58d727101a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=ED=97=8C?= Date: Wed, 17 Jun 2026 10:48:38 +0900 Subject: [PATCH 18/21] =?UTF-8?q?style(front):=20=EB=94=94=EC=9E=90?= =?UTF-8?q?=EC=9D=B8=20=ED=86=A0=ED=81=B0=20=EC=A0=95=EB=B9=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 브랜드 스케일 채도 약 20%↓ (색상·명도 유지) - surface(콘텐츠 면 쿨 그레이)·table-header 토큰 추가 - 배너 주석 제거 및 최소화 Co-Authored-By: Claude Opus 4.8 (1M context) --- front/src/index.css | 111 +++++++++++++++++++++++++++----------------- 1 file changed, 69 insertions(+), 42 deletions(-) diff --git a/front/src/index.css b/front/src/index.css index d935dd2..445ac8a 100644 --- a/front/src/index.css +++ b/front/src/index.css @@ -1,63 +1,93 @@ -/* Pretendard (CDN, 버전 고정 + dynamic-subset: 사용 글리프만 로드) */ +/* Pretendard (CDN, dynamic-subset) */ @import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.css'); @import "tailwindcss"; -/* ============================================================ - 디자인 토큰 (출처: design.md) - - 이 프로젝트는 다크모드 없음 → light 값만 사용 (.dark / @custom-variant 미사용) - - 차트(--chart-*) / 사이드바(--sidebar-*) 미사용 → 정의하지 않음 - ============================================================ */ +/* 디자인 토큰 (light 전용) */ :root { color-scheme: light; - --radius: 0.625rem; /* 10px (기준값) */ + --radius: 0.625rem; /* 10px 기준 */ - /* --- 브랜드 메인 컬러 (blue, 숫자 스케일) --- */ - --brand-500: #3b7de0; /* light : 강조/포커스 등 밝은 변형 */ - --brand-600: #0046b0; /* base : 기본 메인 컬러 (= --primary) */ - --brand-700: #003380; /* deep : hover/pressed 등 톤다운 상태 */ + /* 브랜드 */ + --brand-500: #4e81cd; + --brand-600: #124a9e; /* = --primary */ + --brand-700: #0d3673; - /* --- 색상 --- */ - --background: #ffffff; - --background-rgb: 250, 250, 250; /* rgba() 조합용 raw 값 */ - --foreground: #0a0a0a; + /* Neutral */ + --neutral-90: #151515; + --neutral-80: #303030; + --neutral-70: #606060; + --neutral-60: #808080; + --neutral-40: #dadbde; + --neutral-30: #eaebef; + --neutral-20: #f0f1f4; + --neutral-10: #f7f8fa; + --neutral-00: #ffffff; - --card: #ffffff; - --card-foreground: #0a0a0a; - --popover: #ffffff; - --popover-foreground: #0a0a0a; + /* 상태 / 테이블 */ + --negative: #e71c3b; + --info: #4880ef; + --table-header: #e3e8f1; + --table-hover: #dbe6fc; + --table-selected: #b6ccf9; - --primary: var(--brand-600); /* 메인 컬러 = 브랜드 base(600) */ - --primary-foreground: #fafafa; /* primary 위 텍스트(흰색) */ - --secondary: #f5f5f5; - --secondary-foreground: #171717; - --muted: #f5f5f5; - --muted-foreground: #737373; - --accent: #f5f5f5; - --accent-foreground: #171717; + /* 시맨틱 */ + --background: var(--neutral-00); + --foreground: var(--neutral-90); + --surface: #eceff5; /* 브랜드 틴트 쿨 그레이 (콘텐츠 면) */ - --border: #e5e5e5; - --input: #e5e5e5; - --ring: var(--brand-500); /* 포커스 링 = 브랜드 light(500) */ + --card: var(--neutral-00); + --card-foreground: var(--neutral-90); + --popover: var(--neutral-00); + --popover-foreground: var(--neutral-90); - --destructive: #e7000b; + --primary: var(--brand-600); + --primary-foreground: var(--neutral-00); + --secondary: var(--neutral-20); + --secondary-foreground: var(--neutral-90); + --muted: var(--neutral-10); + --muted-foreground: var(--neutral-60); + --accent: var(--neutral-20); + --accent-foreground: var(--neutral-90); + + --border: var(--neutral-30); + --input: var(--neutral-30); + --ring: var(--brand-500); + + --destructive: var(--negative); --success: #10b981; --warning: #f59e0b; } -/* ============================================================ - Tailwind 테마 매핑 (@theme inline) - 토큰을 유틸로 노출: bg-*, text-*, border-*, rounded-*, font-* 등 - ============================================================ */ +/* Tailwind 테마 매핑 (@theme inline) */ @theme inline { - /* 브랜드 메인 컬러 (bg-brand-600 / text-brand-700 / hover:bg-brand-700 ...) */ + /* 브랜드 */ --color-brand-500: var(--brand-500); --color-brand-600: var(--brand-600); --color-brand-700: var(--brand-700); - /* 색상 */ + /* Neutral */ + --color-neutral-90: var(--neutral-90); + --color-neutral-80: var(--neutral-80); + --color-neutral-70: var(--neutral-70); + --color-neutral-60: var(--neutral-60); + --color-neutral-40: var(--neutral-40); + --color-neutral-30: var(--neutral-30); + --color-neutral-20: var(--neutral-20); + --color-neutral-10: var(--neutral-10); + --color-neutral-00: var(--neutral-00); + + /* 상태 / 테이블 */ + --color-negative: var(--negative); + --color-info: var(--info); + --color-table-header: var(--table-header); + --color-table-hover: var(--table-hover); + --color-table-selected: var(--table-selected); + + /* 시맨틱 */ --color-background: var(--background); --color-foreground: var(--foreground); + --color-surface: var(--surface); --color-card: var(--card); --color-card-foreground: var(--card-foreground); --color-popover: var(--popover); @@ -82,7 +112,7 @@ --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace; --font-heading: var(--font-sans); - /* 모서리 반경 (--radius 의 배수) */ + /* 모서리 반경 (--radius 배수) */ --radius-sm: calc(var(--radius) * 0.6); /* 6px */ --radius-md: calc(var(--radius) * 0.8); /* 8px */ --radius-lg: var(--radius); /* 10px */ @@ -92,9 +122,6 @@ --radius-4xl: calc(var(--radius) * 2.6); /* 26px */ } -/* ============================================================ - 기본 스타일 - ============================================================ */ @layer base { body { margin: 0; @@ -109,7 +136,7 @@ } } -/* 스크롤바 (webkit) — 6px, thumb=border / hover=muted-foreground */ +/* 스크롤바 (webkit) */ ::-webkit-scrollbar { width: 6px; height: 6px; From 4d1c6f97c06901ad438914db8f0564bc1776f8d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=ED=97=8C?= Date: Wed, 17 Jun 2026 10:49:07 +0900 Subject: [PATCH 19/21] =?UTF-8?q?refactor(front):=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80/=EC=97=94=ED=8A=B8=EB=A6=AC=20import=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EA=B0=B1=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SidebarFooter(auth)·Provider 이동에 따른 import 경로 반영 - 불필요한 주석 정리 Co-Authored-By: Claude Opus 4.8 (1M context) --- front/src/main.tsx | 2 +- front/src/pages/ChatPage.tsx | 3 ++- front/src/pages/ListPage.tsx | 8 ++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/front/src/main.tsx b/front/src/main.tsx index b0f2ed4..af7833d 100644 --- a/front/src/main.tsx +++ b/front/src/main.tsx @@ -1,6 +1,6 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' -import { Provider } from '@/core/provider' +import { Provider } from '@/core/Provider' import '@/index.css' import App from '@/App' diff --git a/front/src/pages/ChatPage.tsx b/front/src/pages/ChatPage.tsx index 0d7cd19..71edb00 100644 --- a/front/src/pages/ChatPage.tsx +++ b/front/src/pages/ChatPage.tsx @@ -1,5 +1,6 @@ import { useNavigate } from 'react-router' import { List } from 'lucide-react' +import { cn, interactive } from '@/lib' import { MainLayout, MainHeaderBar } from '@/layouts' export function ChatPage() { @@ -27,7 +28,7 @@ function ListNavButton() { type="button" aria-label="목록으로 이동" onClick={() => navigate('/list')} - className="cursor-pointer text-foreground" + className={cn('text-foreground', interactive)} > diff --git a/front/src/pages/ListPage.tsx b/front/src/pages/ListPage.tsx index 9d49500..b6927a5 100644 --- a/front/src/pages/ListPage.tsx +++ b/front/src/pages/ListPage.tsx @@ -1,5 +1,6 @@ import { MainLayout, MainHeaderBar } from '@/layouts' -import { FilterSection, SidebarFooter } from '@/features/list' +import { FilterContainer, ContentContainer } from '@/features/list' +import { SidebarFooter } from '@/features/auth' export function ListPage() { return ( @@ -12,7 +13,7 @@ export function ListPage() { } > - {/* TODO: Content (ButtonSection / TableSection / PageSection) */} + ) } @@ -20,8 +21,7 @@ export function ListPage() { function Sidebar() { return ( <> - - {/* TODO: ArchiveSection */} + ) From 54b44c087780e302f59470ee0ca5c9824869b18c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=ED=97=8C?= Date: Wed, 17 Jun 2026 14:37:48 +0900 Subject: [PATCH 20/21] =?UTF-8?q?style(front):=20=EB=94=94=EC=9E=90?= =?UTF-8?q?=EC=9D=B8=20=ED=86=A0=ED=81=B0=20=EC=A0=95=EB=B9=84=20(?= =?UTF-8?q?=EC=B1=84=ED=8C=85=20=ED=83=80=EC=9D=B4=ED=8F=AC=20+=20?= =?UTF-8?q?=EB=B8=8C=EB=9E=9C=EB=93=9C=20=EC=B1=84=EB=8F=84=20=EC=9D=B8?= =?UTF-8?q?=ED=95=98)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 타이포그래피 스케일 추가 (headline/title/body/reject 등) — 채팅에서 사용 - 채팅 영역 스크롤바 스타일(.chat-scroll) 추가 - 브랜드 스케일 채도 추가 인하, surface(콘텐츠 면) 톤 조정 Co-Authored-By: Claude Opus 4.8 (1M context) --- front/src/index.css | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/front/src/index.css b/front/src/index.css index 445ac8a..ae917ee 100644 --- a/front/src/index.css +++ b/front/src/index.css @@ -9,9 +9,9 @@ --radius: 0.625rem; /* 10px 기준 */ /* 브랜드 */ - --brand-500: #4e81cd; - --brand-600: #124a9e; /* = --primary */ - --brand-700: #0d3673; + --brand-500: #5a83c1; + --brand-600: #254d8b; /* = --primary */ + --brand-700: #1b3965; /* Neutral */ --neutral-90: #151515; @@ -34,7 +34,7 @@ /* 시맨틱 */ --background: var(--neutral-00); --foreground: var(--neutral-90); - --surface: #eceff5; /* 브랜드 틴트 쿨 그레이 (콘텐츠 면) */ + --surface: #eef0f4; /* 중립 라이트 그레이 (콘텐츠 면, 블루기 최소) */ --card: var(--neutral-00); --card-foreground: var(--neutral-90); @@ -136,6 +136,29 @@ } } +/* 타이포그래피 스케일 (headline / title / body / reject) */ +@layer components { + .headline-3 { font-size: 24px; font-weight: 700; line-height: 30px; letter-spacing: -0.48px; } + .headline-4 { font-size: 20px; font-weight: 700; line-height: 30px; letter-spacing: -0.4px; } + .title-1 { font-size: 20px; font-weight: 600; letter-spacing: -0.4px; } + .title-2 { font-size: 18px; font-weight: 600; letter-spacing: -0.36px; } + .title-3 { font-size: 16px; font-weight: 600; letter-spacing: -0.32px; } + .title-5 { font-size: 14px; font-weight: 600; letter-spacing: -0.28px; } + .title-6 { font-size: 13px; font-weight: 600; letter-spacing: -0.26px; } + .body-1 { font-size: 20px; font-weight: 400; line-height: 30px; letter-spacing: -0.4px; } + .body-3 { font-size: 16px; font-weight: 400; letter-spacing: -0.32px; } + .body-5 { font-size: 14px; font-weight: 400; letter-spacing: -0.28px; } + .body-1-read-r { font-size: 20px; font-weight: 400; line-height: 150%; letter-spacing: -0.4px; } + .body-1-read-b { font-size: 20px; font-weight: 700; line-height: 150%; letter-spacing: -0.4px; } + .caption-1 { font-size: 13px; font-weight: 400; letter-spacing: -0.26px; } + /* 메뉴 섹션 제목 (MD안내/협상절차/이용방법) */ + .menu-title { font-size: 20px; font-weight: 700; line-height: 25px; letter-spacing: -0.4px; } + /* 재견적/재협상 폼 */ + .reject { font-size: 18px; font-weight: 400; line-height: 150%; letter-spacing: -0.4px; color: var(--neutral-90); } + .reject-2 { font-size: 18px; font-weight: 700; line-height: 150%; letter-spacing: -0.4px; color: var(--neutral-90); } + .reject-gray { font-size: 18px; font-weight: 400; line-height: 150%; letter-spacing: -0.4px; color: var(--neutral-60); } +} + /* 스크롤바 (webkit) */ ::-webkit-scrollbar { width: 6px; @@ -148,3 +171,11 @@ ::-webkit-scrollbar-thumb:hover { background-color: var(--muted-foreground); } + +/* 채팅 영역 스크롤바 (배경과 구분되게 진하게) */ +.chat-scroll::-webkit-scrollbar-thumb { + background-color: var(--neutral-40); +} +.chat-scroll::-webkit-scrollbar-thumb:hover { + background-color: var(--neutral-60); +} From 147f9dd0071d544d14dda48bbea8e87b4e67b739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=ED=97=8C?= Date: Wed, 17 Jun 2026 14:38:04 +0900 Subject: [PATCH 21/21] =?UTF-8?q?feat(front):=20=ED=98=91=EC=83=81=20?= =?UTF-8?q?=EC=B1=84=ED=8C=85=20=ED=8E=98=EC=9D=B4=EC=A7=80=20(mock=20UI)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - KT-NEGOWIZ /chat 을 현재 아키텍처(Vite·React Router·feature 구조)로 이식 - 사이드 ItemSection(+이미지 확대), 헤더 잔여시간, 채팅(메시지/템플릿/입력), 메뉴 섹션 - 메시지 템플릿: 협상 성공률·협상요약·투찰요약·재협상/재견적 폼 - zustand 스토어 + mock 데이터(정적 쇼케이스, 제출 시 로컬 append) - Next.js·KT 의존 제거(next/image·navigation, 로고·서비스명·연락처, 팝업 보류) - ChatPage 배선: ChatContainer / ItemSection / RemainingTime + auth SidebarFooter 재사용 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../features/chat/components/ChatMessage.tsx | 115 +++++++++++++ .../features/chat/components/ChatSection.tsx | 13 ++ .../features/chat/components/ItemImage.tsx | 74 +++++++++ .../features/chat/components/ItemSection.tsx | 128 ++++++++++++++ .../chat/components/RemainingTime.tsx | 18 ++ .../features/chat/components/UserButton.tsx | 99 +++++++++++ .../features/chat/components/menu/Contact.tsx | 22 +++ .../features/chat/components/menu/Guide.tsx | 18 ++ .../chat/components/menu/MDInformation.tsx | 84 ++++++++++ .../chat/components/menu/MenuSection.tsx | 21 +++ .../chat/components/menu/NegoStep.tsx | 52 ++++++ .../chat/components/templates/BidSummary.tsx | 61 +++++++ .../chat/components/templates/Indicator.tsx | 78 +++++++++ .../chat/components/templates/OtherReason.tsx | 84 ++++++++++ .../chat/components/templates/RejectCM.tsx | 115 +++++++++++++ .../chat/components/templates/RejectRSP.tsx | 155 +++++++++++++++++ .../chat/components/templates/Summary.tsx | 91 ++++++++++ .../components/templates/rejectControls.tsx | 79 +++++++++ .../features/chat/components/userInputs.tsx | 157 ++++++++++++++++++ .../chat/containers/ChatContainer.tsx | 14 ++ front/src/features/chat/hooks/useChatInit.ts | 16 ++ front/src/features/chat/index.ts | 4 + front/src/features/chat/lib/koreanNumber.ts | 45 +++++ front/src/features/chat/lib/rejectForm.ts | 24 +++ front/src/features/chat/lib/remainingTime.ts | 15 ++ .../src/features/chat/lib/userButtonConfig.ts | 25 +++ front/src/features/chat/mocks/mockChatInit.ts | 25 +++ front/src/features/chat/mocks/mockMessages.ts | 106 ++++++++++++ .../features/chat/stores/useChatInitStore.ts | 32 ++++ .../src/features/chat/stores/useChatStore.ts | 63 +++++++ front/src/features/chat/types.ts | 82 +++++++++ front/src/pages/ChatPage.tsx | 11 +- 32 files changed, 1921 insertions(+), 5 deletions(-) create mode 100644 front/src/features/chat/components/ChatMessage.tsx create mode 100644 front/src/features/chat/components/ChatSection.tsx create mode 100644 front/src/features/chat/components/ItemImage.tsx create mode 100644 front/src/features/chat/components/ItemSection.tsx create mode 100644 front/src/features/chat/components/RemainingTime.tsx create mode 100644 front/src/features/chat/components/UserButton.tsx create mode 100644 front/src/features/chat/components/menu/Contact.tsx create mode 100644 front/src/features/chat/components/menu/Guide.tsx create mode 100644 front/src/features/chat/components/menu/MDInformation.tsx create mode 100644 front/src/features/chat/components/menu/MenuSection.tsx create mode 100644 front/src/features/chat/components/menu/NegoStep.tsx create mode 100644 front/src/features/chat/components/templates/BidSummary.tsx create mode 100644 front/src/features/chat/components/templates/Indicator.tsx create mode 100644 front/src/features/chat/components/templates/OtherReason.tsx create mode 100644 front/src/features/chat/components/templates/RejectCM.tsx create mode 100644 front/src/features/chat/components/templates/RejectRSP.tsx create mode 100644 front/src/features/chat/components/templates/Summary.tsx create mode 100644 front/src/features/chat/components/templates/rejectControls.tsx create mode 100644 front/src/features/chat/components/userInputs.tsx create mode 100644 front/src/features/chat/containers/ChatContainer.tsx create mode 100644 front/src/features/chat/hooks/useChatInit.ts create mode 100644 front/src/features/chat/index.ts create mode 100644 front/src/features/chat/lib/koreanNumber.ts create mode 100644 front/src/features/chat/lib/rejectForm.ts create mode 100644 front/src/features/chat/lib/remainingTime.ts create mode 100644 front/src/features/chat/lib/userButtonConfig.ts create mode 100644 front/src/features/chat/mocks/mockChatInit.ts create mode 100644 front/src/features/chat/mocks/mockMessages.ts create mode 100644 front/src/features/chat/stores/useChatInitStore.ts create mode 100644 front/src/features/chat/stores/useChatStore.ts create mode 100644 front/src/features/chat/types.ts diff --git a/front/src/features/chat/components/ChatMessage.tsx b/front/src/features/chat/components/ChatMessage.tsx new file mode 100644 index 0000000..b4cea65 --- /dev/null +++ b/front/src/features/chat/components/ChatMessage.tsx @@ -0,0 +1,115 @@ +import { useEffect, useRef, memo } from 'react' +import { cn } from '@/lib' +import { useChatStore } from '@/features/chat/stores/useChatStore' +import type { ChatMessage as ChatMessageType } from '@/features/chat/types' +import { Indicator } from '@/features/chat/components/templates/Indicator' +import { Summary } from '@/features/chat/components/templates/Summary' +import { BidSummary } from '@/features/chat/components/templates/BidSummary' +import { RejectRSP } from '@/features/chat/components/templates/RejectRSP' +import { RejectCM } from '@/features/chat/components/templates/RejectCM' + +export function ChatMessage() { + return ( +
+
+ +
+
+ ) +} + +function ChatList() { + const bottomRef = useRef(null) + const chats = useChatStore((s) => s.messages) + + useEffect(() => { + bottomRef.current?.scrollIntoView({ behavior: 'smooth' }) + }, [chats]) + + if (!chats || chats.length === 0) { + return ( +
+

채팅 내역이 없습니다.

+
+ ) + } + + return ( +
+ {chats.map((message, index) => ( + + ))} +
+
+ ) +} + +const MessageItem = memo(function MessageItem({ + message, + isFirst, + messages, + currentIndex, +}: { + message: ChatMessageType + isFirst: boolean + messages: ChatMessageType[] + currentIndex: number +}) { + const isBot = message.sender === 'bot' + + // 직전이 reject 폼이면 사용자 답변은 숨기고 구분선만 표시 + if (!isBot && currentIndex > 0) { + const prev = messages[currentIndex - 1] + if (prev?.bot_chat_type === 'rejectRSP' || prev?.bot_chat_type === 'rejectCM') { + return ( + <> +
+
+ + ) + } + } + + return
{isBot ? : }
+}) + +const BotMessage = memo(function BotMessage({ message, isFirst }: { message: ChatMessageType; isFirst?: boolean }) { + return ( +
+
+
{message.script || ''}
+
+ +
+ {message.bot_chat_type === 'indicator' && message.indicator_value != null && ( + + )} + {message.bot_chat_type === 'summaryRSP' && message.summary && } + {message.bot_chat_type === 'summaryCM' && message.summary && ( + + )} + {message.bot_chat_type === 'rejectRSP' && } + {message.bot_chat_type === 'rejectCM' && } +
+
+ ) +}) + +const UserMessage = memo(function UserMessage({ text }: { text: string }) { + return ( + <> +
+
+ {text} +
+
+
+ + ) +}) diff --git a/front/src/features/chat/components/ChatSection.tsx b/front/src/features/chat/components/ChatSection.tsx new file mode 100644 index 0000000..27d5e5a --- /dev/null +++ b/front/src/features/chat/components/ChatSection.tsx @@ -0,0 +1,13 @@ +import { useChatStore } from '@/features/chat/stores/useChatStore' +import { ChatMessage } from '@/features/chat/components/ChatMessage' +import { UserButton } from '@/features/chat/components/UserButton' + +export function ChatSection() { + const { userButtonConfig } = useChatStore() + return ( +
+ + +
+ ) +} diff --git a/front/src/features/chat/components/ItemImage.tsx b/front/src/features/chat/components/ItemImage.tsx new file mode 100644 index 0000000..ff88d06 --- /dev/null +++ b/front/src/features/chat/components/ItemImage.tsx @@ -0,0 +1,74 @@ +import { useState, useEffect } from 'react' +import { createPortal } from 'react-dom' +import { ImageIcon, X } from 'lucide-react' +import { useChatInitStore } from '@/features/chat/stores/useChatInitStore' + +export function ItemImage() { + const { item_image } = useChatInitStore() + const [isOpen, setIsOpen] = useState(false) + + return ( +
+ + {isOpen && setIsOpen(false)} />} +
+ ) +} + +function Thumb({ src }: { src: string }) { + if (src) { + return 상품 이미지 + } + return ( +
+ +
+ ) +} + +function ImageModal({ src, onClose }: { src: string; onClose: () => void }) { + useEffect(() => { + const onKey = (e: KeyboardEvent) => e.key === 'Escape' && onClose() + window.addEventListener('keydown', onKey) + return () => window.removeEventListener('keydown', onKey) + }, [onClose]) + + return createPortal( +
+
e.stopPropagation()}> + {src ? ( + 상품 이미지 + ) : ( +
+ +
+ )} + +
+
, + document.body, + ) +} diff --git a/front/src/features/chat/components/ItemSection.tsx b/front/src/features/chat/components/ItemSection.tsx new file mode 100644 index 0000000..d9744c8 --- /dev/null +++ b/front/src/features/chat/components/ItemSection.tsx @@ -0,0 +1,128 @@ +import { useState, useCallback, useRef, useEffect } from 'react' +import { createPortal } from 'react-dom' +import { cn } from '@/lib' +import { ItemImage } from '@/features/chat/components/ItemImage' +import { useChatInitStore } from '@/features/chat/stores/useChatInitStore' + +export function ItemSection() { + return ( +
+ + +
+ ) +} + +interface TooltipState { + text: string + x: number + y: number + above?: boolean +} + +function Tooltip({ text, x, y, above }: TooltipState) { + return createPortal( +
+
+ {text} +
+
, + document.body, + ) +} + +function ItemInfo() { + const { + item_code, + item_price, + item_vat_yn, + item_model_name, + item_maker_name, + item_min_order_quantity, + item_lead_time, + item_spec, + item_name, + } = useChatInitStore() + + const [tooltip, setTooltip] = useState(null) + const hideTimer = useRef | null>(null) + + const showTooltip = useCallback((e: React.MouseEvent, text: string, above = false) => { + if (hideTimer.current) clearTimeout(hideTimer.current) + if (above) { + const rect = e.currentTarget.getBoundingClientRect() + setTooltip({ text, x: rect.left, y: rect.top - 8, above }) + } else { + setTooltip({ text, x: e.clientX, y: e.clientY + 16, above }) + } + }, []) + + const hideTooltip = useCallback(() => { + hideTimer.current = setTimeout(() => setTooltip(null), 100) + }, []) + + useEffect(() => () => { + if (hideTimer.current) clearTimeout(hideTimer.current) + }, []) + + const formatPrice = (price: number) => price.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') + const isNewItem = !item_code && !item_price + const priceText = isNewItem ? '신규' : `${formatPrice(item_price)}원` + const formattedPrice = `${priceText}(${item_vat_yn || 'VAT별도'})` + + const renderRow = (title: string, data: string, important = false) => { + const textClass = important ? 'title-3 text-neutral-90' : 'body-3 text-neutral-70' + const displayData = data || '-' + return ( +
+
{title}
+
displayData !== '-' && showTooltip(e, displayData)} + onMouseLeave={hideTooltip} + > + {displayData} +
+
+ ) + } + + return ( +
+
item_name && showTooltip(e, item_name)} + onMouseLeave={hideTooltip} + > + {item_name} +
+ +
+
+ {renderRow('상품코드', item_code, true)} + {renderRow('단가', formattedPrice, true)} + {renderRow('모델명', item_model_name, true)} + {renderRow('제조사', item_maker_name)} + {renderRow('최소주문수량', item_min_order_quantity)} + {renderRow('리드타임', item_lead_time)} + +
+
규격
+
item_spec && showTooltip(e, item_spec, true)} + onMouseLeave={hideTooltip} + > + {item_spec || '-'} +
+
+
+
+ + {tooltip && } +
+ ) +} diff --git a/front/src/features/chat/components/RemainingTime.tsx b/front/src/features/chat/components/RemainingTime.tsx new file mode 100644 index 0000000..df6edfe --- /dev/null +++ b/front/src/features/chat/components/RemainingTime.tsx @@ -0,0 +1,18 @@ +import { useState, useEffect } from 'react' +import { getTimeRemaining } from '@/features/chat/lib/remainingTime' +import { useChatInitStore } from '@/features/chat/stores/useChatInitStore' + +// 헤더의 협상 잔여 시간. 1초마다 tick 을 올려 렌더 중 남은 시간을 다시 계산한다. +export function RemainingTime() { + const { quotation_end_time } = useChatInitStore() + const [, setTick] = useState(0) + + useEffect(() => { + const id = setInterval(() => setTick((t) => t + 1), 1000) + return () => clearInterval(id) + }, []) + + const remaining = quotation_end_time ? getTimeRemaining(quotation_end_time) : '-' + + return 협상 잔여 시간 : {remaining} +} diff --git a/front/src/features/chat/components/UserButton.tsx b/front/src/features/chat/components/UserButton.tsx new file mode 100644 index 0000000..064f75a --- /dev/null +++ b/front/src/features/chat/components/UserButton.tsx @@ -0,0 +1,99 @@ +import { useNavigate } from 'react-router' +import { List, Loader2 } from 'lucide-react' +import { useChatStore } from '@/features/chat/stores/useChatStore' +import { Percent, Price } from '@/features/chat/components/userInputs' +import { GO_TO_LIST_TEXT } from '@/features/chat/lib/userButtonConfig' +import type { UserButtonConfig } from '@/features/chat/types' + +const style = { + goToList: + 'flex w-[159px] h-[48px] bg-neutral-40 hover:brightness-[0.97] rounded-[999px] items-center justify-center title-2 text-neutral-80 cursor-pointer gap-2 transition-all ease-out hover:scale-[1.01]', + black: + 'flex min-w-[120px] px-[32px] h-[48px] bg-primary hover:brightness-[0.97] rounded-[999px] items-center justify-center title-2 text-primary-foreground cursor-pointer whitespace-nowrap transition-all duration-200 ease-out hover:scale-[1.01]', + gray: 'flex min-w-[120px] px-[32px] h-[48px] bg-neutral-60 rounded-[999px] items-center justify-center title-2 text-neutral-00 cursor-pointer whitespace-nowrap transition-all ease-out hover:scale-[1.01]', + white: + 'flex min-w-[120px] px-[32px] h-[48px] bg-neutral-00 rounded-[999px] items-center justify-center title-2 text-neutral-80 border border-neutral-80 cursor-pointer whitespace-nowrap transition-all ease-out hover:scale-[1.01]', +} + +export function UserButton({ type, text, textList, priceErrorMessage }: UserButtonConfig) { + if (type === '') return null + + return ( +
+
+
+
+ {type === 'one-black' && } + {type === 'one-gray' && } + {type === 'black-white' && } + {type === 'percent' && } + {type === 'three-black' && ( + + )} + {type === 'price' && } + {type === 'loading' && } +
+
+ +
+
+
+ ) +} + +function GoToList() { + const navigate = useNavigate() + return ( + + ) +} + +function OneBlack({ text }: { text: string }) { + const navigate = useNavigate() + const sendMessage = useChatStore((s) => s.sendMessage) + const onClick = () => (text === GO_TO_LIST_TEXT ? navigate('/list') : sendMessage(text)) + return ( + + ) +} + +function OneGray({ text }: { text: string }) { + const sendMessage = useChatStore((s) => s.sendMessage) + return ( + + ) +} + +function ThreeBlack({ textList }: { textList: [string, string, string] }) { + const sendMessage = useChatStore((s) => s.sendMessage) + return ( +
+ {textList.map((t) => ( + + ))} +
+ ) +} + +function BlackWhite({ textList }: { textList: [string, string] }) { + const sendMessage = useChatStore((s) => s.sendMessage) + return ( +
+ + +
+ ) +} diff --git a/front/src/features/chat/components/menu/Contact.tsx b/front/src/features/chat/components/menu/Contact.tsx new file mode 100644 index 0000000..7530870 --- /dev/null +++ b/front/src/features/chat/components/menu/Contact.tsx @@ -0,0 +1,22 @@ +import { interactive, cn } from '@/lib' + +// 헬프데스크 (TODO: 이용가이드 팝업 연동, 연락처는 추후 설정값으로 교체) +export function Contact() { + return ( +
+
+
헬프 데스크
+ +
+
-
+
-
+
+
+
+ ) +} diff --git a/front/src/features/chat/components/menu/Guide.tsx b/front/src/features/chat/components/menu/Guide.tsx new file mode 100644 index 0000000..7a0abcf --- /dev/null +++ b/front/src/features/chat/components/menu/Guide.tsx @@ -0,0 +1,18 @@ +import { ChevronRight } from 'lucide-react' +import { interactive, cn } from '@/lib' + +// 유의사항 및 이용방법 (TODO: 이용가이드 팝업 연동) +export function Guide() { + return ( + + ) +} diff --git a/front/src/features/chat/components/menu/MDInformation.tsx b/front/src/features/chat/components/menu/MDInformation.tsx new file mode 100644 index 0000000..61a7f30 --- /dev/null +++ b/front/src/features/chat/components/menu/MDInformation.tsx @@ -0,0 +1,84 @@ +import { useMemo } from 'react' +import { ChevronDown } from 'lucide-react' +import { cn } from '@/lib' +import { useChatInitStore } from '@/features/chat/stores/useChatInitStore' + +interface Props { + isOpen: boolean + setIsOpen: (isOpen: boolean) => void +} + +export function MDInformation({ isOpen, setIsOpen }: Props) { + const { quotation_memo } = useChatInitStore() + + const memoArray = useMemo(() => { + if (!quotation_memo?.trim()) return [] + return quotation_memo + .split(/\r?\n+/) + .map((s) => s.trim()) + .filter(Boolean) + }, [quotation_memo]) + + if (memoArray.length === 0) return null + + return ( +
+
+
+
+
MD 안내사항
+ +
+
+ +
+
+ {memoArray.map((item, i) => ( +
+ • +
{linkText(item)}
+
+ ))} +
+
+
+
+ ) +} + +function linkText(text: string) { + const urlRegex = /(https?:\/\/[^\s]+)/g + const isUrl = (s: string) => /^https?:\/\/[^\s]+$/.test(s) + const parts = text.split(urlRegex) + return ( +
+ {parts.map((part, i) => + isUrl(part) ? ( + + 상품 사이트로 이동 + + ) : ( + {i > 0 && isUrl(parts[i - 1]) ? part.replace(/^(\s)/, '') : part} + ), + )} +
+ ) +} diff --git a/front/src/features/chat/components/menu/MenuSection.tsx b/front/src/features/chat/components/menu/MenuSection.tsx new file mode 100644 index 0000000..75c5036 --- /dev/null +++ b/front/src/features/chat/components/menu/MenuSection.tsx @@ -0,0 +1,21 @@ +import { useState } from 'react' +import { MDInformation } from '@/features/chat/components/menu/MDInformation' +import { NegoStep } from '@/features/chat/components/menu/NegoStep' +import { Guide } from '@/features/chat/components/menu/Guide' +import { Contact } from '@/features/chat/components/menu/Contact' + +export function MenuSection() { + const [isMDOpen, setIsMDOpen] = useState(true) + const [isStepOpen, setIsStepOpen] = useState(true) + + return ( +
+
+ + + +
+ +
+ ) +} diff --git a/front/src/features/chat/components/menu/NegoStep.tsx b/front/src/features/chat/components/menu/NegoStep.tsx new file mode 100644 index 0000000..f9f8cce --- /dev/null +++ b/front/src/features/chat/components/menu/NegoStep.tsx @@ -0,0 +1,52 @@ +import { ChevronDown } from 'lucide-react' +import { cn } from '@/lib' +import { useChatStore } from '@/features/chat/stores/useChatStore' + +interface Props { + isOpen: boolean + setIsOpen: (isOpen: boolean) => void +} + +const STEPS = ['서비스안내', '담당자확인', '협상품목안내', '가격협상', '협상종료'] + +export function NegoStep({ isOpen, setIsOpen }: Props) { + const chats = useChatStore((s) => s.messages) + const currentStep = chats[chats.length - 1]?.display_step || '서비스안내' + + return ( +
+
+
+
+
협상절차
+ +
+
+ +
+
+ {STEPS.map((step, i) => ( +
+
+ {i + 1}. {step} +
+
+ ))} +
+
+
+
+ ) +} diff --git a/front/src/features/chat/components/templates/BidSummary.tsx b/front/src/features/chat/components/templates/BidSummary.tsx new file mode 100644 index 0000000..66b614d --- /dev/null +++ b/front/src/features/chat/components/templates/BidSummary.tsx @@ -0,0 +1,61 @@ +import { numberToKorean } from '@/features/chat/lib/koreanNumber' + +interface BidSummaryProps { + itemName: string + itemCode: string + bidPrice: number + deliveryType: string + isVAT: boolean +} + +// 투찰 결과 요약 카드 +export function BidSummary({ itemName, itemCode, bidPrice, deliveryType, isVAT }: BidSummaryProps) { + return ( +
+

투찰 결과 요약

+ + + + +
+ ) +} + +function BidItem({ title, value }: { title: string; value: string }) { + return ( +
+ +
+ {title} : +
 {value}
+
+
+ ) +} + +function BidPrice({ number, isVAT, title }: { number: number; isVAT: boolean; title: string }) { + const numberString = number.toLocaleString() + return ( +
+ +
+
{title} :
+
+  {numberString}원 + +  ({numberToKorean(parseInt(numberString.replace(/,/g, '')))}원) + +  {isVAT ? 'VAT포함' : 'VAT별도'} +
+
+
+ ) +} + +function Dot() { + return ( +
+

•

+
+ ) +} diff --git a/front/src/features/chat/components/templates/Indicator.tsx b/front/src/features/chat/components/templates/Indicator.tsx new file mode 100644 index 0000000..b62653e --- /dev/null +++ b/front/src/features/chat/components/templates/Indicator.tsx @@ -0,0 +1,78 @@ +import { cn } from '@/lib' + +// 협상 성공률 바 (0~100%). 구간별 색: 빨강(~30) / 주황(~70) / 초록(70~) +function selectBgColor(index: number, currentStep: number): string { + let fill = '#E71C3B' + if (currentStep > 3 && currentStep <= 7) fill = '#FDB21C' + if (currentStep > 7) fill = '#16BD14' + return index <= currentStep ? fill : '#ddd' +} + +function UnitBlock({ blockIndex, percent }: { blockIndex: number; percent: number }) { + const minBlockPercent = blockIndex * 10 + const maxBlockPercent = (blockIndex + 1) * 10 + const remindCount = percent - minBlockPercent + + let nodeType: 'fill' | 'empty' | 'half' = 'empty' + if (maxBlockPercent < percent) nodeType = 'fill' + else if (minBlockPercent <= percent) nodeType = 'half' + + return ( +
+
+ {Array.from({ length: 10 }).map((_, i) => { + let color = selectBgColor(blockIndex, Math.floor(percent / 10)) + if (nodeType === 'half' && i >= remindCount) color = '#ddd' + + const subBlockPercent = blockIndex * 10 + i + 1 + const isTarget = subBlockPercent === percent + const isLeftAlign = percent < 90 + + return ( +
+ {isTarget && ( +
+
+
+ + {percent}% + +
+
+ )} +
+ ) + })} +
+
+ ) +} + +export function Indicator({ number }: { number: number }) { + const percent = Math.min(Math.max(number, 0), 100) + + return ( +
+

협상 성공률

+
+ {percent === 0 ? ( +
+
▼ 0%
+
+ ) : ( +
+ )} +
+ {Array.from({ length: 10 }).map((_, i) => ( + + ))} +
+
+
+ ) +} diff --git a/front/src/features/chat/components/templates/OtherReason.tsx b/front/src/features/chat/components/templates/OtherReason.tsx new file mode 100644 index 0000000..a83152b --- /dev/null +++ b/front/src/features/chat/components/templates/OtherReason.tsx @@ -0,0 +1,84 @@ +import { useEffect, useRef } from 'react' +import { cn } from '@/lib' + +// RejectRSP 의 '기타' 사유: 라디오 + 자동 높이 textarea +export function OtherReason({ + inputValue, + setInputValue, + errorMessage, + selectedValue, + onChange, + isError, + disabled, +}: { + inputValue: string + setInputValue: (value: string) => void + errorMessage: string + selectedValue: string + onChange: (value: string) => void + isError: boolean + disabled?: boolean +}) { + const isChecked = selectedValue === '기타' + const ref = useRef(null) + + useEffect(() => { + const ta = ref.current + if (!ta) return + ta.style.height = 'auto' + const maxHeight = 20 * 3 + 16 + if (ta.scrollHeight > maxHeight) { + ta.style.height = `${maxHeight}px` + ta.style.overflowY = 'auto' + } else { + ta.style.height = `${ta.scrollHeight}px` + ta.style.overflowY = 'hidden' + } + }, [inputValue, isChecked]) + + const isTextareaDisabled = disabled || !isChecked + + return ( +
+
+ +