From f08fa755e4078b6bc609db9e24c782a767d70c28 Mon Sep 17 00:00:00 2001 From: geg222 Date: Mon, 2 Jun 2025 15:55:38 +0900 Subject: [PATCH] feat: Create Week10 Mission1 feat: Create Week10 Mission1 --- Week10/geg222/Mission1/.gitignore | 26 + Week10/geg222/Mission1/README.md | 54 + Week10/geg222/Mission1/eslint.config.js | 28 + Week10/geg222/Mission1/images/google.png | Bin 0 -> 33647 bytes Week10/geg222/Mission1/index.html | 13 + Week10/geg222/Mission1/package.json | 37 + Week10/geg222/Mission1/pnpm-lock.yaml | 2354 +++++++++++++++++ Week10/geg222/Mission1/public/vite.svg | 1 + Week10/geg222/Mission1/src/App.css | 14 + Week10/geg222/Mission1/src/App.tsx | 89 + Week10/geg222/Mission1/src/apis/auth.ts | 30 + Week10/geg222/Mission1/src/apis/axios.ts | 59 + .../geg222/Mission1/src/apis/axiosClient.ts | 8 + .../Mission1/src/apis/refreshTokenManager.ts | 37 + Week10/geg222/Mission1/src/assets/react.svg | 1 + .../geg222/Mission1/src/components/Input.tsx | 26 + .../Mission1/src/components/IsError.tsx | 12 + .../src/components/LanguageSelector.tsx | 34 + .../src/components/LoadingSpinner.tsx | 12 + .../Mission1/src/components/MovieCard.tsx | 52 + .../Mission1/src/components/MovieFilter.tsx | 78 + .../Mission1/src/components/MovieHeader.tsx | 33 + .../Mission1/src/components/MovieList.tsx | 28 + .../MovieModal/MovieDetailContent.tsx | 52 + .../MovieModal/MovieDetailModal.tsx | 54 + .../src/components/MoviePeopleGrid.tsx | 33 + .../geg222/Mission1/src/components/Navbar.tsx | 60 + .../Mission1/src/components/SelectBox.tsx | 30 + Week10/geg222/Mission1/src/constants/key.ts | 4 + Week10/geg222/Mission1/src/constants/movie.ts | 5 + .../Mission1/src/context/AuthContext.tsx | 83 + .../Mission1/src/hooks/useCustomFetch.ts | 39 + Week10/geg222/Mission1/src/hooks/useFetch.ts | 38 + Week10/geg222/Mission1/src/hooks/useForm.ts | 44 + .../Mission1/src/hooks/useLocalStorage.ts | 29 + Week10/geg222/Mission1/src/index.css | 1 + .../geg222/Mission1/src/layout/HomeLayout.tsx | 18 + .../Mission1/src/layout/ProtectedLayout.tsx | 16 + Week10/geg222/Mission1/src/main.tsx | 9 + .../src/pages/GoogleLoginRedirectPage.tsx | 28 + Week10/geg222/Mission1/src/pages/Homepage.tsx | 41 + .../geg222/Mission1/src/pages/LoginPage.tsx | 155 ++ .../Mission1/src/pages/MovieDetailPage.tsx | 52 + .../geg222/Mission1/src/pages/MoviePage.tsx | 94 + Week10/geg222/Mission1/src/pages/Mypage.tsx | 53 + .../Mission1/src/pages/NotFoundPage.tsx | 16 + .../geg222/Mission1/src/pages/SearchPage.tsx | 63 + .../geg222/Mission1/src/pages/SignupPage.tsx | 304 +++ Week10/geg222/Mission1/src/types/auth.ts | 44 + Week10/geg222/Mission1/src/types/common.ts | 6 + Week10/geg222/Mission1/src/types/movie.ts | 57 + Week10/geg222/Mission1/src/utils/validate.ts | 33 + Week10/geg222/Mission1/src/vite-env.d.ts | 10 + Week10/geg222/Mission1/tsconfig.app.json | 26 + Week10/geg222/Mission1/tsconfig.json | 7 + Week10/geg222/Mission1/tsconfig.node.json | 24 + Week10/geg222/Mission1/vite.config.ts | 8 + 57 files changed, 4562 insertions(+) create mode 100644 Week10/geg222/Mission1/.gitignore create mode 100644 Week10/geg222/Mission1/README.md create mode 100644 Week10/geg222/Mission1/eslint.config.js create mode 100644 Week10/geg222/Mission1/images/google.png create mode 100644 Week10/geg222/Mission1/index.html create mode 100644 Week10/geg222/Mission1/package.json create mode 100644 Week10/geg222/Mission1/pnpm-lock.yaml create mode 100644 Week10/geg222/Mission1/public/vite.svg create mode 100644 Week10/geg222/Mission1/src/App.css create mode 100644 Week10/geg222/Mission1/src/App.tsx create mode 100644 Week10/geg222/Mission1/src/apis/auth.ts create mode 100644 Week10/geg222/Mission1/src/apis/axios.ts create mode 100644 Week10/geg222/Mission1/src/apis/axiosClient.ts create mode 100644 Week10/geg222/Mission1/src/apis/refreshTokenManager.ts create mode 100644 Week10/geg222/Mission1/src/assets/react.svg create mode 100644 Week10/geg222/Mission1/src/components/Input.tsx create mode 100644 Week10/geg222/Mission1/src/components/IsError.tsx create mode 100644 Week10/geg222/Mission1/src/components/LanguageSelector.tsx create mode 100644 Week10/geg222/Mission1/src/components/LoadingSpinner.tsx create mode 100644 Week10/geg222/Mission1/src/components/MovieCard.tsx create mode 100644 Week10/geg222/Mission1/src/components/MovieFilter.tsx create mode 100644 Week10/geg222/Mission1/src/components/MovieHeader.tsx create mode 100644 Week10/geg222/Mission1/src/components/MovieList.tsx create mode 100644 Week10/geg222/Mission1/src/components/MovieModal/MovieDetailContent.tsx create mode 100644 Week10/geg222/Mission1/src/components/MovieModal/MovieDetailModal.tsx create mode 100644 Week10/geg222/Mission1/src/components/MoviePeopleGrid.tsx create mode 100644 Week10/geg222/Mission1/src/components/Navbar.tsx create mode 100644 Week10/geg222/Mission1/src/components/SelectBox.tsx create mode 100644 Week10/geg222/Mission1/src/constants/key.ts create mode 100644 Week10/geg222/Mission1/src/constants/movie.ts create mode 100644 Week10/geg222/Mission1/src/context/AuthContext.tsx create mode 100644 Week10/geg222/Mission1/src/hooks/useCustomFetch.ts create mode 100644 Week10/geg222/Mission1/src/hooks/useFetch.ts create mode 100644 Week10/geg222/Mission1/src/hooks/useForm.ts create mode 100644 Week10/geg222/Mission1/src/hooks/useLocalStorage.ts create mode 100644 Week10/geg222/Mission1/src/index.css create mode 100644 Week10/geg222/Mission1/src/layout/HomeLayout.tsx create mode 100644 Week10/geg222/Mission1/src/layout/ProtectedLayout.tsx create mode 100644 Week10/geg222/Mission1/src/main.tsx create mode 100644 Week10/geg222/Mission1/src/pages/GoogleLoginRedirectPage.tsx create mode 100644 Week10/geg222/Mission1/src/pages/Homepage.tsx create mode 100644 Week10/geg222/Mission1/src/pages/LoginPage.tsx create mode 100644 Week10/geg222/Mission1/src/pages/MovieDetailPage.tsx create mode 100644 Week10/geg222/Mission1/src/pages/MoviePage.tsx create mode 100644 Week10/geg222/Mission1/src/pages/Mypage.tsx create mode 100644 Week10/geg222/Mission1/src/pages/NotFoundPage.tsx create mode 100644 Week10/geg222/Mission1/src/pages/SearchPage.tsx create mode 100644 Week10/geg222/Mission1/src/pages/SignupPage.tsx create mode 100644 Week10/geg222/Mission1/src/types/auth.ts create mode 100644 Week10/geg222/Mission1/src/types/common.ts create mode 100644 Week10/geg222/Mission1/src/types/movie.ts create mode 100644 Week10/geg222/Mission1/src/utils/validate.ts create mode 100644 Week10/geg222/Mission1/src/vite-env.d.ts create mode 100644 Week10/geg222/Mission1/tsconfig.app.json create mode 100644 Week10/geg222/Mission1/tsconfig.json create mode 100644 Week10/geg222/Mission1/tsconfig.node.json create mode 100644 Week10/geg222/Mission1/vite.config.ts diff --git a/Week10/geg222/Mission1/.gitignore b/Week10/geg222/Mission1/.gitignore new file mode 100644 index 00000000..3b0b4037 --- /dev/null +++ b/Week10/geg222/Mission1/.gitignore @@ -0,0 +1,26 @@ +# 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? + +.env \ No newline at end of file diff --git a/Week10/geg222/Mission1/README.md b/Week10/geg222/Mission1/README.md new file mode 100644 index 00000000..40ede56e --- /dev/null +++ b/Week10/geg222/Mission1/README.md @@ -0,0 +1,54 @@ +# 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/Week10/geg222/Mission1/eslint.config.js b/Week10/geg222/Mission1/eslint.config.js new file mode 100644 index 00000000..092408a9 --- /dev/null +++ b/Week10/geg222/Mission1/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/Week10/geg222/Mission1/images/google.png b/Week10/geg222/Mission1/images/google.png new file mode 100644 index 0000000000000000000000000000000000000000..36875953fd871178d1a762e8055e210e0f769b42 GIT binary patch literal 33647 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 X2EgUrC6G8#00000NkvXXu0mjf`77j| literal 0 HcmV?d00001 diff --git a/Week10/geg222/Mission1/index.html b/Week10/geg222/Mission1/index.html new file mode 100644 index 00000000..e4b78eae --- /dev/null +++ b/Week10/geg222/Mission1/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/Week10/geg222/Mission1/package.json b/Week10/geg222/Mission1/package.json new file mode 100644 index 00000000..34ead33e --- /dev/null +++ b/Week10/geg222/Mission1/package.json @@ -0,0 +1,37 @@ +{ + "name": "package.json", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@hookform/resolvers": "^5.0.1", + "@tailwindcss/vite": "^4.1.3", + "axios": "^1.8.4", + "js-cookie": "^3.0.5", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-hook-form": "^7.55.0", + "react-router-dom": "^7.5.0", + "tailwindcss": "^4.1.3", + "zod": "^3.24.2" + }, + "devDependencies": { + "@eslint/js": "^9.21.0", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react-swc": "^3.8.0", + "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" + } +} diff --git a/Week10/geg222/Mission1/pnpm-lock.yaml b/Week10/geg222/Mission1/pnpm-lock.yaml new file mode 100644 index 00000000..b2854b5a --- /dev/null +++ b/Week10/geg222/Mission1/pnpm-lock.yaml @@ -0,0 +1,2354 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@hookform/resolvers': + specifier: ^5.0.1 + version: 5.0.1(react-hook-form@7.55.0(react@19.1.0)) + '@tailwindcss/vite': + specifier: ^4.1.3 + version: 4.1.3(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2)) + axios: + specifier: ^1.8.4 + version: 1.8.4 + js-cookie: + specifier: ^3.0.5 + version: 3.0.5 + react: + specifier: ^19.0.0 + version: 19.1.0 + react-dom: + specifier: ^19.0.0 + version: 19.1.0(react@19.1.0) + react-hook-form: + specifier: ^7.55.0 + version: 7.55.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) + tailwindcss: + specifier: ^4.1.3 + version: 4.1.3 + zod: + specifier: ^3.24.2 + version: 3.24.2 + 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-swc': + specifier: ^3.8.0 + version: 3.8.1(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2)) + eslint: + specifier: ^9.21.0 + version: 9.24.0(jiti@2.4.2) + eslint-plugin-react-hooks: + specifier: ^5.1.0 + version: 5.2.0(eslint@9.24.0(jiti@2.4.2)) + eslint-plugin-react-refresh: + specifier: ^0.4.19 + version: 0.4.19(eslint@9.24.0(jiti@2.4.2)) + 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(jiti@2.4.2))(typescript@5.7.3) + vite: + specifier: ^6.2.0 + version: 6.2.5(jiti@2.4.2)(lightningcss@1.29.2) + +packages: + + '@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} + + '@hookform/resolvers@5.0.1': + resolution: {integrity: sha512-u/+Jp83luQNx9AdyW2fIPGY6Y7NG68eN2ZW8FOJYL+M0i4s49+refdJdOp/A9n9HFQtQs3HIDHQvX3ZET2o7YA==} + peerDependencies: + react-hook-form: ^7.55.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'} + + '@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] + + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + + '@swc/core-darwin-arm64@1.11.18': + resolution: {integrity: sha512-K6AntdUlNMQg8aChqjeXwnVhK6d4WRZ9TgtLSTmdU0Ugll4an7QK49s9NrT7XQU91cEsVvzdr++p1bNImx0hJg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.11.18': + resolution: {integrity: sha512-RCRvC6Q9M5BArTvj/IzUAAYGrgxYFbTTnAtf6UX7JFq2DAn+hEwYUjmC1m0gFso9HqFU0m5QZUGfZvVmACGWUw==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.11.18': + resolution: {integrity: sha512-wteAKf8YKb3jOnZFm3EzuIMzzCVXMuQOLHsz1IgEOc44/gdgNXKxaYTWAowZuej7t68tf/w0cRNMc7Le414v/g==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.11.18': + resolution: {integrity: sha512-hY6jJYZ6PKHSBo5OATswfyKsUgsWu9+4nDcN8liYIRRgz3E0G9wk0VUTP4cFPivBFeHWTTAGz687/Nf2aQEIpw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-arm64-musl@1.11.18': + resolution: {integrity: sha512-slu0mlP2nucvQalttnapfpqpD/LlM9NHx9g3ofgsLzjObyMEBiX4ZysQ3y65U8Mjw71RNqtLd/ZmvxI6OmLdiQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-x64-gnu@1.11.18': + resolution: {integrity: sha512-h9a/8PA25arMCQ9t8CE8rA1s0c77z4kCZZ7dUuUkD88yEXIrARMca1IKR7of+S3slfQrf1Zlq3Ac1Fb1HVJziQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-linux-x64-musl@1.11.18': + resolution: {integrity: sha512-0sMDJj5qUGK9QEw4lrxLxkTP/4AoKciqNzXvqbk+J9XuXN2aIv4BsR1Y7z3GwAeMFGsba2lbHLOtJlDsaqIsiA==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-win32-arm64-msvc@1.11.18': + resolution: {integrity: sha512-zGv9HnfgBcKyt54MJRWdwRNu9BuYkAFM7bx+tWtKhd37Ef7ZX20QLs9xXl5wWDXCbsOdRxXIZgXs6PEL+Pzmrw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.11.18': + resolution: {integrity: sha512-uBKj0S1lYv/E2ZhxHZOxSiQwoegYmzbPRpjq6eHBZDv97mu7W3K27/lsnPbvAfQ6b6rnv8BI+EsmJ7VLQBAHBQ==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.11.18': + resolution: {integrity: sha512-8USTRcdgeFMNBgvVXl8tz6n4+9s9m+zHsfDeBT4jPgwnq2bnLBlTUlwnPwzDxfg9nUJr6RFD4xeKfWyZZRosZg==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.11.18': + resolution: {integrity: sha512-ORZxyCKKiqYt2iHdh1C7pfVR1GBjkuFOdwqZggQzaq0vt22DpGca+2JsUtkUoWQmWcct04v5+ScwgvsHuMObxA==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '*' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/types@0.1.21': + resolution: {integrity: sha512-2YEtj5HJVbKivud9N4bpPBAyZhj4S2Ipe5LkUG94alTpr7in/GU/EARgPAd3BwU+YOmFVJC2+kjqhGRi3r0ZpQ==} + + '@tailwindcss/node@4.1.3': + resolution: {integrity: sha512-H/6r6IPFJkCfBJZ2dKZiPJ7Ueb2wbL592+9bQEl2r73qbX6yGnmQVIfiUvDRB2YI0a3PWDrzUwkvQx1XW1bNkA==} + + '@tailwindcss/oxide-android-arm64@4.1.3': + resolution: {integrity: sha512-cxklKjtNLwFl3mDYw4XpEfBY+G8ssSg9ADL4Wm6//5woi3XGqlxFsnV5Zb6v07dxw1NvEX2uoqsxO/zWQsgR+g==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.3': + resolution: {integrity: sha512-mqkf2tLR5VCrjBvuRDwzKNShRu99gCAVMkVsaEOFvv6cCjlEKXRecPu9DEnxp6STk5z+Vlbh1M5zY3nQCXMXhw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.3': + resolution: {integrity: sha512-7sGraGaWzXvCLyxrc7d+CCpUN3fYnkkcso3rCzwUmo/LteAl2ZGCDlGvDD8Y/1D3ngxT8KgDj1DSwOnNewKhmg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.3': + resolution: {integrity: sha512-E2+PbcbzIReaAYZe997wb9rId246yDkCwAakllAWSGqe6VTg9hHle67hfH6ExjpV2LSK/siRzBUs5wVff3RW9w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.3': + resolution: {integrity: sha512-GvfbJ8wjSSjbLFFE3UYz4Eh8i4L6GiEYqCtA8j2Zd2oXriPuom/Ah/64pg/szWycQpzRnbDiJozoxFU2oJZyfg==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.3': + resolution: {integrity: sha512-35UkuCWQTeG9BHcBQXndDOrpsnt3Pj9NVIB4CgNiKmpG8GnCNXeMczkUpOoqcOhO6Cc/mM2W7kaQ/MTEENDDXg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.3': + resolution: {integrity: sha512-dm18aQiML5QCj9DQo7wMbt1Z2tl3Giht54uVR87a84X8qRtuXxUqnKQkRDK5B4bCOmcZ580lF9YcoMkbDYTXHQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.3': + resolution: {integrity: sha512-LMdTmGe/NPtGOaOfV2HuO7w07jI3cflPrVq5CXl+2O93DCewADK0uW1ORNAcfu2YxDUS035eY2W38TxrsqngxA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.3': + resolution: {integrity: sha512-aalNWwIi54bbFEizwl1/XpmdDrOaCjRFQRgtbv9slWjmNPuJJTIKPHf5/XXDARc9CneW9FkSTqTbyvNecYAEGw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.3': + resolution: {integrity: sha512-PEj7XR4OGTGoboTIAdXicKuWl4EQIjKHKuR+bFy9oYN7CFZo0eu74+70O4XuERX4yjqVZGAkCdglBODlgqcCXg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.3': + resolution: {integrity: sha512-T8gfxECWDBENotpw3HR9SmNiHC9AOJdxs+woasRZ8Q/J4VHN0OMs7F+4yVNZ9EVN26Wv6mZbK0jv7eHYuLJLwA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.3': + resolution: {integrity: sha512-t16lpHCU7LBxDe/8dCj9ntyNpXaSTAgxWm1u2XQP5NiIu4KGSyrDJJRlK9hJ4U9yJxx0UKCVI67MJWFNll5mOQ==} + engines: {node: '>= 10'} + + '@tailwindcss/vite@4.1.3': + resolution: {integrity: sha512-lUI/QaDxLtlV52Lho6pu07CG9pSnRYLOPmKGIQjyHdTBagemc6HmgZxyjGAQ/5HMPrNeWBfTVIpQl0/jLXvWHQ==} + peerDependencies: + vite: ^5.2.0 || ^6 + + '@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-swc@3.8.1': + resolution: {integrity: sha512-aEUPCckHDcFyxpwFm0AIkbtv6PpUp3xTb9wYGFjtABynXjCYKkWoxX0AOK9NT9XCrdk6mBBUOeHQS+RKdcNO1A==} + peerDependencies: + vite: ^4 || ^5 || ^6 + + 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==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.8.4: + resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==} + + 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'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + 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==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + 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==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + enhanced-resolve@5.18.1: + resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} + engines: {node: '>=10.13.0'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + esbuild@0.25.2: + resolution: {integrity: sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==} + engines: {node: '>=18'} + hasBin: true + + 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==} + + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.2: + resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} + engines: {node: '>= 6'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + 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@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'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + 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==} + + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + hasBin: true + + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + 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==} + + 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'} + + lightningcss-darwin-arm64@1.29.2: + resolution: {integrity: sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.29.2: + resolution: {integrity: sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.29.2: + resolution: {integrity: sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.29.2: + resolution: {integrity: sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.29.2: + resolution: {integrity: sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.29.2: + resolution: {integrity: sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.29.2: + resolution: {integrity: sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.29.2: + resolution: {integrity: sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.29.2: + resolution: {integrity: sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.29.2: + resolution: {integrity: sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.29.2: + resolution: {integrity: sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==} + engines: {node: '>= 12.0.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==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + 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'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.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==} + + 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'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + 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-hook-form@7.55.0: + resolution: {integrity: sha512-XRnjsH3GVMQz1moZTW53MxfoWN7aDpUg/GpVNc4A3eXRVNdGXfbzJ4vM4aLQ8g6XCUh1nIbx70aaNCl7kxnjog==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + + 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@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'} + + tailwindcss@4.1.3: + resolution: {integrity: sha512-2Q+rw9vy1WFXu5cIxlvsabCwhU2qUwodGq03ODhLJ0jW4ek5BUtoCsnLB0qG+m8AHgEsSJcJGDSDe06FXlP74g==} + + tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + + 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' + + 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 + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + 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'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod@3.24.2: + resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} + +snapshots: + + '@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(jiti@2.4.2))': + dependencies: + eslint: 9.24.0(jiti@2.4.2) + 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 + + '@hookform/resolvers@5.0.1(react-hook-form@7.55.0(react@19.1.0))': + dependencies: + '@standard-schema/utils': 0.3.0 + react-hook-form: 7.55.0(react@19.1.0) + + '@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': {} + + '@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 + + '@standard-schema/utils@0.3.0': {} + + '@swc/core-darwin-arm64@1.11.18': + optional: true + + '@swc/core-darwin-x64@1.11.18': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.11.18': + optional: true + + '@swc/core-linux-arm64-gnu@1.11.18': + optional: true + + '@swc/core-linux-arm64-musl@1.11.18': + optional: true + + '@swc/core-linux-x64-gnu@1.11.18': + optional: true + + '@swc/core-linux-x64-musl@1.11.18': + optional: true + + '@swc/core-win32-arm64-msvc@1.11.18': + optional: true + + '@swc/core-win32-ia32-msvc@1.11.18': + optional: true + + '@swc/core-win32-x64-msvc@1.11.18': + optional: true + + '@swc/core@1.11.18': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.21 + optionalDependencies: + '@swc/core-darwin-arm64': 1.11.18 + '@swc/core-darwin-x64': 1.11.18 + '@swc/core-linux-arm-gnueabihf': 1.11.18 + '@swc/core-linux-arm64-gnu': 1.11.18 + '@swc/core-linux-arm64-musl': 1.11.18 + '@swc/core-linux-x64-gnu': 1.11.18 + '@swc/core-linux-x64-musl': 1.11.18 + '@swc/core-win32-arm64-msvc': 1.11.18 + '@swc/core-win32-ia32-msvc': 1.11.18 + '@swc/core-win32-x64-msvc': 1.11.18 + + '@swc/counter@0.1.3': {} + + '@swc/types@0.1.21': + dependencies: + '@swc/counter': 0.1.3 + + '@tailwindcss/node@4.1.3': + dependencies: + enhanced-resolve: 5.18.1 + jiti: 2.4.2 + lightningcss: 1.29.2 + tailwindcss: 4.1.3 + + '@tailwindcss/oxide-android-arm64@4.1.3': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.3': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.3': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.3': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.3': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.3': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.3': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.3': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.3': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.3': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.3': + optional: true + + '@tailwindcss/oxide@4.1.3': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.3 + '@tailwindcss/oxide-darwin-arm64': 4.1.3 + '@tailwindcss/oxide-darwin-x64': 4.1.3 + '@tailwindcss/oxide-freebsd-x64': 4.1.3 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.3 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.3 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.3 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.3 + '@tailwindcss/oxide-linux-x64-musl': 4.1.3 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.3 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.3 + + '@tailwindcss/vite@4.1.3(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2))': + dependencies: + '@tailwindcss/node': 4.1.3 + '@tailwindcss/oxide': 4.1.3 + tailwindcss: 4.1.3 + vite: 6.2.5(jiti@2.4.2)(lightningcss@1.29.2) + + '@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(jiti@2.4.2))(typescript@5.7.3))(eslint@9.24.0(jiti@2.4.2))(typescript@5.7.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.29.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.7.3) + '@typescript-eslint/scope-manager': 8.29.0 + '@typescript-eslint/type-utils': 8.29.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.7.3) + '@typescript-eslint/utils': 8.29.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.29.0 + eslint: 9.24.0(jiti@2.4.2) + 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(jiti@2.4.2))(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(jiti@2.4.2) + 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(jiti@2.4.2))(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(jiti@2.4.2))(typescript@5.7.3) + debug: 4.4.0 + eslint: 9.24.0(jiti@2.4.2) + 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(jiti@2.4.2))(typescript@5.7.3)': + dependencies: + '@eslint-community/eslint-utils': 4.5.1(eslint@9.24.0(jiti@2.4.2)) + '@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(jiti@2.4.2) + 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-swc@3.8.1(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2))': + dependencies: + '@swc/core': 1.11.18 + vite: 6.2.5(jiti@2.4.2)(lightningcss@1.29.2) + transitivePeerDependencies: + - '@swc/helpers' + + 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: {} + + asynckit@0.4.0: {} + + axios@1.8.4: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.2 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + 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 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + callsites@3.1.0: {} + + 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: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + concat-map@0.0.1: {} + + 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: {} + + delayed-stream@1.0.0: {} + + detect-libc@2.0.3: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + enhanced-resolve@5.18.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + 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 + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@5.2.0(eslint@9.24.0(jiti@2.4.2)): + dependencies: + eslint: 9.24.0(jiti@2.4.2) + + eslint-plugin-react-refresh@0.4.19(eslint@9.24.0(jiti@2.4.2)): + dependencies: + eslint: 9.24.0(jiti@2.4.2) + + 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(jiti@2.4.2): + dependencies: + '@eslint-community/eslint-utils': 4.5.1(eslint@9.24.0(jiti@2.4.2)) + '@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 + optionalDependencies: + jiti: 2.4.2 + 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: {} + + follow-redirects@1.15.9: {} + + form-data@4.0.2: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + mime-types: 2.1.35 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + globals@15.15.0: {} + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + 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: {} + + jiti@2.4.2: {} + + js-cookie@3.0.5: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + 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 + + lightningcss-darwin-arm64@1.29.2: + optional: true + + lightningcss-darwin-x64@1.29.2: + optional: true + + lightningcss-freebsd-x64@1.29.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.29.2: + optional: true + + lightningcss-linux-arm64-gnu@1.29.2: + optional: true + + lightningcss-linux-arm64-musl@1.29.2: + optional: true + + lightningcss-linux-x64-gnu@1.29.2: + optional: true + + lightningcss-linux-x64-musl@1.29.2: + optional: true + + lightningcss-win32-arm64-msvc@1.29.2: + optional: true + + lightningcss-win32-x64-msvc@1.29.2: + optional: true + + lightningcss@1.29.2: + dependencies: + detect-libc: 2.0.3 + optionalDependencies: + lightningcss-darwin-arm64: 1.29.2 + lightningcss-darwin-x64: 1.29.2 + lightningcss-freebsd-x64: 1.29.2 + lightningcss-linux-arm-gnueabihf: 1.29.2 + lightningcss-linux-arm64-gnu: 1.29.2 + lightningcss-linux-arm64-musl: 1.29.2 + lightningcss-linux-x64-gnu: 1.29.2 + lightningcss-linux-x64-musl: 1.29.2 + lightningcss-win32-arm64-msvc: 1.29.2 + lightningcss-win32-x64-msvc: 1.29.2 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + math-intrinsics@1.1.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + 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: {} + + 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: {} + + proxy-from-env@1.1.0: {} + + 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-hook-form@7.55.0(react@19.1.0): + dependencies: + react: 19.1.0 + + 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@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 + + tailwindcss@4.1.3: {} + + tapable@2.2.1: {} + + 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 + + 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(jiti@2.4.2))(typescript@5.7.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.24.0(jiti@2.4.2))(typescript@5.7.3) + '@typescript-eslint/parser': 8.29.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.7.3) + '@typescript-eslint/utils': 8.29.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.7.3) + eslint: 9.24.0(jiti@2.4.2) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + typescript@5.7.3: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2): + dependencies: + esbuild: 0.25.2 + postcss: 8.5.3 + rollup: 4.39.0 + optionalDependencies: + fsevents: 2.3.3 + jiti: 2.4.2 + lightningcss: 1.29.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + yocto-queue@0.1.0: {} + + zod@3.24.2: {} diff --git a/Week10/geg222/Mission1/public/vite.svg b/Week10/geg222/Mission1/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/Week10/geg222/Mission1/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Week10/geg222/Mission1/src/App.css b/Week10/geg222/Mission1/src/App.css new file mode 100644 index 00000000..788c99c0 --- /dev/null +++ b/Week10/geg222/Mission1/src/App.css @@ -0,0 +1,14 @@ +@keyframes zoomFadeIn { + 0% { + opacity: 0; + transform: scale(0.8); + } + 100% { + opacity: 1; + transform: scale(1); + } +} + +.zoom-fade { + animation: zoomFadeIn 1.5s ease-out forwards; +} \ No newline at end of file diff --git a/Week10/geg222/Mission1/src/App.tsx b/Week10/geg222/Mission1/src/App.tsx new file mode 100644 index 00000000..5260415e --- /dev/null +++ b/Week10/geg222/Mission1/src/App.tsx @@ -0,0 +1,89 @@ +import "./App.css"; +import Homepage from "./pages/Homepage"; +import MoviePage from "./pages/MoviePage"; +import { JSX } from "react"; +import { createBrowserRouter, RouteObject, RouterProvider } from "react-router-dom"; +import NotFoundPage from "./pages/NotFoundPage"; +import MovieDetailPage from "./pages/MovieDetailPage"; +import LoginPage from "./pages/LoginPage"; +import HomeLayout from "./layout/HomeLayout"; +import { SignupPage } from "./pages/SignupPage"; +import Mypage from "./pages/Mypage"; +import { AuthProvider } from "./context/AuthContext"; +import ProtectedLayout from "./layout/ProtectedLayout"; +import GoogleLoginRedirectPage from "./pages/GoogleLoginRedirectPage"; +import SearchPage from "./pages/SearchPage"; + + + +const publicRoutes:RouteObject[] = [ + { + path: "/", + element: , + errorElement: , + children: [ + { + path: "/", + element: , + }, + { + path: "movies/:category", + element: , + }, + { + path: "movie/:movieId", + element: , + }, + { + path: "login", + element: + }, + { + path: "signup", + element: + }, + { + path: "/v1/auth/google/callback", + element: + }, + { + path: "/search", + element: + } + ] + }, +]; + +const protectedRoutes: RouteObject[] = [ + { + path: "/", + element: , + errorElement: , + children: [ + { + path: "", + element: , + children: [ + { + path: "my", + element: , + } + ] + } + ] + } +] + + +const router = createBrowserRouter([...publicRoutes, ...protectedRoutes]); + + +function App(): JSX.Element { + return ( + + + + ); +} + +export default App; diff --git a/Week10/geg222/Mission1/src/apis/auth.ts b/Week10/geg222/Mission1/src/apis/auth.ts new file mode 100644 index 00000000..05a68135 --- /dev/null +++ b/Week10/geg222/Mission1/src/apis/auth.ts @@ -0,0 +1,30 @@ +import { RequestSigninDto, RequestSignupDto, ResponseMyInfoDto, ResponseSigninDto, ResponseSingupDto } from "../types/auth" +import { axiosInstance } from "./axios"; + +export const postSignup = async (body: RequestSignupDto): Promise => { + const { data } = await axiosInstance.post("/v1/auth/signup",body); + + return data; +}; + +export const postSignin = async (body: RequestSigninDto): Promise => { + const { data } = await axiosInstance.post("/v1/auth/signin",body); + + return data; +}; + +export const getMyInfo = async (): Promise => { + // const { getItem } = useLocalStorage(LOCAL_STORAGE_KEY.accessToken); + const { data } = await axiosInstance.get("/v1/users/me", { + headers: { + Authorization: `Bearer`, + }, + }); + + return data; +}; + +export const postLogout = async () => { + const {data} = await axiosInstance.post("/v1/auth/signout"); + return data; +}; \ No newline at end of file diff --git a/Week10/geg222/Mission1/src/apis/axios.ts b/Week10/geg222/Mission1/src/apis/axios.ts new file mode 100644 index 00000000..17ace3d5 --- /dev/null +++ b/Week10/geg222/Mission1/src/apis/axios.ts @@ -0,0 +1,59 @@ +import axios, { InternalAxiosRequestConfig } from "axios"; +import { LOCAL_STORAGE_KEY } from "../constants/key"; +import { useLocalStorage } from "../hooks/useLocalStorage"; +import { getRefreshPromise } from "./refreshTokenManager"; + +// 요청 인터셉터에 사용할 accessToken 설정 함수 +const attachAccessToken = (config: InternalAxiosRequestConfig) => { + const { getItem } = useLocalStorage(LOCAL_STORAGE_KEY.accessToken); + const accessToken = getItem(); + + if (accessToken) { + config.headers = config.headers || {}; + config.headers.Authorization = `Bearer ${accessToken}`; + } + + return config; +}; + +// 401 에러를 처리하는 함수 +const handleResponseError = async (error: any) => { + const originalRequest = error.config; + + if ( + error.response?.status === 401 && + !originalRequest._retry + ) { + if (originalRequest.url === "/v1/auth/refresh") { + const { removeItem: removeAccessToken } = useLocalStorage(LOCAL_STORAGE_KEY.accessToken); + removeAccessToken(); + const { removeItem: removeRefreshToken } = useLocalStorage(LOCAL_STORAGE_KEY.refreshToken); + removeRefreshToken(); + window.location.href = "/login"; + return Promise.reject(error); + } + + originalRequest._retry = true; + + const newAccessToken = await getRefreshPromise(); + if (newAccessToken) { + originalRequest.headers = { + ...(originalRequest.headers || {}), + Authorization: `Bearer ${newAccessToken}`, + }; + return axiosInstance.request(originalRequest); + } + } + + return Promise.reject(error); +}; + +// axios 인스턴스 생성 +export const axiosInstance = axios.create({ + baseURL: import.meta.env.VITE_SERVER_API_URL, + withCredentials: true, +}); + +// 인터셉터 등록 +axiosInstance.interceptors.request.use(attachAccessToken, (error) => Promise.reject(error)); +axiosInstance.interceptors.response.use((response) => response, handleResponseError); diff --git a/Week10/geg222/Mission1/src/apis/axiosClient.ts b/Week10/geg222/Mission1/src/apis/axiosClient.ts new file mode 100644 index 00000000..d4fdf86b --- /dev/null +++ b/Week10/geg222/Mission1/src/apis/axiosClient.ts @@ -0,0 +1,8 @@ +import axios from "axios"; + +export const axiosClient = axios.create({ + baseURL: "https://api.themoviedb.org/3", + headers: { + Authorization: `Bearer ${import.meta.env.VITE_TMDB_KEY}`, + }, +}); diff --git a/Week10/geg222/Mission1/src/apis/refreshTokenManager.ts b/Week10/geg222/Mission1/src/apis/refreshTokenManager.ts new file mode 100644 index 00000000..1e3f7ad4 --- /dev/null +++ b/Week10/geg222/Mission1/src/apis/refreshTokenManager.ts @@ -0,0 +1,37 @@ +import { axiosInstance } from "./axios"; +import { LOCAL_STORAGE_KEY } from "../constants/key"; +import { useLocalStorage } from "../hooks/useLocalStorage"; + +let refreshPromise: Promise | null = null; + +export const refreshToken = async () => { + const { getItem: getRefreshToken } = useLocalStorage(LOCAL_STORAGE_KEY.refreshToken); + const refreshToken = getRefreshToken(); + + const { data } = await axiosInstance.post("/v1/auth/refresh", { refresh: refreshToken }); + + const { setItem: setAccessToken } = useLocalStorage(LOCAL_STORAGE_KEY.accessToken); + setAccessToken(data.data.accessToken); + + const { setItem: setRefreshToken } = useLocalStorage(LOCAL_STORAGE_KEY.refreshToken); + setRefreshToken(data.data.refreshToken); + + return data.data.accessToken; +}; + +export const getRefreshPromise = () => { + if (!refreshPromise) { + refreshPromise = refreshToken() + .catch(() => { + const { removeItem: removeAccessToken } = useLocalStorage(LOCAL_STORAGE_KEY.accessToken); + removeAccessToken(); + const { removeItem: removeRefreshToken } = useLocalStorage(LOCAL_STORAGE_KEY.refreshToken); + removeRefreshToken(); + window.location.href = "/login"; + }) + .finally(() => { + refreshPromise = null; + }); + } + return refreshPromise; +}; \ No newline at end of file diff --git a/Week10/geg222/Mission1/src/assets/react.svg b/Week10/geg222/Mission1/src/assets/react.svg new file mode 100644 index 00000000..6c87de9b --- /dev/null +++ b/Week10/geg222/Mission1/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Week10/geg222/Mission1/src/components/Input.tsx b/Week10/geg222/Mission1/src/components/Input.tsx new file mode 100644 index 00000000..6a2e203a --- /dev/null +++ b/Week10/geg222/Mission1/src/components/Input.tsx @@ -0,0 +1,26 @@ +import { memo, JSX } from "react"; + +interface InputProps { + value?: string; + onChange: (value: string) => void; + placeholder?: string; + className?: string; +} + +export const Input = memo( + ({ + value = "", + onChange, + placeholder = "검색어를 입력하세요", + className, + }: InputProps): JSX.Element => { + return ( + onChange(e.target.value)} + /> + ); + } +); diff --git a/Week10/geg222/Mission1/src/components/IsError.tsx b/Week10/geg222/Mission1/src/components/IsError.tsx new file mode 100644 index 00000000..6a10ab97 --- /dev/null +++ b/Week10/geg222/Mission1/src/components/IsError.tsx @@ -0,0 +1,12 @@ +import React from "react"; + +const IsError = () => { + return ( +
+

에러가 발생했습니다

+

잠시 후 다시 시도해주세요.

+
+ ); +}; + +export default IsError; diff --git a/Week10/geg222/Mission1/src/components/LanguageSelector.tsx b/Week10/geg222/Mission1/src/components/LanguageSelector.tsx new file mode 100644 index 00000000..1fc7ef88 --- /dev/null +++ b/Week10/geg222/Mission1/src/components/LanguageSelector.tsx @@ -0,0 +1,34 @@ +interface LanguageOption { + value: string; + label: string; +} + +interface LanguageSelectorProps { + value: string; + onChange: (value: string) => void; + options: LanguageOption[]; + className?: string; +} + +const LanguageSelector = ({ + value, + onChange, + options, + className = "", +}: LanguageSelectorProps) => { + return ( + + ); +}; + +export default LanguageSelector; diff --git a/Week10/geg222/Mission1/src/components/LoadingSpinner.tsx b/Week10/geg222/Mission1/src/components/LoadingSpinner.tsx new file mode 100644 index 00000000..8b5e5ba0 --- /dev/null +++ b/Week10/geg222/Mission1/src/components/LoadingSpinner.tsx @@ -0,0 +1,12 @@ +import { JSX } from "react"; + +export const LoadingSpinner = (): JSX.Element => { + return ( +
+ 로딩 중... +
+ ); +}; diff --git a/Week10/geg222/Mission1/src/components/MovieCard.tsx b/Week10/geg222/Mission1/src/components/MovieCard.tsx new file mode 100644 index 00000000..a9297eec --- /dev/null +++ b/Week10/geg222/Mission1/src/components/MovieCard.tsx @@ -0,0 +1,52 @@ +import { useState } from "react"; +import { Movie } from "../types/movie"; + +interface MovieCardProps { + movie: Movie; + onClick: (movieId: number) => void; +} + +const MovieCard = ({ movie, onClick }: MovieCardProps) => { + const imageBaseUrl = "https://image.tmdb.org/t/p/w500"; + const fallbackImageImage = "https://via.placeholder.com/640x480"; + const [isHovered, setIsHovered] = useState(false); + + return ( +
setIsHovered(true)} + onMouseLeave={(): void => setIsHovered(false)} + onClick={() => onClick(movie.id)} + > +
+ {movie.title} +
+ {movie.vote_average.toFixed(1)} +
+ {isHovered && ( +
+

{movie.title}

+

+ {movie.overview} +

+
+ )} +
+
+ ); +}; + +export default MovieCard; diff --git a/Week10/geg222/Mission1/src/components/MovieFilter.tsx b/Week10/geg222/Mission1/src/components/MovieFilter.tsx new file mode 100644 index 00000000..5036a322 --- /dev/null +++ b/Week10/geg222/Mission1/src/components/MovieFilter.tsx @@ -0,0 +1,78 @@ +import { JSX, memo, useState, useCallback } from "react"; +import { SelectBox } from "./SelectBox"; +import LanguageSelector from "./LanguageSelector"; +import { MovieFilters } from "../types/movie"; +import { LANGUAGE_OPTIONS } from "../constants/movie"; +import { Input } from "./Input"; + +interface MovieFilterProps { + onChange: (filter: MovieFilters) => void; +} + +const MovieFilter = ({ onChange }: MovieFilterProps): JSX.Element => { + const [query, setQuery] = useState(""); + const [includeAdult, setIncludeAdult] = useState(false); + const [language, setLanguage] = useState("ko-KR"); + + const handleQueryChange = useCallback((value: string) => { + setQuery(value); + }, []); + + const handleSubmit = (): void => { + const filters: MovieFilters = { + query, + include_adult: includeAdult, + language, + }; + onChange(filters); + }; + return ( +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ ); +}; + +export default memo(MovieFilter); diff --git a/Week10/geg222/Mission1/src/components/MovieHeader.tsx b/Week10/geg222/Mission1/src/components/MovieHeader.tsx new file mode 100644 index 00000000..a779805c --- /dev/null +++ b/Week10/geg222/Mission1/src/components/MovieHeader.tsx @@ -0,0 +1,33 @@ +import React, { JSX } from 'react'; +import { MovieDetail } from '../types/movie'; + +const IMAGE_BASE_URL = 'https://image.tmdb.org/t/p/original'; + +interface MovieHeaderProps { + movie: MovieDetail; +} + +const MovieHeader = ({ movie }: MovieHeaderProps): JSX.Element => { + return ( +
+ {movie.title} +
+
+

{movie.title}

+
+ ⭐ {movie.vote_average.toFixed(1)} + {new Date(movie.release_date).getFullYear()}년 + ⏱ {movie.runtime}분 +
+

{movie.tagline}

+

{movie.overview}

+
+
+ ); +}; + +export default MovieHeader; diff --git a/Week10/geg222/Mission1/src/components/MovieList.tsx b/Week10/geg222/Mission1/src/components/MovieList.tsx new file mode 100644 index 00000000..01590959 --- /dev/null +++ b/Week10/geg222/Mission1/src/components/MovieList.tsx @@ -0,0 +1,28 @@ +import { Movie } from "../types/movie"; +import MovieCard from "./MovieCard"; + + +interface MovieListProps { + movies: Movie[]; + onMovieClick: (movieId: number) => void; +} + +const MovieList = ({ movies, onMovieClick }: MovieListProps) => { + if (movies.length === 0) { + return ( +
+

검색 결과가 없습니다.

+
+ ); + } + + return ( +
+ {movies.map((movie) => ( + + ))} +
+ ); +}; + +export default MovieList; diff --git a/Week10/geg222/Mission1/src/components/MovieModal/MovieDetailContent.tsx b/Week10/geg222/Mission1/src/components/MovieModal/MovieDetailContent.tsx new file mode 100644 index 00000000..00122d25 --- /dev/null +++ b/Week10/geg222/Mission1/src/components/MovieModal/MovieDetailContent.tsx @@ -0,0 +1,52 @@ + + + +import { JSX } from "react"; +import { MovieDetail } from "../../types/movie"; + +interface MovieDetailContentProps { + data: MovieDetail; + onClose: () => void; +} + +const MovieDetailContent = ({ data, onClose }: MovieDetailContentProps): JSX.Element => { + return ( +
+ {data.title} +
+

+ 평점: {data.vote_average} ({data.vote_count}명 참여) +

+

개봉일: {data.release_date}

+

인기도: {data.popularity}

+

상영 시간: {data.runtime}분

+

언어: {data.original_language}

+

관람 등급: {data.adult ? "청불" : "전체 관람가"}

+
+

{data.overview}

+
+ + IMDb에서 검색 + + +
+
+
+ ); +}; + +export default MovieDetailContent; \ No newline at end of file diff --git a/Week10/geg222/Mission1/src/components/MovieModal/MovieDetailModal.tsx b/Week10/geg222/Mission1/src/components/MovieModal/MovieDetailModal.tsx new file mode 100644 index 00000000..0ba43a21 --- /dev/null +++ b/Week10/geg222/Mission1/src/components/MovieModal/MovieDetailModal.tsx @@ -0,0 +1,54 @@ +import { JSX } from "react"; +import useFetch from "../../hooks/useFetch"; +import { MovieDetail } from "../../types/movie"; +import MovieDetailContent from "./MovieDetailContent"; + + +interface MovieDetailModalProps { + movieId: number | null; + onClose: () => void; +} + +const MovieDetailModal = ({ + movieId, + onClose, +}: MovieDetailModalProps): JSX.Element | null => { + if (!movieId) return null; + + const { data, isLoading, isError } = useFetch( + `/movie/${movieId}?language=ko-KR` + ); + + if (isLoading) + return
영화 정보를 불러오는 중입니다...
; + if (isError || !data) + return
에러가 발생했습니다
; + + return ( +
+
+
+ backdrop + +
+

{data.title}

+

{data.original_title}

+
+
+ +
+
+ ); +}; + +export default MovieDetailModal; diff --git a/Week10/geg222/Mission1/src/components/MoviePeopleGrid.tsx b/Week10/geg222/Mission1/src/components/MoviePeopleGrid.tsx new file mode 100644 index 00000000..df105295 --- /dev/null +++ b/Week10/geg222/Mission1/src/components/MoviePeopleGrid.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { MovieCast } from '../types/movie'; + +interface Props { + people: MovieCast[]; +} + +const MoviePeopleGrid = ({ people }: Props) => { + return ( +
+

감독/출연

+
+ {people.map((person) => ( +
+ {person.name} +

{person.name}

+

{person.character || person.job}

+
+ ))} +
+
+ ); +}; + +export default MoviePeopleGrid; \ No newline at end of file diff --git a/Week10/geg222/Mission1/src/components/Navbar.tsx b/Week10/geg222/Mission1/src/components/Navbar.tsx new file mode 100644 index 00000000..85872c11 --- /dev/null +++ b/Week10/geg222/Mission1/src/components/Navbar.tsx @@ -0,0 +1,60 @@ +import { NavLink } from "react-router-dom"; + +const MAIN_LINKS = [ + { to: "/", label: "홈" }, + { to: "/movies/popular", label: "인기 영화" }, + { to: "/movies/now_playing", label: "상영 중" }, + { to: "/movies/top_rated", label: "평점 순" }, + { to: "/movies/upcoming", label: "개봉 예정" }, + { to: "/search", label: "영화 검색" }, +]; + +const AUTH_LINKS = [ + { to: "/login", label: "로그인" }, + { to: "/signup", label: "회원가입" }, + { to: "/my/", label: "마이페이지" }, +]; + +const Navbar = () => { + return ( +
+
+

+ YOUNGFLIX +

+ {MAIN_LINKS.map(({ to, label }) => ( + + isActive + ? "text-lg font-semibold text-red-600" + : "text-lg font-semibold text-white hover:text-red-500" + } + > + {label} + + ))} +
+
+ {AUTH_LINKS.map(({ to, label }, index) => ( + + `px-3 py-1 rounded ${ + index === 0 + ? "bg-black text-white borde hover:bg-white hover:text-black" + : "bg-red-600 text-white hover:bg-red-500" + } text-sm font-semibold transition-colors duration-200` + } + > + {label} + + ))} +
+
+ ); +}; + +export default Navbar; diff --git a/Week10/geg222/Mission1/src/components/SelectBox.tsx b/Week10/geg222/Mission1/src/components/SelectBox.tsx new file mode 100644 index 00000000..2c5d1539 --- /dev/null +++ b/Week10/geg222/Mission1/src/components/SelectBox.tsx @@ -0,0 +1,30 @@ +interface SelectBoxProps { + checked: boolean; + onChange: (checked: boolean) => void; + label: string; + id?: string; + className: string; +} + +export const SelectBox = ({ + checked, + onChange, + label, + id = "checkbox", + className = "", +}: SelectBoxProps) => { + return ( +
+ onChange(e.target.checked)} + className="size-4 rounded border-gray-300 bg-gray-200 text-blue-600 focus:ring-blue-500" + /> + +
+ ); +}; diff --git a/Week10/geg222/Mission1/src/constants/key.ts b/Week10/geg222/Mission1/src/constants/key.ts new file mode 100644 index 00000000..bc90a544 --- /dev/null +++ b/Week10/geg222/Mission1/src/constants/key.ts @@ -0,0 +1,4 @@ +export const LOCAL_STORAGE_KEY = { + accessToken : 'accessToken', + refreshToken : 'refreshToken', +} \ No newline at end of file diff --git a/Week10/geg222/Mission1/src/constants/movie.ts b/Week10/geg222/Mission1/src/constants/movie.ts new file mode 100644 index 00000000..82374746 --- /dev/null +++ b/Week10/geg222/Mission1/src/constants/movie.ts @@ -0,0 +1,5 @@ +export const LANGUAGE_OPTIONS = [ + { value: "ko-KR", label: "한국어" }, + { value: "en-US", label: "English" }, + { value: "ja-JP", label: "日本語" }, +]; diff --git a/Week10/geg222/Mission1/src/context/AuthContext.tsx b/Week10/geg222/Mission1/src/context/AuthContext.tsx new file mode 100644 index 00000000..27fcfa9d --- /dev/null +++ b/Week10/geg222/Mission1/src/context/AuthContext.tsx @@ -0,0 +1,83 @@ +import { createContext, PropsWithChildren, useContext, useState } from "react"; +import { RequestSigninDto } from "../types/auth"; +import { LOCAL_STORAGE_KEY } from "../constants/key"; +import { useLocalStorage } from "../hooks/useLocalStorage"; +import { postLogout, postSignin } from "../apis/auth"; + +interface AuthContextType { + accessToken: string | null; + refreshToken: string | null; + login: (signinData: RequestSigninDto) => Promise; + logout: () => Promise; + +} + +export const AuthContext = createContext({ + accessToken: null, + refreshToken: null, + login: async () => {}, + logout: async () => {}, +}); + +export const AuthProvider = ({children}: PropsWithChildren) => { + const {getItem: getAccessTokenFromStorage, setItem: setAccessTokenStorage, removeItem: removeAccessTokenFromStorage} = useLocalStorage(LOCAL_STORAGE_KEY.accessToken); + const {getItem: getRefreshTokenFromStorage, setItem: setRefreshTokenStorage, removeItem: removeRefreshTokenFromStroage} = useLocalStorage(LOCAL_STORAGE_KEY.refreshToken); + + const [accessToken, setAccessToken] = useState(getAccessTokenFromStorage()); + const [refreshToken, setRefreshToken] = useState(getRefreshTokenFromStorage()); + + const login = async(signinData: RequestSigninDto)=> { + try { + const {data} = await postSignin(signinData); + + if(data) { + const newAccressToken = data.accessToken; + const newRefreshToken = data.refreshToken; + + setAccessTokenStorage(newAccressToken); + setRefreshTokenStorage(newRefreshToken); + + setAccessToken(newAccressToken); + setRefreshToken(newRefreshToken); + alert("로그인 성공"); + window.location.href = "/my" + } + }catch (error) { + console.error("로그인 오류", error); + alert("로그인에 실패했습니다."); + + } + + }; + + const logout = async() => { + try{ + await postLogout(); + removeAccessTokenFromStorage(); + removeRefreshTokenFromStroage(); + + setAccessToken(null); + setRefreshToken(null); + + alert("로그아웃 성공"); + }catch (error) { + console.error("로그아웃 오류", error); + alert("로그아웃에 실패했습니다."); + } +}; + +return ( + + {children} + + ); +}; + + +export const useAuth = () => { + const context: AuthContextType = useContext(AuthContext); + if (!context) { + throw new Error("AuthContext를 찾을 수 없습니다"); + } + return context; +} \ No newline at end of file diff --git a/Week10/geg222/Mission1/src/hooks/useCustomFetch.ts b/Week10/geg222/Mission1/src/hooks/useCustomFetch.ts new file mode 100644 index 00000000..0e0a1b94 --- /dev/null +++ b/Week10/geg222/Mission1/src/hooks/useCustomFetch.ts @@ -0,0 +1,39 @@ +import axios from "axios"; +import { useEffect, useState } from "react"; + +interface ApiResponse { + data: T | null; + isPending: boolean; + isError: boolean; + +} + +function useCustomFetch(url: string): ApiResponse { + const [data, setData] = useState(null); + const [isPending, setIsPending] = useState(false); + const [isError, setIsError] = useState(false); + + useEffect(() => { + const fetchData = async () => { + setIsPending(true); + try { + const response = await axios.get(url, { + headers: { + Authorization: `Bearer ${import.meta.env.VITE_TMDB_KEY}`, + }, + }); + setData(response.data); + } catch { + setIsError(true); + } finally { + setIsPending(false); + } + }; + + fetchData(); + }, [url]); + + return { data, isPending, isError }; +} + +export default useCustomFetch; \ No newline at end of file diff --git a/Week10/geg222/Mission1/src/hooks/useFetch.ts b/Week10/geg222/Mission1/src/hooks/useFetch.ts new file mode 100644 index 00000000..dfd826de --- /dev/null +++ b/Week10/geg222/Mission1/src/hooks/useFetch.ts @@ -0,0 +1,38 @@ +import { useEffect, useState } from "react"; +import { axiosClient } from "../apis/axiosClient"; +import { AxiosRequestConfig } from "axios"; + +const useFetch = (url: string, options?: AxiosRequestConfig) => { + const [data, setData] = useState(null); + const [error, setIsError] = useState(null); + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + const fetchData = async () => { + setIsLoading(true); + setIsError(null); + + try { + const { data } = await axiosClient.get(url, { + ...options, + }); + + setData(data); + } catch { + setIsError("데이터를 가져오는데 에러가 발생했습니다"); + } finally { + setIsLoading(false); + } + }; + + fetchData(); + }, [url, options]); + + return { + data, + error, + isLoading, + }; +}; + +export default useFetch; diff --git a/Week10/geg222/Mission1/src/hooks/useForm.ts b/Week10/geg222/Mission1/src/hooks/useForm.ts new file mode 100644 index 00000000..9afcd3e3 --- /dev/null +++ b/Week10/geg222/Mission1/src/hooks/useForm.ts @@ -0,0 +1,44 @@ +import { ChangeEvent, useEffect, useState } from "react"; + +interface UseFormProps { + initialValues: T; + validate: (values: T) => Record; +} + +function useForm({ initialValues, validate }: UseFormProps) { + const [values, setValues] = useState(initialValues); + const [touched, setTouched] = useState>({}); + const [errors, setErrors] = useState>({}); + + const handleChange = (name: keyof T, text: string) => { + setValues({ + ...values, + [name]: text, + }); + }; + + const handleBlur = (name: keyof T) => { + setTouched({ + ...touched, + [name]: true, + }); + }; + + const getInputProps = (name: keyof T) => { + const value = values[name]; + const onChange = (e: ChangeEvent) => + handleChange(name, e.target.value); + const onBlur = () => handleBlur(name); + + return { value, onChange, onBlur }; + }; + + useEffect(() => { + const newErrors = validate(values); + setErrors(newErrors); + }, [validate, values]); + + return { values, errors, touched, getInputProps }; +} + +export default useForm; \ No newline at end of file diff --git a/Week10/geg222/Mission1/src/hooks/useLocalStorage.ts b/Week10/geg222/Mission1/src/hooks/useLocalStorage.ts new file mode 100644 index 00000000..5c0af3cd --- /dev/null +++ b/Week10/geg222/Mission1/src/hooks/useLocalStorage.ts @@ -0,0 +1,29 @@ +export const useLocalStorage = (key: string) => { + const setItem = (value: unknown) => { + try { + window.localStorage.setItem(key, JSON.stringify(value)); + } catch (error) { + console.log(error); + } + }; + + const getItem = () => { + try { + const item = window.localStorage.getItem(key); + return item ? JSON.parse(item) : null; + } catch (e) { + console.log(e); + } + }; + + const removeItem = () => { + try { + window.localStorage.removeItem(key); + } catch (error) { + console.log(error); + } + }; + + return { setItem, getItem, removeItem }; + }; + \ No newline at end of file diff --git a/Week10/geg222/Mission1/src/index.css b/Week10/geg222/Mission1/src/index.css new file mode 100644 index 00000000..f1d8c73c --- /dev/null +++ b/Week10/geg222/Mission1/src/index.css @@ -0,0 +1 @@ +@import "tailwindcss"; diff --git a/Week10/geg222/Mission1/src/layout/HomeLayout.tsx b/Week10/geg222/Mission1/src/layout/HomeLayout.tsx new file mode 100644 index 00000000..86e121b1 --- /dev/null +++ b/Week10/geg222/Mission1/src/layout/HomeLayout.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { Outlet, useLocation } from 'react-router-dom'; +import Navbar from '../components/Navbar'; + +const HomeLayout = () => { + const location = useLocation(); + + return ( +
+ + {/*
*/} + +
+ //
+ ); +}; + +export default HomeLayout; diff --git a/Week10/geg222/Mission1/src/layout/ProtectedLayout.tsx b/Week10/geg222/Mission1/src/layout/ProtectedLayout.tsx new file mode 100644 index 00000000..6fcf5de9 --- /dev/null +++ b/Week10/geg222/Mission1/src/layout/ProtectedLayout.tsx @@ -0,0 +1,16 @@ +import React, { use } from 'react' +import { useAuth } from '../context/AuthContext' +import { Navigate, Outlet, useLocation } from 'react-router-dom'; + +const ProtectedLayout = () => { + const {accessToken} = useAuth(); + const location = useLocation(); + + if(!accessToken) { + return ; + } + + return ; +} + +export default ProtectedLayout diff --git a/Week10/geg222/Mission1/src/main.tsx b/Week10/geg222/Mission1/src/main.tsx new file mode 100644 index 00000000..048b00cb --- /dev/null +++ b/Week10/geg222/Mission1/src/main.tsx @@ -0,0 +1,9 @@ +import { createRoot } from "react-dom/client"; +import "./index.css"; +import App from "./App.tsx"; + +createRoot(document.getElementById("root")!).render( + <> + + +); diff --git a/Week10/geg222/Mission1/src/pages/GoogleLoginRedirectPage.tsx b/Week10/geg222/Mission1/src/pages/GoogleLoginRedirectPage.tsx new file mode 100644 index 00000000..2e77fb11 --- /dev/null +++ b/Week10/geg222/Mission1/src/pages/GoogleLoginRedirectPage.tsx @@ -0,0 +1,28 @@ +import React, { useEffect } from 'react' +import { useLocalStorage } from '../hooks/useLocalStorage'; +import { LOCAL_STORAGE_KEY } from '../constants/key'; +import { set } from 'zod'; + +const GoogleLoginRedirectPage = () => { + const {setItem:setAccessToken} = useLocalStorage(LOCAL_STORAGE_KEY.accessToken); + const {setItem:setRefreshToken} = useLocalStorage(LOCAL_STORAGE_KEY.refreshToken); + + useEffect(() => { + const urlParams = new URLSearchParams(window.location.search); + const accessToken = urlParams.get(LOCAL_STORAGE_KEY.accessToken); + const refreshToken = urlParams.get(LOCAL_STORAGE_KEY.refreshToken); + + if (accessToken) { + setAccessToken(accessToken); + setRefreshToken(refreshToken); + window.location.href = '/my'; + } + }, [setAccessToken, setRefreshToken]); + return ( +
+ 구글 로그인 리다이렉트 페이지 +
+ ) +} + +export default GoogleLoginRedirectPage diff --git a/Week10/geg222/Mission1/src/pages/Homepage.tsx b/Week10/geg222/Mission1/src/pages/Homepage.tsx new file mode 100644 index 00000000..f0635c74 --- /dev/null +++ b/Week10/geg222/Mission1/src/pages/Homepage.tsx @@ -0,0 +1,41 @@ +import React, { JSX } from "react"; +import { useLocation } from "react-router-dom"; +import useCustomFetch from "../hooks/useCustomFetch"; +import { MovieResponse } from "../types/movie"; + +const IMAGE_BASE_URL = "https://image.tmdb.org/t/p/original"; + +const Homepage = (): JSX.Element => { + const location = useLocation(); + const { data, isPending, isError } = useCustomFetch( + "https://api.themoviedb.org/3/movie/popular?language=en-US&page=1" + ); + + const backgroundImages = (data?.results || []) + .filter((movie) => movie.backdrop_path) + .sort(() => Math.random() - 0.5) + .slice(0, 12) + .map((movie) => `${IMAGE_BASE_URL}${movie.backdrop_path}`); + + return ( + location.pathname === "/" && ( +
+
+ {backgroundImages.map((src, index) => ( + 배경 이미지 + ))} +
+
+

YOUNGFLIX

+
+
+ ) + ); +}; + +export default Homepage; \ No newline at end of file diff --git a/Week10/geg222/Mission1/src/pages/LoginPage.tsx b/Week10/geg222/Mission1/src/pages/LoginPage.tsx new file mode 100644 index 00000000..cbd8ccb4 --- /dev/null +++ b/Week10/geg222/Mission1/src/pages/LoginPage.tsx @@ -0,0 +1,155 @@ +import React, { useState, useMemo, useEffect } from 'react'; +import { UserSigninInformation, validateSignin } from '../utils/validate'; +import useForm from '../hooks/useForm'; +import useCustomFetch from '../hooks/useCustomFetch'; +import { MovieResponse } from '../types/movie'; +import { useAuth } from '../context/AuthContext'; +import { useNavigate } from 'react-router-dom'; + +const LoginPage = () => { + const {login, accessToken} = useAuth(); + const navigate = useNavigate(); + + useEffect(() => { + if (accessToken) { + navigate("/"); + } + }, [accessToken, navigate]); + + const { values, errors, touched, getInputProps } = useForm({ + initialValues: { + email: "", + password: "", + }, + validate: validateSignin, + }); + + const IMAGE_BASE_URL = "https://image.tmdb.org/t/p/original"; + const { data } = useCustomFetch( + "https://api.themoviedb.org/3/movie/popular?language=en-US&page=1" + ); + + const backgroundImages = useMemo(() => { + return (data?.results || []) + .filter((movie) => movie.backdrop_path) + .sort(() => Math.random() - 0.5) + .slice(0, 1) + .map((movie) => `${IMAGE_BASE_URL}${movie.backdrop_path}`); + }, [data]); + + const handleSubmit = async () => { + await login(values); + }; + + const handleGoogleLogin = () => { + window.location.href = import.meta.env.VITE_SERVER_API_URL + "/v1/auth/google/login"; + }; + + + const isDisabled = Object.values(errors || {}).some((error) => error.length > 0) || Object.values(values).some((value) => value === ""); + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Enter') { + const activeElement = document.activeElement; + const isInputFocused = activeElement && (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA'); + + if (!isInputFocused && !isDisabled) { + handleSubmit(); + } + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => { + window.removeEventListener('keydown', handleKeyDown); + }; + }, [handleSubmit, isDisabled]); + + + return ( +
+
+
+
+
+ +

로그인

+
+ + + +
+
+ 또는 +
+
+ +
{ e.preventDefault(); handleSubmit(); }} className="flex flex-col gap-4"> + + {errors?.email && touched?.email && ( +
{errors.email}
+ )} + + + {errors?.password && touched?.password && ( +
{errors.password}
+ )} + + +
+
+ 아직 계정이 없으신가요?{" "} + +
+
+
+
+ ); +} + +export default LoginPage; diff --git a/Week10/geg222/Mission1/src/pages/MovieDetailPage.tsx b/Week10/geg222/Mission1/src/pages/MovieDetailPage.tsx new file mode 100644 index 00000000..c556d0e8 --- /dev/null +++ b/Week10/geg222/Mission1/src/pages/MovieDetailPage.tsx @@ -0,0 +1,52 @@ +import React, { JSX } from 'react'; +import { useParams } from 'react-router-dom'; +import useCustomFetch from '../hooks/useCustomFetch'; +import { MovieDetail } from '../types/movie'; +import IsError from '../components/IsError'; +import MovieHeader from '../components/MovieHeader'; +import MoviePeopleGrid from '../components/MoviePeopleGrid'; + +const MovieDetailPage = (): JSX.Element => { + const { movieId } = useParams<{ movieId: string }>(); + + const { data: movie, isPending, isError } = useCustomFetch( + `https://api.themoviedb.org/3/movie/${movieId}?language=ko-KR&append_to_response=credits` + ); + + const getCastAndCrew = (movie: any) => { + const cast = movie?.credits?.cast || []; + const crew = movie?.credits?.crew || []; + + const directors = crew.filter((person: any) => person.job === "Director"); + const combined = [...directors, ...cast]; + const uniquePeople = combined.reduce((acc: any[], current: any) => { + const exists = acc.find((p) => p.id === current.id); + if (!exists) acc.push(current); + return acc; + }, []); + return uniquePeople; + }; + + const peopleToShow = getCastAndCrew(movie).slice(0, 20); + + if (isPending) { + return ( +
+ 영화 정보를 불러오는 중입니다... +
+ ); + } + + if (isError || !movie) { + return + } + + return ( +
+ + {peopleToShow.length > 0 && } +
+ ); +}; + +export default MovieDetailPage; diff --git a/Week10/geg222/Mission1/src/pages/MoviePage.tsx b/Week10/geg222/Mission1/src/pages/MoviePage.tsx new file mode 100644 index 00000000..42ffceb4 --- /dev/null +++ b/Week10/geg222/Mission1/src/pages/MoviePage.tsx @@ -0,0 +1,94 @@ +import { JSX, useEffect, useState } from "react"; +import axios from "axios"; +import { Movie, MovieResponse } from "../types/movie"; +import MovieCard from "../components/MovieCard"; +import { LoadingSpinner } from "../components/LoadingSpinner"; +import { useParams, Link } from "react-router-dom"; +import useCustomFetch from "../hooks/useCustomFetch"; +import IsError from "../components/IsError"; + +export default function MoviePage(): JSX.Element { + const [page, setPage] = useState(1); + + const { category } = useParams<{ + category: string; + }>(); + + useEffect(() => { + setPage(1); + }, [category]); + + const categoryMap = { + popular: "인기 영화", + top_rated: "평점 높은 영화", + now_playing: "상영 중인 영화", + upcoming: "개봉 예정 영화" + }; + + const url = `https://api.themoviedb.org/3/movie/${category}?language=ko-KR&page=${page}`; + const { data, isPending, isError } = useCustomFetch(url); + + if (isError) { + return + } + + return ( +
+
+ {data?.results?.[0] && ( +
+ )} + +

{data?.results?.[0]?.title}

+

{data?.results?.[0]?.overview}

+ +
+ +

+ {{ + popular: "인기 영화", + top_rated: "평점 높은 영화", + now_playing: "상영 중인 영화", + upcoming: "개봉 예정 영화" + }[category as keyof typeof categoryMap] || "영화 목록"} +

+ +
+ + {page} 페이지 + +
+ + {isPending && ( +
+ +
+ )} + + {!isPending && data && ( +
+ {data.results.map((movie): JSX.Element => ( + + ))} +
+ )} +
+ ); +} diff --git a/Week10/geg222/Mission1/src/pages/Mypage.tsx b/Week10/geg222/Mission1/src/pages/Mypage.tsx new file mode 100644 index 00000000..a32fcc0f --- /dev/null +++ b/Week10/geg222/Mission1/src/pages/Mypage.tsx @@ -0,0 +1,53 @@ +import React, { useEffect, useState } from 'react'; +import { getMyInfo } from '../apis/auth'; +import { useAuth } from '../context/AuthContext'; +import { useNavigate } from 'react-router-dom'; +import { LoadingSpinner } from '../components/LoadingSpinner'; + +interface UserData { + name: string; + avatar: string; + email: string; +} + +const Mypage = () => { + const navigate = useNavigate(); + const { logout } = useAuth(); + const [data, setData] = useState(null); + + useEffect(() => { + const getData = async () => { + const response = await getMyInfo(); + console.log(response); + + setData(response.data); + }; + + getData(); + }, []); + + const handleLogout = async () => { + await logout(); + navigate("/"); + }; + + if (!data) { + return LoadingSpinner(); + } + + return ( +
+

{data.name}님 환영합니다.

+ profile +

{data.email}

+ +
+ ); +}; + +export default Mypage; diff --git a/Week10/geg222/Mission1/src/pages/NotFoundPage.tsx b/Week10/geg222/Mission1/src/pages/NotFoundPage.tsx new file mode 100644 index 00000000..e9f05ab8 --- /dev/null +++ b/Week10/geg222/Mission1/src/pages/NotFoundPage.tsx @@ -0,0 +1,16 @@ +import { JSX } from "react"; + +export default function NotFoundPage(): JSX.Element { + return ( +
+

404 Not Found

+

유효하지 않은 페이지입니다. 주소를 다시 확인해주세요.

+ +
+ ); +} diff --git a/Week10/geg222/Mission1/src/pages/SearchPage.tsx b/Week10/geg222/Mission1/src/pages/SearchPage.tsx new file mode 100644 index 00000000..00bbeb27 --- /dev/null +++ b/Week10/geg222/Mission1/src/pages/SearchPage.tsx @@ -0,0 +1,63 @@ +import { useCallback, useMemo, useState } from "react"; +import MovieFilter from "../components/MovieFilter"; +import MovieList from "../components/MovieList"; +import { MovieFilters, MovieResponse } from "../types/movie"; +import useFetch from "../hooks/useFetch"; +import MovieDetailModal from "../components/MovieModal/MovieDetailModal"; + + +const SearchPage = () => { + const [filters, setFilters] = useState({ + query: "", + include_adult: false, + language: "ko-KR", + }); + const [selectedMovieId, setSelectedMovieId] = useState(null); + + const axiosRequestConfig = useMemo( + () => ({ + params: filters, + }), + [filters] + ); + + const { data, error, isLoading } = useFetch( + "/search/movie", + axiosRequestConfig + ); + + const handleMovieFilters = useCallback( + (filters: MovieFilters) => { + setFilters(filters); + }, + [setFilters] + ); + + const handleMovieClick = useCallback((id: number) => { + setSelectedMovieId(id); + }, []); + + if (error) { + return
{error}
; + } + + return ( +
+ + {isLoading ? ( +
로딩 중...
+ ) : ( + + )} + setSelectedMovieId(null)} + /> +
+ ); +}; + +export default SearchPage; diff --git a/Week10/geg222/Mission1/src/pages/SignupPage.tsx b/Week10/geg222/Mission1/src/pages/SignupPage.tsx new file mode 100644 index 00000000..54d4415c --- /dev/null +++ b/Week10/geg222/Mission1/src/pages/SignupPage.tsx @@ -0,0 +1,304 @@ +import { z } from "zod"; +import { useForm, SubmitHandler } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useNavigate } from "react-router-dom"; +import { useState, useMemo, useEffect, useCallback } from "react"; +import useCustomFetch from "../hooks/useCustomFetch"; +import { postSignup as apiPostSignup } from "../apis/auth"; +import { MovieResponse } from "../types/movie"; + +const IMAGE_BASE_URL = "https://image.tmdb.org/t/p/original"; + +const schema = z + .object({ + email: z.string().email({ message: "올바른 이메일 형식을 입력해주세요." }), + password: z + .string() + .min(8, { message: "비밀번호는 8자 이상이여야 합니다." }) + .max(20, { message: "비밀번호는 20자 이하이여야 합니다." }), + passwordCheck: z + .string() + .min(8, { message: "비밀번호는 8자 이상이여야 합니다." }) + .max(20, { message: "비밀번호는 20자 이하이여야 합니다." }), + name: z.string().min(1, { message: "이름을 입력해주세요." }), + }) + .refine((data) => data.password === data.passwordCheck, { + message: "비밀번호가 일치하지 않습니다.", + path: ["passwordCheck"], + }); + +type FormFields = z.infer; + +export const SignupPage = () => { + const navigate = useNavigate(); + const { data } = useCustomFetch( + "https://api.themoviedb.org/3/movie/popular?language=en-US&page=1" + ); + + const backgroundImage = useMemo(() => { + return ( + (data?.results || []) + .filter((movie) => movie.backdrop_path) + .sort(() => Math.random() - 0.5) + .slice(0, 1) + .map((movie) => `${IMAGE_BASE_URL}${movie.backdrop_path}`)[0] || "" + ); + }, [data]); + + const [step, setStep] = useState(1); + const [showPassword, setShowPassword] = useState(false); + const [showPasswordCheck, setShowPasswordCheck] = useState(false); + const [previewUrl, setPreviewUrl] = useState(null); + + const { + register, + handleSubmit, + formState: { errors }, + watch, + trigger, + } = useForm({ + resolver: zodResolver(schema), + mode: "onChange", + }); + + const email = watch("email"); + const password = watch("password"); + const passwordCheck = watch("passwordCheck"); + const name = watch("name"); + + const handleNextStep = async () => { + const valid = await trigger( + step === 1 + ? "email" + : step === 2 + ? ["password", "passwordCheck"] + : "name" + ); + if (valid && (step !== 2 || password === passwordCheck)) { + setStep(step + 1); + } + }; + + const onSubmit: SubmitHandler = async (data) => { + const { passwordCheck, ...rest } = data; + const response = await apiPostSignup(rest); + console.log(response); + navigate("/login"); + }; + + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + if (e.key === "Enter") { + if (step === 1 && email && !errors.email) { + handleNextStep(); + } else if ( + step === 2 && + password && + passwordCheck && + !errors.password && + !errors.passwordCheck && + password === passwordCheck + ) { + handleNextStep(); + } + } + }, + [step, email, password, passwordCheck, errors] + ); + + useEffect(() => { + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [handleKeyDown]); + + return ( +
+ {backgroundImage && ( +
+ )} +
+
+
+ +
+ 회원가입 +
+
+ + {step === 1 && ( + <> + + +
+
+ 또는 +
+
+ + + {errors?.email && ( +
{errors.email.message}
+ )} + + + + )} + + {step === 2 && ( + <> +
{email}
+ +
+ + + {errors?.password && ( +
{errors.password.message}
+ )} +
+ +
+ + + {errors?.passwordCheck && ( +
{errors.passwordCheck.message}
+ )} + {!errors?.passwordCheck && password !== passwordCheck && ( +
비밀번호가 일치하지 않습니다.
+ )} +
+ + + + )} + + {step === 3 && ( +
+
{email}
+ +
+ + { + const file = e.target.files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onloadend = () => + setPreviewUrl(reader.result as string); + reader.readAsDataURL(file); + } + }} + className="hidden" + /> +
+ + + {errors?.name && ( +
{errors.name.message}
+ )} + + +
+ )} +
+
+
+ ); +}; + +export default SignupPage; \ No newline at end of file diff --git a/Week10/geg222/Mission1/src/types/auth.ts b/Week10/geg222/Mission1/src/types/auth.ts new file mode 100644 index 00000000..952a23cc --- /dev/null +++ b/Week10/geg222/Mission1/src/types/auth.ts @@ -0,0 +1,44 @@ +import { CommonResponse } from "./common.ts"; + +//회원가입 +export type RequestSignupDto = { + name: string; + email: string; + bio?: string; + avatar?: string; + password: string; +}; + +export type ResponseSingupDto = CommonResponse<{ + id: number; + name: string; + email: string; + bio: string | null; + avatar: string | null; + createdAt: Date; + updatedAt: Date; +}>; + +//로그인 +export type RequestSigninDto = { + email: string; + password: string; +}; + +export type ResponseSigninDto = CommonResponse<{ + id: number; + name: string; + accessToken: string; + refreshToken: string; +}>; + +// 내 정보 조회 +export type ResponseMyInfoDto = CommonResponse<{ + id: number; + name: string; + email: string; + bio: string | null; + avatar: string | null; + createdAt: Date; + updatedAt: Date; +}>; \ No newline at end of file diff --git a/Week10/geg222/Mission1/src/types/common.ts b/Week10/geg222/Mission1/src/types/common.ts new file mode 100644 index 00000000..15ee914d --- /dev/null +++ b/Week10/geg222/Mission1/src/types/common.ts @@ -0,0 +1,6 @@ +export type CommonResponse = { + status: string; + statusCode: number; + message: string; + data: T; +}; \ No newline at end of file diff --git a/Week10/geg222/Mission1/src/types/movie.ts b/Week10/geg222/Mission1/src/types/movie.ts new file mode 100644 index 00000000..d25956f1 --- /dev/null +++ b/Week10/geg222/Mission1/src/types/movie.ts @@ -0,0 +1,57 @@ +export type MovieFilters = { + query: string; + include_adult: boolean; + language: string; +}; + +export type Movie = { + adult: boolean; + backdrop_path: string; + genre_ids: number[]; + id: number; + original_language: string; + original_title: string; + overview: string; + popularity: number; + poster_path: string; + release_date: string; + title: string; + video: boolean; + vote_average: number; + vote_count: number; +}; + +export type MovieResponse = { + page: number; + results: Movie[]; + total_pages: number; + total_results: number; +}; + +export type MovieDetail = { + title: string; + tagline: string; + release_date: string; + runtime: number; + vote_average: number; + overview: string; + poster_path: string; + backdrop_path: string; + genres: { id: number; name: string }[]; + revenue: number; + budget: number; + status: string; + original_language: string; + credits?: { + cast: MovieCast[]; + }; +}; + +export type MovieCast = { + id: number; + name: string; + original_name: string; + profile_path: string | null; + character: string; + job?: string; +}; diff --git a/Week10/geg222/Mission1/src/utils/validate.ts b/Week10/geg222/Mission1/src/utils/validate.ts new file mode 100644 index 00000000..f8bb459b --- /dev/null +++ b/Week10/geg222/Mission1/src/utils/validate.ts @@ -0,0 +1,33 @@ +export type UserSigninInformation = { + email: string; + password: string; +}; + +function validateUser(values: UserSigninInformation) { + const errors = { + email: "", + password: "" + }; + + if (!values.email) { + errors.email = "이메일을 입력해주세요."; + } else if (!/^[A-Za-z0-9]([-_.]?[A-Za-z0-9])*@[A-Za-z0-9]([-_.]?[A-Za-z0-9])*\.[A-Za-z]{2,3}$/.test(values.email)) { + errors.email = "올바른 이메일 형식이 아닙니다."; + } + + + if (!values.password) { + errors.password = "비밀번호를 입력해주세요."; + } else if (!(values.password.length >= 8 && values.password.length <= 20)) { + errors.password = "비밀번호는 8자 이상 20자 이하로 입력해주세요."; + } + + return errors; +} + + +function validateSignin(values: UserSigninInformation) { + return validateUser(values); +} + +export { validateSignin }; \ No newline at end of file diff --git a/Week10/geg222/Mission1/src/vite-env.d.ts b/Week10/geg222/Mission1/src/vite-env.d.ts new file mode 100644 index 00000000..054eb5a6 --- /dev/null +++ b/Week10/geg222/Mission1/src/vite-env.d.ts @@ -0,0 +1,10 @@ +/// +interface ImportMetaEnv { + readonly VITE_TMDB_KEY: string; + readonly VITE_SERVER_API_URL: string; + } + + interface ImportMeta { + readonly env: ImportMetaEnv; + } + \ No newline at end of file diff --git a/Week10/geg222/Mission1/tsconfig.app.json b/Week10/geg222/Mission1/tsconfig.app.json new file mode 100644 index 00000000..358ca9ba --- /dev/null +++ b/Week10/geg222/Mission1/tsconfig.app.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* 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/Week10/geg222/Mission1/tsconfig.json b/Week10/geg222/Mission1/tsconfig.json new file mode 100644 index 00000000..1ffef600 --- /dev/null +++ b/Week10/geg222/Mission1/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/Week10/geg222/Mission1/tsconfig.node.json b/Week10/geg222/Mission1/tsconfig.node.json new file mode 100644 index 00000000..db0becc8 --- /dev/null +++ b/Week10/geg222/Mission1/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/Week10/geg222/Mission1/vite.config.ts b/Week10/geg222/Mission1/vite.config.ts new file mode 100644 index 00000000..173bb24b --- /dev/null +++ b/Week10/geg222/Mission1/vite.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vite'; +import tailwindcss from '@tailwindcss/vite'; +import react from '@vitejs/plugin-react-swc'; + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react(), tailwindcss()], +}); \ No newline at end of file