diff --git a/.gitignore b/.gitignore index 4d29575d..54f07af5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,24 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - +# 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? \ No newline at end of file diff --git a/README.md b/README.md index 58beeacc..40ede56e 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,54 @@ -# Getting Started with Create React App - -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). - -## Available Scripts - -In the project directory, you can run: - -### `npm start` - -Runs the app in the development mode.\ -Open [http://localhost:3000](http://localhost:3000) to view it in your browser. - -The page will reload when you make changes.\ -You may also see any lint errors in the console. - -### `npm test` - -Launches the test runner in the interactive watch mode.\ -See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. - -### `npm run build` - -Builds the app for production to the `build` folder.\ -It correctly bundles React in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes.\ -Your app is ready to be deployed! - -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. - -### `npm run eject` - -**Note: this is a one-way operation. Once you `eject`, you can't go back!** - -If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. - -Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. - -You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. - -## Learn More - -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). - -To learn React, check out the [React documentation](https://reactjs.org/). - -### Code Splitting - -This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) - -### Analyzing the Bundle Size - -This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) - -### Making a Progressive Web App - -This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) - -### Advanced Configuration - -This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) - -### Deployment - -This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) - -### `npm run build` fails to minify - -This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) +# 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/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## 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 tseslint.config({ + extends: [ + // 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, + ], + languageOptions: { + // other options... + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, +}) +``` + +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 tseslint.config({ + plugins: { + // Add the react-x and react-dom plugins + 'react-x': reactX, + 'react-dom': reactDom, + }, + rules: { + // other rules... + // Enable its recommended typescript rules + ...reactX.configs['recommended-typescript'].rules, + ...reactDom.configs.recommended.rules, + }, +}) +``` diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..092408a9 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,28 @@ +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' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +) diff --git a/index.html b/index.html new file mode 100644 index 00000000..ccd7bb90 --- /dev/null +++ b/index.html @@ -0,0 +1,19 @@ + + + + + + + + 판다마켓 + + +
+ + + diff --git a/package.json b/package.json index 7ff0d6b5..6b16fb73 100644 --- a/package.json +++ b/package.json @@ -1,38 +1,31 @@ { - "name": "1-weekly-mission", - "version": "0.1.0", + "name": "14-sprint-mission", "private": true, - "dependencies": { - "@testing-library/jest-dom": "^5.17.0", - "@testing-library/react": "^13.4.0", - "@testing-library/user-event": "^13.5.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-scripts": "5.0.1", - "web-vitals": "^2.1.4" - }, + "version": "0.0.0", + "type": "module", "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject" + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^7.5.0" }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] + "devDependencies": { + "@eslint/js": "^9.21.0", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react": "^4.3.4", + "eslint": "^9.21.0", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^15.15.0", + "typescript": "~5.7.2", + "typescript-eslint": "^8.24.1", + "vite": "^6.2.0", + "vite-tsconfig-paths": "^5.1.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000..1468e938 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2112 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + react: + specifier: ^19.0.0 + version: 19.1.0 + react-dom: + specifier: ^19.0.0 + version: 19.1.0(react@19.1.0) + react-router-dom: + specifier: ^7.5.0 + version: 7.5.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + devDependencies: + '@eslint/js': + specifier: ^9.21.0 + version: 9.24.0 + '@types/react': + specifier: ^19.0.10 + version: 19.1.0 + '@types/react-dom': + specifier: ^19.0.4 + version: 19.1.1(@types/react@19.1.0) + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.3.4(vite@6.2.5) + eslint: + specifier: ^9.21.0 + version: 9.24.0 + eslint-plugin-react-hooks: + specifier: ^5.1.0 + version: 5.2.0(eslint@9.24.0) + eslint-plugin-react-refresh: + specifier: ^0.4.19 + version: 0.4.19(eslint@9.24.0) + globals: + specifier: ^15.15.0 + version: 15.15.0 + typescript: + specifier: ~5.7.2 + version: 5.7.3 + typescript-eslint: + specifier: ^8.24.1 + version: 8.29.0(eslint@9.24.0)(typescript@5.7.3) + vite: + specifier: ^6.2.0 + version: 6.2.5 + vite-tsconfig-paths: + specifier: ^5.1.4 + version: 5.1.4(typescript@5.7.3)(vite@6.2.5) + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.26.8': + resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.26.10': + resolution: {integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.27.0': + resolution: {integrity: sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.0': + resolution: {integrity: sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.25.9': + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.26.0': + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.26.5': + resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.25.9': + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.27.0': + resolution: {integrity: sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.27.0': + resolution: {integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.25.9': + resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.25.9': + resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.27.0': + resolution: {integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.27.0': + resolution: {integrity: sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.27.0': + resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.25.2': + resolution: {integrity: sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.2': + resolution: {integrity: sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.2': + resolution: {integrity: sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.2': + resolution: {integrity: sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.2': + resolution: {integrity: sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.2': + resolution: {integrity: sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.2': + resolution: {integrity: sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.2': + resolution: {integrity: sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.2': + resolution: {integrity: sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.2': + resolution: {integrity: sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.2': + resolution: {integrity: sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.2': + resolution: {integrity: sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.2': + resolution: {integrity: sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.2': + resolution: {integrity: sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.2': + resolution: {integrity: sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.2': + resolution: {integrity: sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.2': + resolution: {integrity: sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.2': + resolution: {integrity: sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.2': + resolution: {integrity: sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.2': + resolution: {integrity: sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.2': + resolution: {integrity: sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.25.2': + resolution: {integrity: sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.2': + resolution: {integrity: sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.2': + resolution: {integrity: sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.2': + resolution: {integrity: sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.5.1': + resolution: {integrity: sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.20.0': + resolution: {integrity: sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.2.1': + resolution: {integrity: sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.12.0': + resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.13.0': + resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.24.0': + resolution: {integrity: sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.2.8': + resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.2': + resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} + engines: {node: '>=18.18'} + + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@rollup/rollup-android-arm-eabi@4.39.0': + resolution: {integrity: sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.39.0': + resolution: {integrity: sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.39.0': + resolution: {integrity: sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.39.0': + resolution: {integrity: sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.39.0': + resolution: {integrity: sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.39.0': + resolution: {integrity: sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.39.0': + resolution: {integrity: sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.39.0': + resolution: {integrity: sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.39.0': + resolution: {integrity: sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.39.0': + resolution: {integrity: sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.39.0': + resolution: {integrity: sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.39.0': + resolution: {integrity: sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.39.0': + resolution: {integrity: sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.39.0': + resolution: {integrity: sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.39.0': + resolution: {integrity: sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.39.0': + resolution: {integrity: sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.39.0': + resolution: {integrity: sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.39.0': + resolution: {integrity: sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.39.0': + resolution: {integrity: sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.39.0': + resolution: {integrity: sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==} + cpu: [x64] + os: [win32] + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.7': + resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} + + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + + '@types/estree@1.0.7': + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/react-dom@19.1.1': + resolution: {integrity: sha512-jFf/woGTVTjUJsl2O7hcopJ1r0upqoq/vIOoCj0yLh3RIXxWcljlpuZ+vEBRXsymD1jhfeJrlyTy/S1UW+4y1w==} + peerDependencies: + '@types/react': ^19.0.0 + + '@types/react@19.1.0': + resolution: {integrity: sha512-UaicktuQI+9UKyA4njtDOGBD/67t8YEBt2xdfqu8+gP9hqPUPsiXlNPcpS2gVdjmis5GKPG3fCxbQLVgxsQZ8w==} + + '@typescript-eslint/eslint-plugin@8.29.0': + resolution: {integrity: sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/parser@8.29.0': + resolution: {integrity: sha512-8C0+jlNJOwQso2GapCVWWfW/rzaq7Lbme+vGUFKE31djwNncIpgXD7Cd4weEsDdkoZDjH0lwwr3QDQFuyrMg9g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/scope-manager@8.29.0': + resolution: {integrity: sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/type-utils@8.29.0': + resolution: {integrity: sha512-ahaWQ42JAOx+NKEf5++WC/ua17q5l+j1GFrbbpVKzFL/tKVc0aYY8rVSYUpUvt2hUP1YBr7mwXzx+E/DfUWI9Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/types@8.29.0': + resolution: {integrity: sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.29.0': + resolution: {integrity: sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/utils@8.29.0': + resolution: {integrity: sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/visitor-keys@8.29.0': + resolution: {integrity: sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitejs/plugin-react@4.3.4': + resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.14.1: + resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.24.4: + resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001711: + resolution: {integrity: sha512-OpFA8GsKtoV3lCcsI3U5XBAV+oVrMu96OS8XafKqnhOaEAW2mveD1Mx81Sx/02chERwhDakuXs28zbyEc4QMKg==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + electron-to-chromium@1.5.132: + resolution: {integrity: sha512-QgX9EBvWGmvSRa74zqfnG7+Eno0Ak0vftBll0Pt2/z5b3bEGYL6OUXLgKPtvx73dn3dvwrlyVkjPKRRlhLYTEg==} + + esbuild@0.25.2: + resolution: {integrity: sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-react-hooks@5.2.0: + resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react-refresh@0.4.19: + resolution: {integrity: sha512-eyy8pcr/YxSYjBoqIFSrlbn9i/xvxUFa8CjzAYo9cFjgGXqq1hyjihcpZvxRLalpaWmueWR81xn7vuKmAFijDQ==} + peerDependencies: + eslint: '>=8.40' + + eslint-scope@8.3.0: + resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.24.0: + resolution: {integrity: sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@15.15.0: + resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} + engines: {node: '>=18'} + + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-dom@19.1.0: + resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} + peerDependencies: + react: ^19.1.0 + + react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + engines: {node: '>=0.10.0'} + + react-router-dom@7.5.0: + resolution: {integrity: sha512-fFhGFCULy4vIseTtH5PNcY/VvDJK5gvOWcwJVHQp8JQcWVr85ENhJ3UpuF/zP1tQOIFYNRJHzXtyhU1Bdgw0RA==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.5.0: + resolution: {integrity: sha512-estOHrRlDMKdlQa6Mj32gIks4J+AxNsYoE0DbTTxiMy2mPzZuWSDU+N85/r1IlNR7kGfznF3VCUlvc5IUO+B9g==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + + react@19.1.0: + resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@4.39.0: + resolution: {integrity: sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + scheduler@0.26.0: + resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + engines: {node: '>=10'} + hasBin: true + + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tsconfck@3.1.5: + resolution: {integrity: sha512-CLDfGgUp7XPswWnezWwsCRxNmgQjhYq3VXHM0/XIRxhVrKw0M1if9agzryh1QS3nxjCROvV+xWxoJO1YctzzWg==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + turbo-stream@2.4.0: + resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.29.0: + resolution: {integrity: sha512-ep9rVd9B4kQsZ7ZnWCVxUE/xDLUUUsRzE0poAeNu+4CkFErLfuvPt/qtm2EpnSyfvsR0S6QzDFSrPCFBwf64fg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + typescript@5.7.3: + resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} + engines: {node: '>=14.17'} + hasBin: true + + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + vite-tsconfig-paths@5.1.4: + resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true + + vite@6.2.5: + resolution: {integrity: sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.26.8': {} + + '@babel/core@7.26.10': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.27.0 + '@babel/helper-compilation-targets': 7.27.0 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helpers': 7.27.0 + '@babel/parser': 7.27.0 + '@babel/template': 7.27.0 + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 + convert-source-map: 2.0.0 + debug: 4.4.0 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.27.0': + dependencies: + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.27.0': + dependencies: + '@babel/compat-data': 7.26.8 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.4 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-module-imports@7.25.9': + dependencies: + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.26.5': {} + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/helper-validator-option@7.25.9': {} + + '@babel/helpers@7.27.0': + dependencies: + '@babel/template': 7.27.0 + '@babel/types': 7.27.0 + + '@babel/parser@7.27.0': + dependencies: + '@babel/types': 7.27.0 + + '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/template@7.27.0': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + + '@babel/traverse@7.27.0': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.27.0 + '@babel/parser': 7.27.0 + '@babel/template': 7.27.0 + '@babel/types': 7.27.0 + debug: 4.4.0 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.27.0': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@esbuild/aix-ppc64@0.25.2': + optional: true + + '@esbuild/android-arm64@0.25.2': + optional: true + + '@esbuild/android-arm@0.25.2': + optional: true + + '@esbuild/android-x64@0.25.2': + optional: true + + '@esbuild/darwin-arm64@0.25.2': + optional: true + + '@esbuild/darwin-x64@0.25.2': + optional: true + + '@esbuild/freebsd-arm64@0.25.2': + optional: true + + '@esbuild/freebsd-x64@0.25.2': + optional: true + + '@esbuild/linux-arm64@0.25.2': + optional: true + + '@esbuild/linux-arm@0.25.2': + optional: true + + '@esbuild/linux-ia32@0.25.2': + optional: true + + '@esbuild/linux-loong64@0.25.2': + optional: true + + '@esbuild/linux-mips64el@0.25.2': + optional: true + + '@esbuild/linux-ppc64@0.25.2': + optional: true + + '@esbuild/linux-riscv64@0.25.2': + optional: true + + '@esbuild/linux-s390x@0.25.2': + optional: true + + '@esbuild/linux-x64@0.25.2': + optional: true + + '@esbuild/netbsd-arm64@0.25.2': + optional: true + + '@esbuild/netbsd-x64@0.25.2': + optional: true + + '@esbuild/openbsd-arm64@0.25.2': + optional: true + + '@esbuild/openbsd-x64@0.25.2': + optional: true + + '@esbuild/sunos-x64@0.25.2': + optional: true + + '@esbuild/win32-arm64@0.25.2': + optional: true + + '@esbuild/win32-ia32@0.25.2': + optional: true + + '@esbuild/win32-x64@0.25.2': + optional: true + + '@eslint-community/eslint-utils@4.5.1(eslint@9.24.0)': + dependencies: + eslint: 9.24.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.20.0': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.0 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.2.1': {} + + '@eslint/core@0.12.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/core@0.13.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.0 + espree: 10.3.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.24.0': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.2.8': + dependencies: + '@eslint/core': 0.13.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.2': {} + + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@rollup/rollup-android-arm-eabi@4.39.0': + optional: true + + '@rollup/rollup-android-arm64@4.39.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.39.0': + optional: true + + '@rollup/rollup-darwin-x64@4.39.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.39.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.39.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.39.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.39.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.39.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.39.0': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.39.0': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.39.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.39.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.39.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.39.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.39.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.39.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.39.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.39.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.39.0': + optional: true + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.7 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.27.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + + '@types/babel__traverse@7.20.7': + dependencies: + '@babel/types': 7.27.0 + + '@types/cookie@0.6.0': {} + + '@types/estree@1.0.7': {} + + '@types/json-schema@7.0.15': {} + + '@types/react-dom@19.1.1(@types/react@19.1.0)': + dependencies: + '@types/react': 19.1.0 + + '@types/react@19.1.0': + dependencies: + csstype: 3.1.3 + + '@typescript-eslint/eslint-plugin@8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.24.0)(typescript@5.7.3))(eslint@9.24.0)(typescript@5.7.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.29.0(eslint@9.24.0)(typescript@5.7.3) + '@typescript-eslint/scope-manager': 8.29.0 + '@typescript-eslint/type-utils': 8.29.0(eslint@9.24.0)(typescript@5.7.3) + '@typescript-eslint/utils': 8.29.0(eslint@9.24.0)(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.29.0 + eslint: 9.24.0 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.7.3) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.29.0(eslint@9.24.0)(typescript@5.7.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.29.0 + '@typescript-eslint/types': 8.29.0 + '@typescript-eslint/typescript-estree': 8.29.0(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.29.0 + debug: 4.4.0 + eslint: 9.24.0 + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.29.0': + dependencies: + '@typescript-eslint/types': 8.29.0 + '@typescript-eslint/visitor-keys': 8.29.0 + + '@typescript-eslint/type-utils@8.29.0(eslint@9.24.0)(typescript@5.7.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.29.0(typescript@5.7.3) + '@typescript-eslint/utils': 8.29.0(eslint@9.24.0)(typescript@5.7.3) + debug: 4.4.0 + eslint: 9.24.0 + ts-api-utils: 2.1.0(typescript@5.7.3) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.29.0': {} + + '@typescript-eslint/typescript-estree@8.29.0(typescript@5.7.3)': + dependencies: + '@typescript-eslint/types': 8.29.0 + '@typescript-eslint/visitor-keys': 8.29.0 + debug: 4.4.0 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.1 + ts-api-utils: 2.1.0(typescript@5.7.3) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.29.0(eslint@9.24.0)(typescript@5.7.3)': + dependencies: + '@eslint-community/eslint-utils': 4.5.1(eslint@9.24.0) + '@typescript-eslint/scope-manager': 8.29.0 + '@typescript-eslint/types': 8.29.0 + '@typescript-eslint/typescript-estree': 8.29.0(typescript@5.7.3) + eslint: 9.24.0 + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.29.0': + dependencies: + '@typescript-eslint/types': 8.29.0 + eslint-visitor-keys: 4.2.0 + + '@vitejs/plugin-react@4.3.4(vite@6.2.5)': + dependencies: + '@babel/core': 7.26.10 + '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.10) + '@types/babel__core': 7.20.5 + react-refresh: 0.14.2 + vite: 6.2.5 + transitivePeerDependencies: + - supports-color + + acorn-jsx@5.3.2(acorn@8.14.1): + dependencies: + acorn: 8.14.1 + + acorn@8.14.1: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + balanced-match@1.0.2: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.24.4: + dependencies: + caniuse-lite: 1.0.30001711 + electron-to-chromium: 1.5.132 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.24.4) + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001711: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + cookie@1.0.2: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.1.3: {} + + debug@4.4.0: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + electron-to-chromium@1.5.132: {} + + esbuild@0.25.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.2 + '@esbuild/android-arm': 0.25.2 + '@esbuild/android-arm64': 0.25.2 + '@esbuild/android-x64': 0.25.2 + '@esbuild/darwin-arm64': 0.25.2 + '@esbuild/darwin-x64': 0.25.2 + '@esbuild/freebsd-arm64': 0.25.2 + '@esbuild/freebsd-x64': 0.25.2 + '@esbuild/linux-arm': 0.25.2 + '@esbuild/linux-arm64': 0.25.2 + '@esbuild/linux-ia32': 0.25.2 + '@esbuild/linux-loong64': 0.25.2 + '@esbuild/linux-mips64el': 0.25.2 + '@esbuild/linux-ppc64': 0.25.2 + '@esbuild/linux-riscv64': 0.25.2 + '@esbuild/linux-s390x': 0.25.2 + '@esbuild/linux-x64': 0.25.2 + '@esbuild/netbsd-arm64': 0.25.2 + '@esbuild/netbsd-x64': 0.25.2 + '@esbuild/openbsd-arm64': 0.25.2 + '@esbuild/openbsd-x64': 0.25.2 + '@esbuild/sunos-x64': 0.25.2 + '@esbuild/win32-arm64': 0.25.2 + '@esbuild/win32-ia32': 0.25.2 + '@esbuild/win32-x64': 0.25.2 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@5.2.0(eslint@9.24.0): + dependencies: + eslint: 9.24.0 + + eslint-plugin-react-refresh@0.4.19(eslint@9.24.0): + dependencies: + eslint: 9.24.0 + + eslint-scope@8.3.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.0: {} + + eslint@9.24.0: + dependencies: + '@eslint-community/eslint-utils': 4.5.1(eslint@9.24.0) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.20.0 + '@eslint/config-helpers': 0.2.1 + '@eslint/core': 0.12.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.24.0 + '@eslint/plugin-kit': 0.2.8 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.2 + '@types/estree': 1.0.7 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.0 + escape-string-regexp: 4.0.0 + eslint-scope: 8.3.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.3.0: + dependencies: + acorn: 8.14.1 + acorn-jsx: 5.3.2(acorn@8.14.1) + eslint-visitor-keys: 4.2.0 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@11.12.0: {} + + globals@14.0.0: {} + + globals@15.15.0: {} + + globrex@0.1.2: {} + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + isexe@2.0.0: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + node-releases@2.0.19: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + postcss@8.5.3: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + react-dom@19.1.0(react@19.1.0): + dependencies: + react: 19.1.0 + scheduler: 0.26.0 + + react-refresh@0.14.2: {} + + react-router-dom@7.5.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-router: 7.5.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + + react-router@7.5.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@types/cookie': 0.6.0 + cookie: 1.0.2 + react: 19.1.0 + set-cookie-parser: 2.7.1 + turbo-stream: 2.4.0 + optionalDependencies: + react-dom: 19.1.0(react@19.1.0) + + react@19.1.0: {} + + resolve-from@4.0.0: {} + + reusify@1.1.0: {} + + rollup@4.39.0: + dependencies: + '@types/estree': 1.0.7 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.39.0 + '@rollup/rollup-android-arm64': 4.39.0 + '@rollup/rollup-darwin-arm64': 4.39.0 + '@rollup/rollup-darwin-x64': 4.39.0 + '@rollup/rollup-freebsd-arm64': 4.39.0 + '@rollup/rollup-freebsd-x64': 4.39.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.39.0 + '@rollup/rollup-linux-arm-musleabihf': 4.39.0 + '@rollup/rollup-linux-arm64-gnu': 4.39.0 + '@rollup/rollup-linux-arm64-musl': 4.39.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.39.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.39.0 + '@rollup/rollup-linux-riscv64-gnu': 4.39.0 + '@rollup/rollup-linux-riscv64-musl': 4.39.0 + '@rollup/rollup-linux-s390x-gnu': 4.39.0 + '@rollup/rollup-linux-x64-gnu': 4.39.0 + '@rollup/rollup-linux-x64-musl': 4.39.0 + '@rollup/rollup-win32-arm64-msvc': 4.39.0 + '@rollup/rollup-win32-ia32-msvc': 4.39.0 + '@rollup/rollup-win32-x64-msvc': 4.39.0 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + scheduler@0.26.0: {} + + semver@6.3.1: {} + + semver@7.7.1: {} + + set-cookie-parser@2.7.1: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + source-map-js@1.2.1: {} + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-api-utils@2.1.0(typescript@5.7.3): + dependencies: + typescript: 5.7.3 + + tsconfck@3.1.5(typescript@5.7.3): + optionalDependencies: + typescript: 5.7.3 + + turbo-stream@2.4.0: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.29.0(eslint@9.24.0)(typescript@5.7.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.24.0)(typescript@5.7.3))(eslint@9.24.0)(typescript@5.7.3) + '@typescript-eslint/parser': 8.29.0(eslint@9.24.0)(typescript@5.7.3) + '@typescript-eslint/utils': 8.29.0(eslint@9.24.0)(typescript@5.7.3) + eslint: 9.24.0 + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + typescript@5.7.3: {} + + update-browserslist-db@1.1.3(browserslist@4.24.4): + dependencies: + browserslist: 4.24.4 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + vite-tsconfig-paths@5.1.4(typescript@5.7.3)(vite@6.2.5): + dependencies: + debug: 4.4.0 + globrex: 0.1.2 + tsconfck: 3.1.5(typescript@5.7.3) + optionalDependencies: + vite: 6.2.5 + transitivePeerDependencies: + - supports-color + - typescript + + vite@6.2.5: + dependencies: + esbuild: 0.25.2 + postcss: 8.5.3 + rollup: 4.39.0 + optionalDependencies: + fsevents: 2.3.3 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + yallist@3.1.1: {} + + yocto-queue@0.1.0: {} diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 00000000..c3a99793 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,22 @@ +import Home from "@/pages/home/home"; +import { BrowserRouter, Route, Routes } from "react-router-dom"; +import Login from "./pages/login/login"; +import SignUp from "./pages/sign-up/sign-up"; +import Items from "./pages/items/items"; + +function App() { + return ( + <> + + + } /> + } /> + } /> + } /> + + + + ); +} + +export default App; diff --git a/src/api/getProducts.ts b/src/api/getProducts.ts new file mode 100644 index 00000000..62485b21 --- /dev/null +++ b/src/api/getProducts.ts @@ -0,0 +1,48 @@ +import SERVER_URL from "@/constants/server-url"; + +export interface Product { + id: number; + name: string; + description: string; + price: number; + tags: string[]; + images: string[]; + ownerId: number; + favoriteCount: number; + createdAt: string; + updatedAt: string; +} + +export type Products = Product[]; + +export interface Query { + page: number; + pageSize: number; + orderBy: "recent" | "favorite"; + keyword?: string; +} + +export interface ProductsData { + list: Products; + totalCount: number; +} + +const getProducts = async (query: Query): Promise => { + const params = new URLSearchParams(); + Object.entries(query).forEach(([key, value]) => { + params.append(key, String(value)); + }); + + const url = `${SERVER_URL}/products?${params.toString()}`; + + const response = await fetch(url); + if (!response.ok) { + throw new Error( + `상품 데이터를 불러오는 데 실패했습니다. ${response.status}` + ); + } + const data = await response.json(); + return data; +}; + +export default getProducts; diff --git a/src/assets/images/banner-home-01.png b/src/assets/images/banner-home-01.png new file mode 100644 index 00000000..a77985be Binary files /dev/null and b/src/assets/images/banner-home-01.png differ diff --git a/src/assets/images/banner-home-02.png b/src/assets/images/banner-home-02.png new file mode 100644 index 00000000..837a1709 Binary files /dev/null and b/src/assets/images/banner-home-02.png differ diff --git a/src/assets/images/banner-home-03.png b/src/assets/images/banner-home-03.png new file mode 100644 index 00000000..d4194c6b Binary files /dev/null and b/src/assets/images/banner-home-03.png differ diff --git a/src/assets/images/footer-home.png b/src/assets/images/footer-home.png new file mode 100644 index 00000000..31853d71 Binary files /dev/null and b/src/assets/images/footer-home.png differ diff --git a/src/assets/images/google-auth.png b/src/assets/images/google-auth.png new file mode 100644 index 00000000..53575dc0 Binary files /dev/null and b/src/assets/images/google-auth.png differ diff --git a/src/assets/images/hero-home.png b/src/assets/images/hero-home.png new file mode 100644 index 00000000..83a4881c Binary files /dev/null and b/src/assets/images/hero-home.png differ diff --git a/src/assets/images/ic_arrow_down.svg b/src/assets/images/ic_arrow_down.svg new file mode 100644 index 00000000..8308690f --- /dev/null +++ b/src/assets/images/ic_arrow_down.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/ic_facebook.png b/src/assets/images/ic_facebook.png new file mode 100644 index 00000000..58333d45 Binary files /dev/null and b/src/assets/images/ic_facebook.png differ diff --git a/src/assets/images/ic_heart-blank.svg b/src/assets/images/ic_heart-blank.svg new file mode 100644 index 00000000..86d4af77 --- /dev/null +++ b/src/assets/images/ic_heart-blank.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/ic_heart-full.svg b/src/assets/images/ic_heart-full.svg new file mode 100644 index 00000000..557bade0 --- /dev/null +++ b/src/assets/images/ic_heart-full.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/ic_instagram.png b/src/assets/images/ic_instagram.png new file mode 100644 index 00000000..98e24ea6 Binary files /dev/null and b/src/assets/images/ic_instagram.png differ diff --git a/src/assets/images/ic_profile.svg b/src/assets/images/ic_profile.svg new file mode 100644 index 00000000..0480454d --- /dev/null +++ b/src/assets/images/ic_profile.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/images/ic_search.svg b/src/assets/images/ic_search.svg new file mode 100644 index 00000000..52241e6d --- /dev/null +++ b/src/assets/images/ic_search.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/ic_twitter.png b/src/assets/images/ic_twitter.png new file mode 100644 index 00000000..5df0852d Binary files /dev/null and b/src/assets/images/ic_twitter.png differ diff --git a/src/assets/images/ic_youtube.png b/src/assets/images/ic_youtube.png new file mode 100644 index 00000000..f51731d4 Binary files /dev/null and b/src/assets/images/ic_youtube.png differ diff --git a/src/assets/images/kakao-auth.png b/src/assets/images/kakao-auth.png new file mode 100644 index 00000000..000d07e3 Binary files /dev/null and b/src/assets/images/kakao-auth.png differ diff --git a/src/assets/images/logo-icon.svg b/src/assets/images/logo-icon.svg new file mode 100644 index 00000000..95d41963 --- /dev/null +++ b/src/assets/images/logo-icon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/assets/images/logo-typo.svg b/src/assets/images/logo-typo.svg new file mode 100644 index 00000000..22e13aba --- /dev/null +++ b/src/assets/images/logo-typo.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/password-toggle_off.png b/src/assets/images/password-toggle_off.png new file mode 100644 index 00000000..fb4eeaf4 Binary files /dev/null and b/src/assets/images/password-toggle_off.png differ diff --git a/src/assets/images/password-toggle_on.png b/src/assets/images/password-toggle_on.png new file mode 100644 index 00000000..467cf49a Binary files /dev/null and b/src/assets/images/password-toggle_on.png differ diff --git a/src/components/drop-down.module.css b/src/components/drop-down.module.css new file mode 100644 index 00000000..f11abd64 --- /dev/null +++ b/src/components/drop-down.module.css @@ -0,0 +1,53 @@ +.container { + position: relative; + width: 130px; + height: auto; +} + +.border { + border: 1px solid var(--gray-200); + border-radius: 12px; +} + +.button { + width: 100%; + height: 42px; + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 20px; + background-color: #ffffff; + composes: border; + composes: font-lg font-regular from global; +} + +.list { + position: absolute; + top: 100%; + left: 0; + margin-top: 8px; + width: 100%; +} + +.option { + display: flex; + width: 130px; + height: 42px; + justify-content: center; + align-items: center; + background-color: #ffffff; + composes: border; + composes: font-lg font-regular from global; +} + +.option:hover { + cursor: pointer; +} + +.list > .option:first-child { + border-radius: 12px 12px 0 0; +} + +.list > .option:last-child { + border-radius: 0 0 12px 12px; +} diff --git a/src/components/drop-down.tsx b/src/components/drop-down.tsx new file mode 100644 index 00000000..8597cc39 --- /dev/null +++ b/src/components/drop-down.tsx @@ -0,0 +1,120 @@ +import { useState, useContext, createContext, useCallback } from "react"; +import arrowDown from "@assets/images/ic_arrow_down.svg"; +import styles from "./drop-down.module.css"; + +export type OrderBy = "favorite" | "recent"; + +interface DropDownContextType { + orderBy: "favorite" | "recent"; + isOpen: boolean; + setIsOpen: React.Dispatch>; + handleClick: () => void; + handleChangeOrderBy: (order: OrderBy) => void; +} + +interface DropDownContainerProps { + orderBy: OrderBy; + handleChangeOrderBy: (order: OrderBy) => void; + children: React.ReactNode; +} + +const DropDownContext = createContext( + undefined +); + +const useDropDownContext = () => { + const context = useContext(DropDownContext); + if (!context) { + throw new Error("DropDown에 감싸져 있어야 합니다."); + } + return context; +}; + +function DropDownContainer({ + orderBy = "recent", + handleChangeOrderBy, + children, +}: DropDownContainerProps) { + const [isOpen, setIsOpen] = useState(false); + + const handleClick: () => void = useCallback(() => { + setIsOpen(!isOpen); + }, [isOpen]); + + return ( + +
{children}
+
+ ); +} + +function DropDownButton() { + const { orderBy, handleClick } = useDropDownContext(); + + return ( + + ); +} + +interface DropDownListProps { + children: React.ReactNode; +} + +function DropDownList({ children }: DropDownListProps) { + const { isOpen } = useDropDownContext(); + + if (!isOpen) return null; + return
    {children}
; +} + +interface DropDownOptionProps { + option: OrderBy; +} + +const dict = { favorite: "좋아요순", recent: "최신순" }; + +function DropDownOption({ option }: DropDownOptionProps) { + const { handleChangeOrderBy, setIsOpen } = useDropDownContext(); + + const handleClick = () => { + handleChangeOrderBy(option); + setIsOpen((prev) => !prev); + }; + + return ( +
  • + {dict[option]} +
  • + ); +} + +interface DropDownProps { + /** 현재 정렬 기준 값 */ + orderBy: OrderBy; + /** 정렬 기준 변경 콜백 */ + handleChangeOrderBy: (order: OrderBy) => void; +} + +/** DropDown 컴포넌트를 한 번에 사용하기 위한 래퍼 */ +export default function DropDown({ + orderBy, + handleChangeOrderBy, +}: DropDownProps) { + return ( + + + + + + + + ); +} diff --git a/src/components/input.module.css b/src/components/input.module.css new file mode 100644 index 00000000..e2b6de8a --- /dev/null +++ b/src/components/input.module.css @@ -0,0 +1,31 @@ +.input__group { + width: 100%; + display: flex; + flex-direction: column; + gap: 8px; + text-align: left; +} + +.input__label { + color: var(--gray-800); +} + +.input__field { + width: 100%; + height: 56px; + padding: 16px 24px; + background-color: var(--gray-100); + border: none; + border-radius: 12px; +} + +.input__field--error { + border: 1px solid #f74747; +} + +.input__field::placeholder { + color: var(--gray-400); + font-weight: 400; + font-size: 16px; + line-height: 26px; +} diff --git a/src/components/input.tsx b/src/components/input.tsx new file mode 100644 index 00000000..d88adc98 --- /dev/null +++ b/src/components/input.tsx @@ -0,0 +1,100 @@ +import { createContext, useContext, useState } from "react"; +import styles from "./input.module.css"; +import inputValidate from "@/utils/input-validate"; + +// type, interface 선언 +export type inputType = "email" | "username" | "password"; + +interface InputProps { + type: inputType; + children: React.ReactNode; +} + +interface InputLabelProps { + label: string; +} + +interface InputFieldProps { + placeholder: string; +} + +interface InputContextProps { + value: string; + type: inputType; + error: boolean; + handleChange: (e: React.ChangeEvent) => void; + handleBlur: (e: React.FocusEvent) => void; +} + +//input 컴포넌트 들에서 사용할 컨텍스트 생성, 커스텀 훅 생성 +const InputContext = createContext(undefined); + +const useInputContext = (): InputContextProps => { + const context = useContext(InputContext); + if (!context) { + throw new Error("Input.Group으로 감싸져야 합니다."); + } + return context; +}; + +//Input요소들을 감싸는 컨테이너 역할을 할 Input.Group컴포넌트 작성 +//상태관리, 이벤트 리스너 생성해서 내려줍니다. +function InputGroup({ type, children }: InputProps) { + const [value, setValue] = useState(""); + const [error, setError] = useState(false); + + const handleChange = (e: React.ChangeEvent) => { + setValue(e.target.value); + }; + + const handleBlur = (e: React.FocusEvent) => { + const value = e.target.value; + const isValid = inputValidate(type, value); + console.log(isValid); + setError(!isValid); + }; + + return ( + +
    {children}
    +
    + ); +} + +//단순히 라벨 렌더링만 하는 Input.Label 컴포넌트 입니다. +function InputLabel({ label }: InputLabelProps) { + return ( + + ); +} + +function InputField({ placeholder }: InputFieldProps) { + const { value, type, error, handleChange, handleBlur } = useInputContext(); + + const inputType = type === "username" ? "text" : type; + + return ( + + ); +} + +const Input = { + Group: InputGroup, + Label: InputLabel, + Field: InputField, +}; + +export default Input; diff --git a/src/components/logo.tsx b/src/components/logo.tsx new file mode 100644 index 00000000..98351489 --- /dev/null +++ b/src/components/logo.tsx @@ -0,0 +1,25 @@ +import logoIcon from "@assets/images/logo-icon.svg"; +import logoTypo from "@assets/images/logo-typo.svg"; +import { Link } from "react-router-dom"; +import styles from "@/pages/home/styles/home-header.module.css"; + +interface LogoProps { + isMobile?: boolean; +} + +export default function Logo({ isMobile = false }: LogoProps) { + return ( + <> + + {!isMobile && ( + 판다마켓 아이콘 + )} + 판다마켓 + + + ); +} diff --git a/src/components/pagination.module.css b/src/components/pagination.module.css new file mode 100644 index 00000000..5a9bce24 --- /dev/null +++ b/src/components/pagination.module.css @@ -0,0 +1,46 @@ +.pagesList { + margin-top: 43px; + display: flex; + align-items: center; + justify-content: center; + gap: 4px; +} + +.el { + width: 40px; + height: 40px; +} + +.pageBtn { + width: 100%; + height: 100%; + border: 1px solid var(--gray-200); + border-radius: 20px; + color: var(--gray-500); + background-color: #ffffff; + composes: font-lg font-semibold from global; +} + +.pageBtn:hover { + cursor: pointer; +} + +.arrowBtn { + width: fit-content; + height: auto; + border: none; + background-color: transparent; +} + +.arrowBtn:hover { + cursor: pointer; +} + +.arrowBtn:disabled { + cursor: auto; +} + +.active { + color: var(--gray-100); + background-color: #2f80ed; +} diff --git a/src/components/pagination.tsx b/src/components/pagination.tsx new file mode 100644 index 00000000..d1091416 --- /dev/null +++ b/src/components/pagination.tsx @@ -0,0 +1,68 @@ +import { SetStateAction } from "react"; +import styles from "./pagination.module.css"; +import ArrowSvg from "./svg-components/arrow-svg"; + +export interface PaginationProps { + currentPage: number; + totalPage: number; + setPage: React.Dispatch>; +} + +export default function Pagination({ + currentPage, + totalPage, + setPage, +}: PaginationProps) { + let pages; + if (currentPage <= 3) { + pages = Array.from({ length: 5 }, (_, i) => i + 1); + } else if (currentPage >= totalPage - 3) { + pages = Array.from({ length: 5 }, (_, i) => i + totalPage - 4); + } else { + pages = Array.from({ length: 5 }, (_, i) => i + currentPage - 2); + } + + const leftIsDisabled = currentPage === 1; + + const rightIsDisable = currentPage === totalPage; + + const handleClick = (page: number) => { + console.log(page); + setPage(page); + }; + + return ( +
      +
    • + +
    • + {pages.map((page) => ( +
    • + +
    • + ))} +
    • + +
    • +
    + ); +} diff --git a/src/components/product.module.css b/src/components/product.module.css new file mode 100644 index 00000000..3c0c7c0e --- /dev/null +++ b/src/components/product.module.css @@ -0,0 +1,38 @@ +.card { + width: 100%; + height: 100%; +} + +.image { + width: 100%; + aspect-ratio: 1 / 1; + border-radius: 16px; + object-fit: cover; + object-position: center; +} + +.name { + margin-top: 16px; + composes: font-md font-medium from global; +} + +.price { + margin-top: 6px; + composes: font-lg font-bold from global; +} + +.likeButton { + background: none; + border: none; + padding: 0; + margin: 0; + display: flex; + align-items: center; + gap: 4px; + cursor: pointer; + line-height: 0; +} + +.favoriteCount { + composes: font-xs font-medium from global; +} diff --git a/src/components/product.tsx b/src/components/product.tsx new file mode 100644 index 00000000..dbdc6f0c --- /dev/null +++ b/src/components/product.tsx @@ -0,0 +1,44 @@ +import { Product } from "@/api/getProducts"; +import heartBlank from "@assets/images/ic_heart-blank.svg"; +import heartFull from "@assets/images/ic_heart-full.svg"; +import placeholderImage from "@assets/images/hero-home.png"; +import { useState } from "react"; +import styles from "./product.module.css"; + +interface ProductElementProps { + product: Product; +} + +export default function ProductElement({ product }: ProductElementProps) { + const { name, price, images, favoriteCount } = product; + const [isLiked, setIsLiked] = useState(false); + const [currentImageSrc, setCurrentImageSrc] = useState( + images && images.length > 0 ? images[0] : placeholderImage + ); + + const handleClick: () => void = () => { + setIsLiked((prev) => !prev); + //좋아요를 서버에 요청 보내야 함 + }; + + const handleImageError = () => { + setCurrentImageSrc(placeholderImage); + }; + + return ( +
    + {name} +

    {name}

    +

    {price}원

    + +
    + ); +} diff --git a/src/components/simple-auth.module.css b/src/components/simple-auth.module.css new file mode 100644 index 00000000..c64c66bf --- /dev/null +++ b/src/components/simple-auth.module.css @@ -0,0 +1,19 @@ +.simple-auth { + width: 100%; + max-width: 640px; + height: 74px; + padding: 16px 23px; + display: flex; + gap: 16px; + background-color: #e6f2ff; + align-items: center; + border-radius: 8px; +} + +.simple-auth__span { + flex-grow: 1; +} + +.simple-auth__image { + width: 42px; +} diff --git a/src/components/simple-auth.tsx b/src/components/simple-auth.tsx new file mode 100644 index 00000000..af4a38cc --- /dev/null +++ b/src/components/simple-auth.tsx @@ -0,0 +1,15 @@ +import googleAuth from "@assets/images/google-auth.png"; +import kakaoAuth from "@assets/images/kakao-auth.png"; +import styles from "./simple-auth.module.css"; + +export default function SimpleAuth() { + return ( +
    + + 간편 로그인하기 + + + +
    + ); +} diff --git a/src/components/submit-button.module.css b/src/components/submit-button.module.css new file mode 100644 index 00000000..14db0396 --- /dev/null +++ b/src/components/submit-button.module.css @@ -0,0 +1,12 @@ +.submit-button { + width: 100%; + padding: 12px 124px; + background-color: var(--gray-400); + border: none; + border-radius: 40px; + color: var(--gray-100); +} + +.submit-button:hover { + cursor: pointer; +} diff --git a/src/components/submit-button.tsx b/src/components/submit-button.tsx new file mode 100644 index 00000000..d18472de --- /dev/null +++ b/src/components/submit-button.tsx @@ -0,0 +1,16 @@ +import styles from "./submit-button.module.css"; + +interface SubmitButtonProps { + children: string; +} + +export default function SubmitButton({ children }: SubmitButtonProps) { + return ( + + ); +} diff --git a/src/components/svg-components/arrow-svg.tsx b/src/components/svg-components/arrow-svg.tsx new file mode 100644 index 00000000..6dd7578d --- /dev/null +++ b/src/components/svg-components/arrow-svg.tsx @@ -0,0 +1,40 @@ +interface ArrowSvgProps { + direction: "right" | "left"; + disabled?: boolean; +} + +export default function ArrowSvg({ + direction, + disabled = false, +}: ArrowSvgProps) { + const arrowColor = disabled ? "#9CA3AF" : "#4B5563"; + + const groupTransform = + direction === "right" ? "rotate(180 20 20)" : undefined; + + return ( + + + + + + + + + + + + ); +} diff --git a/src/constants/bannerData.ts b/src/constants/bannerData.ts new file mode 100644 index 00000000..8eb4b210 --- /dev/null +++ b/src/constants/bannerData.ts @@ -0,0 +1,31 @@ +import bannerHome01 from "@assets/images/banner-home-01.png"; +import bannerHome02 from "@assets/images/banner-home-02.png"; +import bannerHome03 from "@assets/images/banner-home-03.png"; + +export interface Banner { + image: string; + keyword: string; + title: string; + description: string; +} + +export const bannerData: Banner[] = [ + { + image: bannerHome01, + keyword: "Hot item", + title: "인기 상품을 확인해보세요", + description: "가장 HOT한 중고거래 물품을\n판다 마켓에서 확인해보세요", + }, + { + image: bannerHome02, + keyword: "Search", + title: "구매를 원하는 상품을 검색하세요", + description: "구매하고 싶은 물품은 검색해서\n쉽게 찾아보세요", + }, + { + image: bannerHome03, + keyword: "register", + title: "판매를 원하는 상품을 등록하세요", + description: "어떤 물건이든 판매하고 싶은 상품을\n쉽게 등록하세요", + }, +]; diff --git a/src/constants/server-url.ts b/src/constants/server-url.ts new file mode 100644 index 00000000..551c3d80 --- /dev/null +++ b/src/constants/server-url.ts @@ -0,0 +1,3 @@ +const SERVER_URL = "https://panda-market-api.vercel.app"; + +export default SERVER_URL; diff --git a/src/hooks/useMediaQuery.ts b/src/hooks/useMediaQuery.ts new file mode 100644 index 00000000..82107cb1 --- /dev/null +++ b/src/hooks/useMediaQuery.ts @@ -0,0 +1,18 @@ +import { useEffect, useState } from "react"; + +const useMediaQuery = (query: string) => { + const [matches, setMatches] = useState(window.matchMedia(query).matches); + + useEffect(() => { + const mediaQueryList = window.matchMedia(query); + const documentChangeHandler = () => setMatches(mediaQueryList.matches); + mediaQueryList.addEventListener("change", documentChangeHandler); + return () => { + mediaQueryList.removeEventListener("change", documentChangeHandler); + }; + }, [query]); + + return matches; +}; + +export default useMediaQuery; diff --git a/src/hooks/useResponsivePageSize.ts b/src/hooks/useResponsivePageSize.ts new file mode 100644 index 00000000..788bd00f --- /dev/null +++ b/src/hooks/useResponsivePageSize.ts @@ -0,0 +1,49 @@ +import useMediaQuery from "@/hooks/useMediaQuery"; +import { useEffect, useState } from "react"; + +export enum DefaultPageSize { + mobile = 1, + tablet = 2, + desktop = 4, +} + +export interface PageSizeConfig { + mobile: number; + tablet: number; + desktop: number; +} + +export default function useResponsivePageSize( + pageSizeConfig: PageSizeConfig = DefaultPageSize, // 기본값으로 DefaultPageSize 사용 + defaultSizeKey: keyof PageSizeConfig = "desktop" // 초기 렌더링 시 사용할 키 +) { + const isMobile = useMediaQuery("(max-width: 767px)"); + const isTablet = useMediaQuery("(min-width: 768px) and (max-width: 1023px)"); + + // useMediaQuery가 안정화되었는지 확인하는 상태 (선택적) + const [isHydrated, setIsHydrated] = useState(false); + + const [pageSize, setPageSize] = useState( + pageSizeConfig[defaultSizeKey] + ); + + useEffect(() => { + setIsHydrated(true); // 클라이언트에서 실행되었음을 표시 + }, []); + + useEffect(() => { + // isHydrated가 true가 된 후 (즉, 클라이언트에서 useMediaQuery가 값을 가질 준비가 된 후) pageSize를 설정 + if (!isHydrated) return; // 아직 클라이언트 사이드 렌더링 전이면 아무것도 안 함 + + if (isMobile) { + setPageSize(pageSizeConfig.mobile); + } else if (isTablet) { + setPageSize(pageSizeConfig.tablet); + } else { + setPageSize(pageSizeConfig.desktop); + } + }, [isMobile, isTablet, pageSizeConfig, isHydrated]); + + // isHydrated 상태와 pageSize를 함께 반환하여 컴포넌트에서 활용 가능 + return { pageSize, isResponsiveSizeReady: isHydrated }; +} diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 00000000..92a27894 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,12 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import "@/styles/reset.css"; +import "@/styles/index.css"; +import "@/styles/fonts.css"; +import App from "./App.tsx"; + +createRoot(document.getElementById("root")!).render( + + + +); diff --git a/src/pages/home/home-footer/home-footer-bottom.tsx b/src/pages/home/home-footer/home-footer-bottom.tsx new file mode 100644 index 00000000..9739924e --- /dev/null +++ b/src/pages/home/home-footer/home-footer-bottom.tsx @@ -0,0 +1,31 @@ +import styles from "../styles/home-footer.module.css"; +import icFacebook from "@assets/images/ic_facebook.png"; +import icInstagram from "@assets/images/ic_instagram.png"; +import icTwitter from "@assets/images/ic_twitter.png"; +import icYoutube from "@assets/images/ic_youtube.png"; + +export default function HomeFooterBottom() { + return ( +
    +

    ©codeit - 2024

    + + +
    + ); +} diff --git a/src/pages/home/home-footer/home-footer-top.tsx b/src/pages/home/home-footer/home-footer-top.tsx new file mode 100644 index 00000000..2558d6ed --- /dev/null +++ b/src/pages/home/home-footer/home-footer-top.tsx @@ -0,0 +1,19 @@ +import styles from "../styles/home-footer.module.css"; +import footerHome from "@assets/images/footer-home.png"; + +export default function HomeFooterTop() { + return ( +
    +

    + 믿을 수 있는 +
    + 판다마켓 중고 거래 +

    + 판다 캐릭터 +
    + ); +} diff --git a/src/pages/home/home-footer/home-footer.tsx b/src/pages/home/home-footer/home-footer.tsx new file mode 100644 index 00000000..126af024 --- /dev/null +++ b/src/pages/home/home-footer/home-footer.tsx @@ -0,0 +1,12 @@ +import styles from "../styles/home-footer.module.css"; +import HomeFooterTop from "./home-footer-top"; +import HomeFooterBottom from "./home-footer-bottom"; + +export default function HomeFooter() { + return ( +
    + + +
    + ); +} diff --git a/src/pages/home/home-header/home-header.tsx b/src/pages/home/home-header/home-header.tsx new file mode 100644 index 00000000..fcce11a2 --- /dev/null +++ b/src/pages/home/home-header/home-header.tsx @@ -0,0 +1,16 @@ +import Logo from "@/components/logo"; +import styles from "../styles/home-header.module.css"; +import HomeLoginButton from "./home-login-button"; + +interface HomeHeaderProps { + isMobile: boolean; +} + +export default function HomeHeader({ isMobile }: HomeHeaderProps) { + return ( +
    + + +
    + ); +} diff --git a/src/pages/home/home-header/home-login-button.tsx b/src/pages/home/home-header/home-login-button.tsx new file mode 100644 index 00000000..183d3630 --- /dev/null +++ b/src/pages/home/home-header/home-login-button.tsx @@ -0,0 +1,13 @@ +import styles from "../styles/home-header.module.css"; +import { Link } from "react-router-dom"; + +export default function HomeLoginButton() { + return ( + + 로그인 + + ); +} diff --git a/src/pages/home/home-main/home-feature.tsx b/src/pages/home/home-main/home-feature.tsx new file mode 100644 index 00000000..52b3beab --- /dev/null +++ b/src/pages/home/home-main/home-feature.tsx @@ -0,0 +1,28 @@ +import { Banner } from "@/constants/bannerData"; +import styles from "../styles/home-feature.module.css"; + +export default function HomeFeature({ + image, + keyword, + title, + description, +}: Banner) { + return ( +
    + +
    +

    + {keyword} +

    +

    + {title} +

    +

    + {description} +

    +
    +
    + ); +} diff --git a/src/pages/home/home-main/home-main.tsx b/src/pages/home/home-main/home-main.tsx new file mode 100644 index 00000000..400c196f --- /dev/null +++ b/src/pages/home/home-main/home-main.tsx @@ -0,0 +1,44 @@ +import styles from "../styles/home.module.css"; +import { Link } from "react-router-dom"; +import heroHome from "@assets/images/hero-home.png"; +import { bannerData } from "@/constants/bannerData"; +import HomeFeature from "./home-feature"; +import useMediaQuery from "@/hooks/useMediaQuery"; + +interface HomeMainProps { + isMobile: boolean; +} + +export default function HomeMain({ isMobile }: HomeMainProps) { + const isDesktop = useMediaQuery("(min-width: 1080px)"); + + return ( +
    +
    +
    +

    + 일상의 모든 물건을{(isMobile || isDesktop) &&
    } 거래해보세요 +

    + + 구경하러 가기 + +
    + 판다마켓 +
    +
    + {bannerData.map((banner, idx) => ( + + ))} +
    +
    + ); +} diff --git a/src/pages/home/home.tsx b/src/pages/home/home.tsx new file mode 100644 index 00000000..ce31f1a9 --- /dev/null +++ b/src/pages/home/home.tsx @@ -0,0 +1,16 @@ +import useMediaQuery from "@/hooks/useMediaQuery"; +import HomeFooter from "./home-footer/home-footer"; +import HomeMain from "./home-main/home-main"; +import HomeHeader from "./home-header/home-header"; + +export default function Home() { + const isMobile = useMediaQuery("(max-width:768px)"); + + return ( + <> + + + + + ); +} diff --git a/src/pages/home/styles/home-feature.module.css b/src/pages/home/styles/home-feature.module.css new file mode 100644 index 00000000..49c2235a --- /dev/null +++ b/src/pages/home/styles/home-feature.module.css @@ -0,0 +1,52 @@ +.home-feature { + width: 100%; + max-width: 988px; + height: auto; + margin: 40px auto; + padding: 0 16px; + background-color: #fcfcfc; +} + +.home-feature__image { + width: 100%; + height: auto; +} + +.home-feature__text-wrapper { + width: 100%; + margin-top: 24px; +} + +.home-feature__keyword { + color: var(--primary-100); +} + +.home-feature__title { + color: var(--gray-700); +} + +.home-feature__description { + white-space: pre-line; + color: var(--gray-700); +} + +@media (min-width: 768px) { + .home-feature { + margin-top: 0; + margin-bottom: 52px; + padding: 0 24px; + } +} + +@media (min-width: 1024px) { + .home-feature { + margin: 138px auto; + display: flex; + align-items: center; + gap: 64px; + } + + .home-feature__image { + max-width: 579px; + } +} diff --git a/src/pages/home/styles/home-footer.module.css b/src/pages/home/styles/home-footer.module.css new file mode 100644 index 00000000..6456d742 --- /dev/null +++ b/src/pages/home/styles/home-footer.module.css @@ -0,0 +1,68 @@ +.footer { + background-color: #0d0d22; + color: #ffffff; + text-align: center; +} + +.footer__top { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + min-height: 540px; + background-color: #d7e8ff; /* 연한 하늘색 */ +} + +.footer__slogan { + padding-top: 121px; + color: #333333; + margin-bottom: 32px; +} + +.footer__mascots { + width: 100%; + max-width: 747px; + height: auto; + margin-top: auto; +} + +.footer__bottom { + height: 160px; + display: flex; + flex-direction: row; + justify-content: space-around; + margin-top: 32px; + gap: 24px; +} + +.footer__nav { + display: flex; + gap: 24px; + font-size: 14px; + color: #cccccc; +} + +.footer__nav a { + color: inherit; + text-decoration: none; +} + +.footer__socials { + display: flex; + gap: 16px; +} + +.footer__socials a { + font-size: 18px; + color: #ffffff; + text-decoration: none; +} + +.footer__copyright { + font-size: 13px; + color: #aaaaaa; + margin-top: 8px; +} + +@media (min-width: 1024px) { +} diff --git a/src/pages/home/styles/home-header.module.css b/src/pages/home/styles/home-header.module.css new file mode 100644 index 00000000..0e4ca510 --- /dev/null +++ b/src/pages/home/styles/home-header.module.css @@ -0,0 +1,35 @@ +.header { + width: 100%; + height: 70px; + padding: 9.5px 16px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.logo { + display: flex; + gap: 8.6px; +} + +.logoIcon { + width: 40px; +} + +.logoTypo { + position: relative; + top: 1.5px; + width: 103px; +} + +.btn-login { + width: 128px; + height: 48px; + padding: 12px 23px; + background-color: var(--primary-100); + border-radius: 8px; + color: var(--gray-100); + text-decoration: none; + align-content: center; + text-align: center; +} diff --git a/src/pages/home/styles/home.module.css b/src/pages/home/styles/home.module.css new file mode 100644 index 00000000..8d72edaa --- /dev/null +++ b/src/pages/home/styles/home.module.css @@ -0,0 +1,80 @@ + + +.hero { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + min-height: 540px; + background-color: #cfe5ff; + align-items: center; + text-align: center; + overflow: hidden; +} + +.hero-content-wrapper { + display: flex; + flex-direction: column; + align-items: center; +} + +.hero__title { + margin-top: 48px; + color: var(--gray-700); +} + +.hero__btn { + width: fit-content; + height: 48px; + padding: 12px 71px; + margin-top: 18px; + background-color: var(--primary-100); + border-radius: 40px; + color: var(--gray-50); + text-decoration: none; +} + +.hero__image { + width: 100%; + min-width: 448px; + max-width: 746px; + height: auto; + margin-top: auto; +} + +.features { + margin-top: 52px; +} + +.features > *:nth-child(even) { + text-align: right; +} + +@media (min-width: 768px) { + .hero__btn { + padding: 12px 96px; + } + + .features { + margin-top: 24px; + } +} + +@media (min-width: 1024px) { + .hero { + flex-direction: row; + justify-content: center; + } + + .hero-content-wrapper { + align-items: baseline; + } + + .hero__title { + text-align: left; + } + + .features > *:nth-child(even) { + flex-direction: row-reverse; + } +} diff --git a/src/pages/items/items-best-products.tsx b/src/pages/items/items-best-products.tsx new file mode 100644 index 00000000..7bffef37 --- /dev/null +++ b/src/pages/items/items-best-products.tsx @@ -0,0 +1,43 @@ +import getProducts, { Products, Query } from "@/api/getProducts"; +import ProductElement from "@/components/product"; +import { useState, useEffect } from "react"; +import styles from "./styles/items-best-products.module.css"; +import useResponsivePageSize, { + DefaultPageSize, +} from "@/hooks/useResponsivePageSize"; + +export default function BestProducts() { + const [products, setProducts] = useState([]); + const { pageSize, isResponsiveSizeReady } = + useResponsivePageSize(DefaultPageSize); + + useEffect(() => { + if (!isResponsiveSizeReady) return; + + const query: Query = { page: 1, pageSize, orderBy: "favorite" }; + getProducts(query) + .then((data) => setProducts(data.list)) + .catch((error) => { + console.error("상품 데이터를 불러오는 데 실패했습니다.", error); + }) + .finally(() => { + console.log("상품 데이터 로딩 완료"); + }); + return () => { + console.log("상품 데이터 로딩 취소"); + }; + }, [pageSize, isResponsiveSizeReady]); + + return ( +
    +

    베스트 상품

    +
      + {products.map((product) => ( +
    • + +
    • + ))} +
    +
    + ); +} diff --git a/src/pages/items/items-header.tsx b/src/pages/items/items-header.tsx new file mode 100644 index 00000000..ade7a619 --- /dev/null +++ b/src/pages/items/items-header.tsx @@ -0,0 +1,29 @@ +import Logo from "@/components/logo"; +import { Link, NavLink } from "react-router-dom"; +import profile from "@assets/images/ic_profile.svg"; +import styles from "./styles/items-header.module.css"; +import useMediaQuery from "@/hooks/useMediaQuery"; + +export default function ItemsHeader() { + // isActive인자를 받아서 ``문자열을 반환하는 콜백함수 + const navLinkClasses = ({ isActive }: { isActive: boolean }) => + `${isActive ? styles.active : ""} font-2lg font-bold ${styles.navLink}`; + const isMobile = useMediaQuery("(max-width: 768px)"); + + return ( +
    + + +
    + ); +} diff --git a/src/pages/items/items-products.tsx b/src/pages/items/items-products.tsx new file mode 100644 index 00000000..3ce89988 --- /dev/null +++ b/src/pages/items/items-products.tsx @@ -0,0 +1,96 @@ +import getProducts, { Products, ProductsData, Query } from "@/api/getProducts"; +import ProductElement from "@/components/product"; +import { useEffect, useState } from "react"; +import { Link } from "react-router-dom"; +import styles from "./styles/items-products.module.css"; +import DropDown, { OrderBy } from "@/components/drop-down"; +import Pagination from "@/components/pagination"; +import icSearch from "@assets/images/ic_search.svg"; +import useMediaQuery from "@/hooks/useMediaQuery"; +import useResponsivePageSize from "@/hooks/useResponsivePageSize"; + +const allProductsPageSizeConfig = { + mobile: 4, + tablet: 6, + desktop: 10, +}; + +export default function AllProducts() { + const [productsData, setProductsData] = useState({ + list: [], + totalCount: 0, + }); + const [orderBy, setOrderBy] = useState("recent"); + const { pageSize: responsivePageSize, isResponsiveSizeReady } = + useResponsivePageSize( + allProductsPageSizeConfig, + "desktop" // 초기 기본값을 desktop으로 설정 + ); + const [page, setPage] = useState(1); + const isMobile = useMediaQuery("(max-width:767px)"); + + const products: Products = productsData.list; + const totalProductCount: number = productsData.totalCount; + const totalPage = Math.floor(totalProductCount / responsivePageSize) + 1; + + const handleChangeOrderBy = (order: OrderBy) => { + setOrderBy(order); + }; + + useEffect(() => { + if (!isResponsiveSizeReady) return; + + const query: Query = { page, pageSize: responsivePageSize, orderBy }; + getProducts(query) + .then((data) => setProductsData(data)) + .catch((error) => { + console.error("상품 데이터를 불러오는 데 실패했습니다.", error); + }) + .finally(() => { + console.log("상품 데이터 로딩 완료"); + }); + }, [page, orderBy, responsivePageSize, isResponsiveSizeReady]); + + return ( +
    +
    +

    전체 상품

    + {!isMobile && ( +
    + + +
    + )} + + 상품 등록하기 + +
    + {isMobile && ( +
    + + +
    + )} + +
    +
    +
      + {products.map((product) => ( +
    • + +
    • + ))} +
    + +
    + ); +} diff --git a/src/pages/items/items.tsx b/src/pages/items/items.tsx new file mode 100644 index 00000000..11b33c28 --- /dev/null +++ b/src/pages/items/items.tsx @@ -0,0 +1,16 @@ +import ItemsHeader from "./items-header"; +import AllProducts from "./items-products"; +import BestProducts from "./items-best-products"; +import styles from "./styles/items.module.css"; + +export default function Items() { + return ( + <> + +
    + + +
    + + ); +} diff --git a/src/pages/items/styles/items-best-products.module.css b/src/pages/items/styles/items-best-products.module.css new file mode 100644 index 00000000..7f85ac6a --- /dev/null +++ b/src/pages/items/styles/items-best-products.module.css @@ -0,0 +1,34 @@ +.bestProducts { + margin: 0 auto; + margin-top: 24px; + max-width: 1200px; +} + +.bestProductsTitle { + composes: font-xl font-bold from global; +} + +.bestProductsList { + margin-top: 16px; + display: flex; + gap: 24px; +} + +.bestProductElement { + width: 282px; + height: 378px; +} + +@media (max-width: 1023px) { + .bestProductElement { + width: 50%; + height: auto; + } +} + +@media (max-width: 767px) { + .bestProductElement { + width: 100%; + height: auto; + } +} diff --git a/src/pages/items/styles/items-header.module.css b/src/pages/items/styles/items-header.module.css new file mode 100644 index 00000000..2bf1c35e --- /dev/null +++ b/src/pages/items/styles/items-header.module.css @@ -0,0 +1,44 @@ +.header { + position: sticky; + top: 0; + background-color: white; + width: 100%; + height: 70px; + padding: 0 200px; + display: flex; + justify-content: space-between; + gap: 32px; + border-bottom: 1px solid #dfdfdf; +} + +.nav { + display: flex; + flex-grow: 1; + align-items: center; +} + +.navLink { + padding: 21px 15px; + text-decoration: none; + color: var(--gray-600); +} + +.active { + color: #3692ff; +} + +.profile { + margin-left: auto; +} + +@media (max-width: 1024px) { + .header { + padding: 0 24px; + } +} + +@media (max-width: 768px) { + .header { + padding: 0 16px; + } +} diff --git a/src/pages/items/styles/items-products.module.css b/src/pages/items/styles/items-products.module.css new file mode 100644 index 00000000..53b933b0 --- /dev/null +++ b/src/pages/items/styles/items-products.module.css @@ -0,0 +1,97 @@ +.allProducts { + margin: 0 auto; + margin-top: 40px; + padding-bottom: 40px; + max-width: 1200px; +} + +.allProductsNav { + display: flex; + align-items: center; + gap: 12px; +} + +.allProductsTitle { + flex-grow: 1; + composes: font-xl font-bold from global; +} + +.allProductsSearchInput { + width: 325px; + height: 42px; + padding: 9px 20px; + padding-left: 44px; + border: none; + border-radius: 12px; + background-color: var(--gray-100); + composes: font-lg font-regular from global; +} + +.allProductsSearchInput::placeholder { + color: var(--gray-400); +} + +.addProduct { + width: auto; + height: 42px; + padding: 12px 23px; + border: none; + border-radius: 8px; + background-color: var(--primary-100); + text-decoration: none; + display: flex; + align-items: center; + color: var(--gray-100); + composes: font-lg font-semibold from global; +} + +.relativeDiv { + position: relative; +} + +.icSearch { + position: absolute; + width: 24px; + height: 24px; + top: 9px; + left: 16px; +} + +.filter { + appearance: none; /* 표준 */ + -webkit-appearance: none; /* 사파리/크롬 */ + -moz-appearance: none; /* 파이어폭스 */ + + width: 130px; + height: 42px; + padding: 12px 20px; + border: 1px solid var(--gray-200); + border-radius: 12px; +} +.filterOption { + width: 130px; + height: 42px; + border-radius: 12px 12px 0 0; +} +.allProductsList { + margin-top: 24px; + display: grid; + grid-template-columns: repeat(5, 1fr); + grid-template-rows: repeat(2, 1fr); + gap: 24px; +} + +@media (max-width: 1023px) { + .allProductsList { + grid-template-columns: repeat(3, 1fr); + grid-template-rows: repeat(2, 1fr); + gap: 16px; + } +} +@media (max-width: 768px) { + .allProductsList { + grid-template-columns: repeat(2, 1fr); + grid-template-rows: repeat(2, 1fr); + gap: 8px; + } +} diff --git a/src/pages/items/styles/items.module.css b/src/pages/items/styles/items.module.css new file mode 100644 index 00000000..85c746af --- /dev/null +++ b/src/pages/items/styles/items.module.css @@ -0,0 +1,9 @@ +main { + padding: 0 15px; +} + +@media (min-width: 768px) { + main { + padding: 0 24px; + } +} diff --git a/src/pages/login/login.module.css b/src/pages/login/login.module.css new file mode 100644 index 00000000..ebaf910c --- /dev/null +++ b/src/pages/login/login.module.css @@ -0,0 +1,15 @@ +.login__form { + max-width: 640px; + padding: 80px 16px 0px; + margin: 0 auto; + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; +} + +@media (min-width: 768px) { + .login__form { + padding: 190px 52px 0px; + } +} diff --git a/src/pages/login/login.tsx b/src/pages/login/login.tsx new file mode 100644 index 00000000..89d15166 --- /dev/null +++ b/src/pages/login/login.tsx @@ -0,0 +1,21 @@ +import Input from "@/components/input"; +import Logo from "@/components/logo"; +import SubmitButton from "@/components/submit-button"; +import styles from "./login.module.css"; + +export default function Login() { + return ( +
    + + + + + + + + + + 로그인 + + ); +} diff --git a/src/pages/sign-up/sign-up.module.css b/src/pages/sign-up/sign-up.module.css new file mode 100644 index 00000000..e09ef375 --- /dev/null +++ b/src/pages/sign-up/sign-up.module.css @@ -0,0 +1,15 @@ +.sign-up__form { + max-width: 640px; + padding: 24px 16px 0px; + margin: 0 auto; + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; +} + +@media (min-width: 768px) { + .sign-up__form { + padding: 48px 52px 0px; + } +} diff --git a/src/pages/sign-up/sign-up.tsx b/src/pages/sign-up/sign-up.tsx new file mode 100644 index 00000000..8a2d0056 --- /dev/null +++ b/src/pages/sign-up/sign-up.tsx @@ -0,0 +1,33 @@ +import Input from "@/components/input"; +import Logo from "@/components/logo"; +import SimpleAuth from "@/components/simple-auth"; +import SubmitButton from "@/components/submit-button"; +import styles from "./sign-up.module.css"; + +export default function SignUp() { + return ( + <> +
    + + + + + + + + + + + + + + + + + + 회원가입 + + + + ); +} diff --git a/src/styles/fonts.css b/src/styles/fonts.css new file mode 100644 index 00000000..c598e948 --- /dev/null +++ b/src/styles/fonts.css @@ -0,0 +1,54 @@ +.font-3xl { + font-size: 32px; + line-height: 42px; +} + +.font-2xl { + font-size: 24px; + line-height: 32px; +} + +.font-xl { + font-size: 20px; + line-height: 32px; +} +.font-2lg { + font-size: 18px; + line-height: 26px; +} + +.font-lg { + font-size: 16px; + line-height: 26px; +} + +.font-md { + font-size: 14px; + line-height: 24px; +} + +.font-sm { + font-size: 13px; + line-height: 22px; +} + +.font-xs { + font-size: 12px; + line-height: 18px; +} + +.font-bold { + font-weight: 700; +} + +.font-semibold { + font-weight: 600; +} + +.font-medium { + font-weight: 500; +} + +.font-regular { + font-weight: 400; +} diff --git a/src/styles/index.css b/src/styles/index.css new file mode 100644 index 00000000..9d34b298 --- /dev/null +++ b/src/styles/index.css @@ -0,0 +1,29 @@ +:root { + /* Primary color */ + --primary-100: #3692ff; + --primary-200: #1967d6; + --primary-300: #1251aa; + + /* Secondary color */ + --gray-900: #111827; /* gray900 */ + --gray-800: #1f2937; /* gray800 */ + --gray-700: #374151; /* gray700 */ + --gray-600: #4b5563; /* gray600 */ + --gray-500: #6b7280; /* gray500 */ + --gray-400: #9ca3af; /* gray400 */ + --gray-200: #e5e7eb; /* gray200 */ + --gray-100: #f3f4f6; /* gray100 */ + --gray-50: #f9fafb; /* gray50 */ + + /* Error */ + --error: #f74747; +} +* { + box-sizing: border-box; +} + +body { + font-family: "Pretendard Variable", Pretendard, -apple-system, + BlinkMacSystemFont, system-ui, Roboto, "Helvetica Neue", "Segoe UI", + "Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic", sans-serif; +} diff --git a/src/styles/reset.css b/src/styles/reset.css new file mode 100644 index 00000000..91648d3d --- /dev/null +++ b/src/styles/reset.css @@ -0,0 +1,44 @@ +/* reset.css - Eric Meyer’s Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/) */ +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} +body { + line-height: 1; +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} diff --git a/src/utils/input-validate.ts b/src/utils/input-validate.ts new file mode 100644 index 00000000..98a3a508 --- /dev/null +++ b/src/utils/input-validate.ts @@ -0,0 +1,28 @@ +import { inputType } from "@/components/input"; + +const inputValidate = ( + type: inputType, + inputValue: string, + regex?: RegExp +): boolean => { + // 빈 입력 값은 false 처리 + if (!inputValue) return false; + + switch (type) { + case "email": + // regex가 주어지면 해당 regex를 사용, 없으면 기본 이메일 형식 검증 + return regex + ? regex.test(inputValue) + : /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(inputValue); + + case "password": + // 예시: 비밀번호는 최소 6자리 이상 + return inputValue.length >= 8; + + default: + // type에 따른 특수 검증이 없다면, regex가 있으면 검증, 없으면 true 반환 + return regex ? regex.test(inputValue) : true; + } +}; + +export default inputValidate; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 00000000..97a2ad7a --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"], + "@components/*": ["./src/components/*"], + "@assets/*": ["./src/assets/*"] + }, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..1ffef600 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 00000000..db0becc8 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 00000000..45f06a5d --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import tsconfigPaths from "vite-tsconfig-paths"; + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react(), tsconfigPaths()], +});