From 5ac0bd6b4a93b8947e934897b8d43b5f496d001c Mon Sep 17 00:00:00 2001 From: wannabeing Date: Thu, 12 Jun 2025 13:41:49 +0900 Subject: [PATCH 01/47] =?UTF-8?q?feat:=20ClaudeCode=20=ED=94=84=EB=A1=A0?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=EC=B6=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 26 + README.md | 46 + create-favicon.html | 71 + index.html | 25 + package-lock.json | 3563 +++++++++++++++++ package.json | 33 + postcss.config.js | 6 + public/cs25_logo.png | Bin 0 -> 109823 bytes public/favicon.svg | 14 + src/App.css | 38 + src/App.test.tsx | 9 + src/App.tsx | 47 + src/components/LandingPage.tsx | 18 + src/components/SubscriptionPage.tsx | 415 ++ src/components/VerificationEmailPage.tsx | 62 + src/components/common/Container.tsx | 16 + src/components/common/EmailTemplate.tsx | 86 + src/components/common/Footer.tsx | 14 + src/components/common/Header.tsx | 59 + src/components/common/LoginForm.tsx | 35 + src/components/common/Modal.tsx | 56 + src/components/common/QuizComponent.tsx | 68 + src/components/common/Section.tsx | 32 + src/components/common/SocialButton.tsx | 31 + src/components/common/SubscriptionModal.tsx | 409 ++ .../common/SubscriptionSettings.tsx | 175 + src/components/sections/FeaturesSection.tsx | 86 + src/components/sections/HeroSection.tsx | 59 + src/components/sections/QuizSection.tsx | 100 + .../sections/TodayEmailFormSection.tsx | 47 + src/index.css | 19 + src/index.tsx | 19 + src/logo.svg | 1 + src/reportWebVitals.ts | 15 + src/setupTests.ts | 5 + tailwind.config.js | 44 + tsconfig.json | 26 + vite.config.ts | 14 + 38 files changed, 5789 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 create-favicon.html create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 postcss.config.js create mode 100644 public/cs25_logo.png create mode 100644 public/favicon.svg create mode 100644 src/App.css create mode 100644 src/App.test.tsx create mode 100644 src/App.tsx create mode 100644 src/components/LandingPage.tsx create mode 100644 src/components/SubscriptionPage.tsx create mode 100644 src/components/VerificationEmailPage.tsx create mode 100644 src/components/common/Container.tsx create mode 100644 src/components/common/EmailTemplate.tsx create mode 100644 src/components/common/Footer.tsx create mode 100644 src/components/common/Header.tsx create mode 100644 src/components/common/LoginForm.tsx create mode 100644 src/components/common/Modal.tsx create mode 100644 src/components/common/QuizComponent.tsx create mode 100644 src/components/common/Section.tsx create mode 100644 src/components/common/SocialButton.tsx create mode 100644 src/components/common/SubscriptionModal.tsx create mode 100644 src/components/common/SubscriptionSettings.tsx create mode 100644 src/components/sections/FeaturesSection.tsx create mode 100644 src/components/sections/HeroSection.tsx create mode 100644 src/components/sections/QuizSection.tsx create mode 100644 src/components/sections/TodayEmailFormSection.tsx create mode 100644 src/index.css create mode 100644 src/index.tsx create mode 100644 src/logo.svg create mode 100644 src/reportWebVitals.ts create mode 100644 src/setupTests.ts create mode 100644 tailwind.config.js create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..accdc86 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# claude +/.claude + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/README.md b/README.md new file mode 100644 index 0000000..b87cb00 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# Getting Started with Create React App + +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). + +## Available Scripts + +In the project directory, you can run: + +### `npm start` + +Runs the app in the development mode.\ +Open [http://localhost:3000](http://localhost:3000) to view it in the browser. + +The page will reload if you make edits.\ +You will also see any lint errors in the console. + +### `npm test` + +Launches the test runner in the interactive watch mode.\ +See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. + +### `npm run build` + +Builds the app for production to the `build` folder.\ +It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.\ +Your app is ready to be deployed! + +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. + +### `npm run eject` + +**Note: this is a one-way operation. Once you `eject`, you can’t go back!** + +If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. + +Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. + +You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. + +## Learn More + +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). + +To learn React, check out the [React documentation](https://reactjs.org/). diff --git a/create-favicon.html b/create-favicon.html new file mode 100644 index 0000000..50df433 --- /dev/null +++ b/create-favicon.html @@ -0,0 +1,71 @@ + + + + Favicon Creator + + + +

+ + + + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..32e445c --- /dev/null +++ b/index.html @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + CS25 - 해설과 함께하는 CS 학습 + + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..539b6b3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3563 @@ +{ + "name": "cs25-fronted", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cs25-fronted", + "version": "0.1.0", + "dependencies": { + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^13.5.0", + "@types/jest": "^27.5.2", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router-dom": "^7.6.2", + "typescript": "^4.9.5", + "web-vitals": "^2.1.4" + }, + "devDependencies": { + "@types/node": "^20.19.0", + "@vitejs/plugin-react": "^4.5.2", + "autoprefixer": "^10.4.21", + "postcss": "^8.5.4", + "tailwindcss": "^3.4.1", + "vite": "^6.3.5" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.3.tgz", + "integrity": "sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==", + "license": "MIT" + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", + "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", + "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.4", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.4", + "@babel/types": "^7.27.3", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", + "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.5", + "@babel/types": "^7.27.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", + "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", + "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", + "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.11", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.11.tgz", + "integrity": "sha512-L/gAA/hyCSuzTF1ftlzUSI/IKr2POHsv1Dd78GfqkR83KMNuswWD61JxGV2L7nRwBBBSDr6R1gCkdTmoN7W4ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.43.0.tgz", + "integrity": "sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.43.0.tgz", + "integrity": "sha512-ss4YJwRt5I63454Rpj+mXCXicakdFmKnUNxr1dLK+5rv5FJgAxnN7s31a5VchRYxCFWdmnDWKd0wbAdTr0J5EA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.43.0.tgz", + "integrity": "sha512-eKoL8ykZ7zz8MjgBenEF2OoTNFAPFz1/lyJ5UmmFSz5jW+7XbH1+MAgCVHy72aG59rbuQLcJeiMrP8qP5d/N0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.43.0.tgz", + "integrity": "sha512-SYwXJgaBYW33Wi/q4ubN+ldWC4DzQY62S4Ll2dgfr/dbPoF50dlQwEaEHSKrQdSjC6oIe1WgzosoaNoHCdNuMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.43.0.tgz", + "integrity": "sha512-SV+U5sSo0yujrjzBF7/YidieK2iF6E7MdF6EbYxNz94lA+R0wKl3SiixGyG/9Klab6uNBIqsN7j4Y/Fya7wAjQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.43.0.tgz", + "integrity": "sha512-J7uCsiV13L/VOeHJBo5SjasKiGxJ0g+nQTrBkAsmQBIdil3KhPnSE9GnRon4ejX1XDdsmK/l30IYLiAaQEO0Cg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.43.0.tgz", + "integrity": "sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.43.0.tgz", + "integrity": "sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.43.0.tgz", + "integrity": "sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.43.0.tgz", + "integrity": "sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.43.0.tgz", + "integrity": "sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.43.0.tgz", + "integrity": "sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.43.0.tgz", + "integrity": "sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.43.0.tgz", + "integrity": "sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.43.0.tgz", + "integrity": "sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.43.0.tgz", + "integrity": "sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.43.0.tgz", + "integrity": "sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.43.0.tgz", + "integrity": "sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.43.0.tgz", + "integrity": "sha512-fYCTEyzf8d+7diCw8b+asvWDCLMjsCEA8alvtAutqJOJp/wL5hs1rWSqJ1vkjgW0L2NB4bsYJrpKkiIPRR9dvw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.43.0.tgz", + "integrity": "sha512-SnGhLiE5rlK0ofq8kzuDkM0g7FN1s5VYY+YSMTibP7CqShxCQvqtNxTARS4xX4PFJfHjG0ZQYX9iGzI3FQh5Aw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@testing-library/dom": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz", + "integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==", + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "lodash": "^4.17.21", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", + "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", + "integrity": "sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jest": { + "version": "27.5.2", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz", + "integrity": "sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA==", + "license": "MIT", + "dependencies": { + "jest-matcher-utils": "^27.0.0", + "pretty-format": "^27.0.0" + } + }, + "node_modules/@types/node": { + "version": "20.19.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.0.tgz", + "integrity": "sha512-hfrc+1tud1xcdVTABC2JiomZJEklMcXYNTVtZLAeqTVWD+qL5jkHKT+1lOtqDdGxt+mB53DTtiz673vfjU8D1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.1.8", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", + "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", + "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.2.tgz", + "integrity": "sha512-QNVT3/Lxx99nMQWJWF7K4N6apUEuT0KlZA3mx/mVaoGj3smm/8rc8ezz15J1pcbcjDK0V15rpHetVfya08r76Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.11", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001722", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001722.tgz", + "integrity": "sha512-DCQHBBZtiK6JVkAGw7drvAMK0Q0POD/xZvEmDp6baiMMP6QXXk9HpD6mNYBZWhOPG6LvIDb82ITqtWjhDckHCA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/diff-sequences": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "license": "MIT", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "license": "MIT" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.166", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.166.tgz", + "integrity": "sha512-QPWqHL0BglzPYyJJ1zSSmwFFL6MFXhbACOCcsCdUMCkzPdS9/OIBVxg516X/Ado2qwAq8k0nJJ7phQPCqiaFAw==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.5", + "@esbuild/android-arm": "0.25.5", + "@esbuild/android-arm64": "0.25.5", + "@esbuild/android-x64": "0.25.5", + "@esbuild/darwin-arm64": "0.25.5", + "@esbuild/darwin-x64": "0.25.5", + "@esbuild/freebsd-arm64": "0.25.5", + "@esbuild/freebsd-x64": "0.25.5", + "@esbuild/linux-arm": "0.25.5", + "@esbuild/linux-arm64": "0.25.5", + "@esbuild/linux-ia32": "0.25.5", + "@esbuild/linux-loong64": "0.25.5", + "@esbuild/linux-mips64el": "0.25.5", + "@esbuild/linux-ppc64": "0.25.5", + "@esbuild/linux-riscv64": "0.25.5", + "@esbuild/linux-s390x": "0.25.5", + "@esbuild/linux-x64": "0.25.5", + "@esbuild/netbsd-arm64": "0.25.5", + "@esbuild/netbsd-x64": "0.25.5", + "@esbuild/openbsd-arm64": "0.25.5", + "@esbuild/openbsd-x64": "0.25.5", + "@esbuild/sunos-x64": "0.25.5", + "@esbuild/win32-arm64": "0.25.5", + "@esbuild/win32-ia32": "0.25.5", + "@esbuild/win32-x64": "0.25.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest-diff": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "license": "MIT", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", + "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", + "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.2.tgz", + "integrity": "sha512-U7Nv3y+bMimgWjhlT5CRdzHPu2/KVmqPwKUCChW8en5P3znxUqwlYFlbmyj8Rgp1SF6zs5X4+77kBVknkg6a0w==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.2.tgz", + "integrity": "sha512-Q8zb6VlTbdYKK5JJBLQEN06oTUa/RAbG/oQS1auK1I0TbJOXktqm+QENEVJU6QvWynlXPRBXI3fiOQcSEA78rA==", + "license": "MIT", + "dependencies": { + "react-router": "7.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.43.0.tgz", + "integrity": "sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.43.0", + "@rollup/rollup-android-arm64": "4.43.0", + "@rollup/rollup-darwin-arm64": "4.43.0", + "@rollup/rollup-darwin-x64": "4.43.0", + "@rollup/rollup-freebsd-arm64": "4.43.0", + "@rollup/rollup-freebsd-x64": "4.43.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.43.0", + "@rollup/rollup-linux-arm-musleabihf": "4.43.0", + "@rollup/rollup-linux-arm64-gnu": "4.43.0", + "@rollup/rollup-linux-arm64-musl": "4.43.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.43.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.43.0", + "@rollup/rollup-linux-riscv64-gnu": "4.43.0", + "@rollup/rollup-linux-riscv64-musl": "4.43.0", + "@rollup/rollup-linux-s390x-gnu": "4.43.0", + "@rollup/rollup-linux-x64-gnu": "4.43.0", + "@rollup/rollup-linux-x64-musl": "4.43.0", + "@rollup/rollup-win32-arm64-msvc": "4.43.0", + "@rollup/rollup-win32-ia32-msvc": "4.43.0", + "@rollup/rollup-win32-x64-msvc": "4.43.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.19.1", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "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 + } + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/web-vitals": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-2.1.4.tgz", + "integrity": "sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==", + "license": "Apache-2.0" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..d315126 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "cs25-fe", + "version": "0.1.0", + "private": true, + "dependencies": { + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^13.5.0", + "@types/jest": "^27.5.2", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router-dom": "^7.6.2", + "typescript": "^4.9.5", + "web-vitals": "^2.1.4" + }, + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "start": "vite" + }, + "devDependencies": { + "@types/node": "^20.19.0", + "@vitejs/plugin-react": "^4.5.2", + "autoprefixer": "^10.4.21", + "postcss": "^8.5.4", + "tailwindcss": "^3.4.1", + "vite": "^6.3.5" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/public/cs25_logo.png b/public/cs25_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..8f6e5f4cc2bb251583945d30af72ba10440a23c9 GIT binary patch literal 109823 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�=>9bv$NvJY~tIp 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 literal 0 HcmV?d00001 diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..bf4e869 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,14 @@ + + + + + + CS + + + 25 + + + + + \ No newline at end of file diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..74b5e05 --- /dev/null +++ b/src/App.css @@ -0,0 +1,38 @@ +.App { + text-align: center; +} + +.App-logo { + height: 40vmin; + pointer-events: none; +} + +@media (prefers-reduced-motion: no-preference) { + .App-logo { + animation: App-logo-spin infinite 20s linear; + } +} + +.App-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} + +.App-link { + color: #61dafb; +} + +@keyframes App-logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/src/App.test.tsx b/src/App.test.tsx new file mode 100644 index 0000000..2a68616 --- /dev/null +++ b/src/App.test.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import App from './App'; + +test('renders learn react link', () => { + render(); + const linkElement = screen.getByText(/learn react/i); + expect(linkElement).toBeInTheDocument(); +}); diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..0d28363 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,47 @@ +import { useState } from 'react'; +import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; +import LandingPage from './components/LandingPage'; +import QuizSection from './components/sections/QuizSection'; +import TodayEmailFormSection from './components/sections/TodayEmailFormSection'; +import SubscriptionPage from './components/SubscriptionPage'; +import VerificationEmailPage from './components/VerificationEmailPage'; +import Header from './components/common/Header'; +import Footer from './components/common/Footer'; +import SubscriptionModal from './components/common/SubscriptionModal'; +import LoginForm from './components/common/LoginForm'; +import Modal from './components/common/Modal'; +import './App.css'; + +function App() { + const [isSubscriptionModalOpen, setIsSubscriptionModalOpen] = useState(false); + const [isLoginModalOpen, setIsLoginModalOpen] = useState(false); + + return ( +
+ +
setIsLoginModalOpen(true)} /> + + setIsSubscriptionModalOpen(true)} />} /> + } /> + } /> + } /> + } /> + +
+ setIsSubscriptionModalOpen(false)} + /> + setIsLoginModalOpen(false)} + title="로그인" + > + + + +
+ ); +} + +export default App; diff --git a/src/components/LandingPage.tsx b/src/components/LandingPage.tsx new file mode 100644 index 0000000..b03139c --- /dev/null +++ b/src/components/LandingPage.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import HeroSection from './sections/HeroSection'; +import FeaturesSection from './sections/FeaturesSection'; + +interface LandingPageProps { + onSubscribeClick: () => void; +} + +const LandingPage: React.FC = ({ onSubscribeClick }) => { + return ( + <> + + + + ); +}; + +export default LandingPage; \ No newline at end of file diff --git a/src/components/SubscriptionPage.tsx b/src/components/SubscriptionPage.tsx new file mode 100644 index 0000000..ef08d6d --- /dev/null +++ b/src/components/SubscriptionPage.tsx @@ -0,0 +1,415 @@ +import React, { useState } from 'react'; +import Container from './common/Container'; +import Section from './common/Section'; + +interface FormData { + categories: string[]; + weekdays: string[]; + email: string; +} + +interface FormErrors { + category?: string; + weekdays?: string; + verification?: string; +} + +type Step = 'form' | 'verification' | 'success'; + +const SubscriptionPage: React.FC = () => { + const [step, setStep] = useState('form'); + const [isLoading, setIsLoading] = useState(false); + const [verificationCode, setVerificationCode] = useState(''); + const [emailError, setEmailError] = useState(''); + const [formErrors, setFormErrors] = useState({}); + const [formData, setFormData] = useState({ + categories: [], + weekdays: [], + email: '' + }); + + const categories = [ + { id: 'frontend', label: '프론트엔드' }, + { id: 'backend', label: '백엔드' }, + { id: 'certification', label: '자격증' } + ]; + + const weekdays = [ + { id: 'MONDAY', label: '월' }, + { id: 'TUESDAY', label: '화' }, + { id: 'WEDNESDAY', label: '수' }, + { id: 'THURSDAY', label: '목' }, + { id: 'FRIDAY', label: '금' }, + { id: 'SATURDAY', label: '토' }, + { id: 'SUNDAY', label: '일' } + ]; + + const handleCategoryChange = (categoryId: string) => { + setFormData(prev => ({ + ...prev, + categories: [categoryId] + })); + if (formErrors.category) { + setFormErrors(prev => ({ ...prev, category: undefined })); + } + }; + + const handleWeekdayChange = (weekdayId: string) => { + setFormData(prev => ({ + ...prev, + weekdays: prev.weekdays.includes(weekdayId) + ? prev.weekdays.filter(id => id !== weekdayId) + : [...prev.weekdays, weekdayId] + })); + if (formErrors.weekdays) { + setFormErrors(prev => ({ ...prev, weekdays: undefined })); + } + }; + + const validateEmail = (email: string): boolean => { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); + }; + + const handleEmailChange = (e: React.ChangeEvent) => { + const email = e.target.value; + setFormData(prev => ({ + ...prev, + email: email + })); + + if (email && !validateEmail(email)) { + setEmailError('유효하지 않은 이메일입니다.'); + } else { + setEmailError(''); + } + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + }; + + const handleVerifyEmail = async () => { + const errors: FormErrors = {}; + + if (formData.categories.length === 0) { + errors.category = '최소 하나의 카테고리를 선택해주세요.'; + } + + if (formData.weekdays.length === 0) { + errors.weekdays = '최소 하나의 요일을 선택해주세요.'; + } + + if (!formData.email) { + setEmailError('이메일을 입력해주세요.'); + return; + } + + if (!validateEmail(formData.email)) { + setEmailError('유효하지 않은 이메일입니다.'); + return; + } + + if (Object.keys(errors).length > 0) { + setFormErrors(errors); + return; + } + + setFormErrors({}); + setEmailError(''); + setIsLoading(true); + + setTimeout(() => { + setStep('verification'); + setIsLoading(false); + }, 1000); + }; + + const handleVerificationSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + setFormErrors({}); + setIsLoading(true); + + setTimeout(() => { + setStep('success'); + setIsLoading(false); + }, 1000); + }; + + const resetForm = () => { + setStep('form'); + setFormData({ categories: [], weekdays: [], email: '' }); + setVerificationCode(''); + setEmailError(''); + setFormErrors({}); + setIsLoading(false); + }; + + const renderFormStep = () => ( +
+
+

+ 구독 설정 +

+

+ 맞춤형 CS 문제를 받아보실 설정을 완료해주세요 +

+
+ +
+
+
+

관심 있는 분야를 선택해주세요

+
+ {categories.map(category => ( +
+ +
+

문제를 받고 싶은 요일을 선택해주세요

+
+ {weekdays.map(weekday => ( + + ))} +
+

* 최소 1개 이상 선택해주세요

+ {formErrors.weekdays && ( +

+ + + + {formErrors.weekdays} +

+ )} +
+ +
+

이메일 주소

+
+
+ + {emailError && ( +

+ + + + {emailError} +

+ )} +
+ +
+
+ +
+
+ ); + + const renderVerificationStep = () => ( +
+
+
+ + + +
+ +

인증 메일을 발송했습니다

+

{formData.email}로

+

인증번호가 포함된 메일을 발송했습니다.

+
+ +
+
+
+ { + setVerificationCode(e.target.value); + if (formErrors.verification) { + setFormErrors(prev => ({ ...prev, verification: undefined })); + } + }} + placeholder="인증번호 6자리 입력" + maxLength={6} + className="w-full px-4 py-4 text-center text-xl border border-gray-300 rounded-lg focus:ring-2 focus:ring-brand-400 focus:border-brand-400 outline-none tracking-widest" + /> + {formErrors.verification && ( +

+ + + + {formErrors.verification} +

+ )} +
+ + +
+ +
+ +
+
+
+ ); + + const renderSuccessStep = () => ( +
+
+
+ + + +
+ +

구독이 완료되었습니다!

+

+ 선택하신 요일에 맞춰
+ 기술 면접 문제를 발송해드리겠습니다. +

+
+ +
+
+
+

이메일: {formData.email}

+

분야: {formData.categories.map(cat => + categories.find(c => c.id === cat)?.label).join(', ')}

+

요일: {formData.weekdays.map(day => + weekdays.find(w => w.id === day)?.label).join(', ')}

+
+
+ + +
+
+ ); + + return ( +
+ + {step === 'form' && renderFormStep()} + {step === 'verification' && renderVerificationStep()} + {step === 'success' && renderSuccessStep()} + +
+ ); +}; + +export default SubscriptionPage; \ No newline at end of file diff --git a/src/components/VerificationEmailPage.tsx b/src/components/VerificationEmailPage.tsx new file mode 100644 index 0000000..dce34d7 --- /dev/null +++ b/src/components/VerificationEmailPage.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import Container from './common/Container'; +import Section from './common/Section'; + +const VerificationEmailPage: React.FC = () => { + // URL 파라미터나 상태에서 인증코드를 받을 수 있지만, 예시로 고정값 사용 + const verificationCode = "123456"; + + return ( +
+ +
+
+ {/* Header */} +
+
+ CS + 25 +
+
+ + + +
+

인증코드

+
+ + {/* Content */} +
+

+ CS25에서 요청하신 인증을 위해 아래의 코드를 입력해주세요. +

+ + {/* Verification Code */} +
+
+ {verificationCode} +
+

인증코드

+
+ + {/* Info Text */} +
+
+
+ + + +

중요 안내

+
+

해당 코드는 3분간 유효합니다.

+
+
+
+
+
+
+
+ ); +}; + +export default VerificationEmailPage; \ No newline at end of file diff --git a/src/components/common/Container.tsx b/src/components/common/Container.tsx new file mode 100644 index 0000000..278f5e4 --- /dev/null +++ b/src/components/common/Container.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +interface ContainerProps { + children: React.ReactNode; + className?: string; +} + +const Container: React.FC = ({ children, className = '' }) => { + return ( +
+ {children} +
+ ); +}; + +export default Container; \ No newline at end of file diff --git a/src/components/common/EmailTemplate.tsx b/src/components/common/EmailTemplate.tsx new file mode 100644 index 0000000..20c7a50 --- /dev/null +++ b/src/components/common/EmailTemplate.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; + +interface EmailTemplateProps { + toEmail: string; + quizLink: string; +} + +const EmailTemplate: React.FC = ({ toEmail, quizLink }) => { + const navigate = useNavigate(); + return ( +
+
+ {/* Header with Logo */} +
+
+ CS + 25 +
+

오늘의 CS 문제

+

AI가 준비한 맞춤형 문제를 확인하세요

+
+ + {/* Content */} +
+
+
+ + + +
+

오늘의 문제를 풀어보세요!

+

+ 안녕하세요! CS25에서 오늘의 맞춤형 CS 문제를 보내드립니다.
+ AI가 생성한 문제와 상세한 해설로 CS 지식을 향상시켜보세요. +

+
+ + {/* Action Button */} +
+ +
+ + {/* Today's Topic Preview */} +
+

오늘의 주제

+
+ 알고리즘 + 시간복잡도 +
+
+ + {/* Subscription Settings */} +
+ +
+ + {/* Footer */} +
+
+

+ 이 메일은 {toEmail} 계정으로 발송되었습니다. +

+

+ 매일 새로운 CS 지식으로 성장하는 개발자가 되어보세요! 🚀 +

+
+
+
+
+
+ ); +}; + +export default EmailTemplate; \ No newline at end of file diff --git a/src/components/common/Footer.tsx b/src/components/common/Footer.tsx new file mode 100644 index 0000000..e7704f7 --- /dev/null +++ b/src/components/common/Footer.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import Container from './Container'; + +const Footer: React.FC = () => { + return ( +
+ +

© 2025 CS25. All rights reserved.

+
+
+ ); +}; + +export default Footer; \ No newline at end of file diff --git a/src/components/common/Header.tsx b/src/components/common/Header.tsx new file mode 100644 index 0000000..cdb2b50 --- /dev/null +++ b/src/components/common/Header.tsx @@ -0,0 +1,59 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; + +interface HeaderProps { + onLoginClick: () => void; +} + +const Header: React.FC = ({ onLoginClick }) => { + const [isScrolled, setIsScrolled] = useState(false); + const navigate = useNavigate(); + + useEffect(() => { + const handleScroll = () => { + const scrollTop = window.scrollY; + setIsScrolled(scrollTop > 100); + }; + + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + return ( +
+
+
+
navigate('/')} + > + + CS + + + 25 + +
+
+ + +
+
+ ); +}; + +export default Header; \ No newline at end of file diff --git a/src/components/common/LoginForm.tsx b/src/components/common/LoginForm.tsx new file mode 100644 index 0000000..10a2809 --- /dev/null +++ b/src/components/common/LoginForm.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import SocialButton from './SocialButton'; + +const LoginForm: React.FC = () => { + return ( +
+

+ CS25와 함께
시작하기 +

+

+ 소셜 계정으로 간편하게 로그인하고
+ AI가 전하는 데일리 CS 지식을 받아보세요 +

+ + + 카카오로 시작하기 + + + + GitHub로 시작하기 + + + + 네이버로 시작하기 + + +
+ 로그인하시면 서비스 이용약관과
+ 개인정보처리방침에 동의하게 됩니다 +
+
+ ); +}; + +export default LoginForm; \ No newline at end of file diff --git a/src/components/common/Modal.tsx b/src/components/common/Modal.tsx new file mode 100644 index 0000000..46b21c4 --- /dev/null +++ b/src/components/common/Modal.tsx @@ -0,0 +1,56 @@ +import React, { useEffect } from 'react'; + +interface ModalProps { + isOpen: boolean; + onClose: () => void; + children: React.ReactNode; + title: string; +} + +const Modal: React.FC = ({ isOpen, onClose, children, title }) => { + useEffect(() => { + if (isOpen) { + document.body.style.overflow = 'hidden'; + } else { + document.body.style.overflow = 'unset'; + } + + return () => { + document.body.style.overflow = 'unset'; + }; + }, [isOpen]); + + if (!isOpen) return null; + + return ( +
+ {/* Backdrop */} +
+ + {/* Modal */} +
+ {/* Header */} +
+ +
+ + {/* Content */} +
+ {children} +
+
+
+ ); +}; + +export default Modal; \ No newline at end of file diff --git a/src/components/common/QuizComponent.tsx b/src/components/common/QuizComponent.tsx new file mode 100644 index 0000000..6177955 --- /dev/null +++ b/src/components/common/QuizComponent.tsx @@ -0,0 +1,68 @@ +import React, { useState } from 'react'; + +interface QuizOption { + id: number; + text: string; +} + +interface QuizComponentProps { + question: string; + options: QuizOption[]; + onSubmit: (selectedAnswer: number) => void; +} + +const QuizComponent: React.FC = ({ question, options, onSubmit }) => { + const [selectedAnswer, setSelectedAnswer] = useState(null); + + const handleOptionClick = (optionId: number) => { + setSelectedAnswer(optionId); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (selectedAnswer === null) { + alert("선택지를 먼저 클릭해주세요!"); + return; + } + onSubmit(selectedAnswer); + }; + + return ( +
+ {/* Question Box */} +
+ Q. {question} +
+ + {/* Quiz Form */} +
+ {/* Options Grid */} +
+ {options.map((option) => ( +
handleOptionClick(option.id)} + className={`border-2 p-4 rounded-xl cursor-pointer transition-all duration-200 hover:shadow-md ${ + selectedAnswer === option.id + ? 'border-brand-500 bg-brand-100 text-brand-800' + : 'border-gray-300 bg-white text-gray-700 hover:border-brand-300' + }`} + > + {option.text} +
+ ))} +
+ + {/* Submit Button */} + +
+
+ ); +}; + +export default QuizComponent; \ No newline at end of file diff --git a/src/components/common/Section.tsx b/src/components/common/Section.tsx new file mode 100644 index 0000000..daecffc --- /dev/null +++ b/src/components/common/Section.tsx @@ -0,0 +1,32 @@ +import React from 'react'; + +interface SectionProps { + children: React.ReactNode; + className?: string; + backgroundColor?: 'white' | 'gray' | 'brand'; +} + +const Section: React.FC = ({ + children, + className = '', + backgroundColor = 'white' +}) => { + const getBgColor = () => { + switch (backgroundColor) { + case 'gray': + return 'bg-gray-50'; + case 'brand': + return 'bg-gradient-to-br from-brand-400 to-purple-600'; + default: + return 'bg-white'; + } + }; + + return ( +
+ {children} +
+ ); +}; + +export default Section; \ No newline at end of file diff --git a/src/components/common/SocialButton.tsx b/src/components/common/SocialButton.tsx new file mode 100644 index 0000000..1887c0e --- /dev/null +++ b/src/components/common/SocialButton.tsx @@ -0,0 +1,31 @@ +import React from 'react'; + +interface SocialButtonProps { + provider: 'kakao' | 'github' | 'naver'; + href: string; + children: React.ReactNode; +} + +const SocialButton: React.FC = ({ provider, href, children }) => { + const getButtonStyles = () => { + switch (provider) { + case 'kakao': + return 'bg-yellow-400 text-black border-yellow-400 hover:bg-yellow-500 hover:shadow-yellow-200 shadow-yellow-100'; + case 'github': + return 'bg-gray-800 text-white border-gray-800 hover:bg-gray-700 hover:shadow-gray-400 shadow-gray-300'; + case 'naver': + return 'bg-green-500 text-white border-green-500 hover:bg-green-600 hover:shadow-green-200 shadow-green-100'; + } + }; + + return ( + + {children} + + ); +}; + +export default SocialButton; \ No newline at end of file diff --git a/src/components/common/SubscriptionModal.tsx b/src/components/common/SubscriptionModal.tsx new file mode 100644 index 0000000..f434282 --- /dev/null +++ b/src/components/common/SubscriptionModal.tsx @@ -0,0 +1,409 @@ +import React, { useState } from 'react'; +import Modal from './Modal'; + +interface SubscriptionModalProps { + isOpen: boolean; + onClose: () => void; +} + +interface FormData { + categories: string[]; + weekdays: string[]; + email: string; +} + +interface FormErrors { + category?: string; + weekdays?: string; + verification?: string; +} + +type Step = 'form' | 'verification' | 'success'; + +const SubscriptionModal: React.FC = ({ isOpen, onClose }) => { + const [step, setStep] = useState('form'); + const [isLoading, setIsLoading] = useState(false); + const [verificationCode, setVerificationCode] = useState(''); + const [emailError, setEmailError] = useState(''); + const [formErrors, setFormErrors] = useState({}); + const [formData, setFormData] = useState({ + categories: [], + weekdays: [], + email: '' + }); + + const categories = [ + { id: 'frontend', label: '프론트엔드' }, + { id: 'backend', label: '백엔드' }, + { id: 'certification', label: '자격증' } + ]; + + const weekdays = [ + { id: 'MONDAY', label: '월' }, + { id: 'TUESDAY', label: '화' }, + { id: 'WEDNESDAY', label: '수' }, + { id: 'THURSDAY', label: '목' }, + { id: 'FRIDAY', label: '금' }, + { id: 'SATURDAY', label: '토' }, + { id: 'SUNDAY', label: '일' } + ]; + + const handleCategoryChange = (categoryId: string) => { + setFormData(prev => ({ + ...prev, + categories: [categoryId] // 단일 선택만 허용 + })); + if (formErrors.category) { + setFormErrors(prev => ({ ...prev, category: undefined })); + } + }; + + const handleWeekdayChange = (weekdayId: string) => { + setFormData(prev => ({ + ...prev, + weekdays: prev.weekdays.includes(weekdayId) + ? prev.weekdays.filter(id => id !== weekdayId) + : [...prev.weekdays, weekdayId] + })); + if (formErrors.weekdays) { + setFormErrors(prev => ({ ...prev, weekdays: undefined })); + } + }; + + const validateEmail = (email: string): boolean => { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); + }; + + const handleEmailChange = (e: React.ChangeEvent) => { + const email = e.target.value; + setFormData(prev => ({ + ...prev, + email: email + })); + + // 실시간 이메일 유효성 검사 + if (email && !validateEmail(email)) { + setEmailError('유효하지 않은 이메일입니다.'); + } else { + setEmailError(''); + } + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + // 폼 제출은 이메일 인증하기 버튼에서 처리됨 + }; + + const handleVerifyEmail = async () => { + const errors: FormErrors = {}; + + if (formData.categories.length === 0) { + errors.category = '최소 하나의 카테고리를 선택해주세요.'; + } + + if (formData.weekdays.length === 0) { + errors.weekdays = '최소 하나의 요일을 선택해주세요.'; + } + + if (!formData.email) { + setEmailError('이메일을 입력해주세요.'); + return; + } + + if (!validateEmail(formData.email)) { + setEmailError('유효하지 않은 이메일입니다.'); + return; + } + + if (Object.keys(errors).length > 0) { + setFormErrors(errors); + return; + } + + setFormErrors({}); + setEmailError(''); + setIsLoading(true); + + // 시뮬레이션: 1초 후 인증 메일 발송 + setTimeout(() => { + setStep('verification'); + setIsLoading(false); + }, 1000); + }; + + const handleVerificationSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + // 인증번호 입력 시 바로 성공으로 간주 + setFormErrors({}); + setIsLoading(true); + + // 시뮬레이션: 1초 후 구독 완료 + setTimeout(() => { + setStep('success'); + setIsLoading(false); + }, 1000); + }; + + const handleModalClose = () => { + setStep('form'); + setFormData({ categories: [], weekdays: [], email: '' }); + setVerificationCode(''); + setEmailError(''); + setFormErrors({}); + setIsLoading(false); + onClose(); + }; + + + const renderFormStep = () => ( +
+ {/* 질문 카테고리 */} +
+

관심 있는 분야를 선택해주세요

+
+ {categories.map(category => ( +
+ + {/* 문제 받는 주기 */} +
+

문제를 받고 싶은 요일을 선택해주세요

+
+ {weekdays.map(weekday => ( + + ))} +
+

* 최소 1개 이상 선택해주세요

+ {formErrors.weekdays && ( +

+ + + + {formErrors.weekdays} +

+ )} +
+ + {/* 이메일 입력 */} +
+

이메일 주소

+
+
+ + {emailError && ( +

+ + + + {emailError} +

+ )} +
+ +
+
+ + + ); + + const renderVerificationStep = () => ( +
+
+ + + +
+ +
+

인증 메일을 발송했습니다

+

{formData.email}로

+

인증번호가 포함된 메일을 발송했습니다.

+
+ +
+
+ { + setVerificationCode(e.target.value); + if (formErrors.verification) { + setFormErrors(prev => ({ ...prev, verification: undefined })); + } + }} + placeholder="인증번호 6자리 입력" + maxLength={6} + className="w-full px-4 py-3 text-center text-lg border border-gray-300 rounded-lg focus:ring-2 focus:ring-brand-400 focus:border-brand-400 outline-none tracking-widest" + /> + {formErrors.verification && ( +

+ + + + {formErrors.verification} +

+ )} +
+ + +
+ + +
+ ); + + const renderSuccessStep = () => ( +
+
+ + + +
+ +
+

구독이 완료되었습니다!

+

+ 선택하신 요일에 맞춰
+ 기술 면접 문제를 발송해드리겠습니다. +

+ +
+
+

이메일: {formData.email}

+

분야: {formData.categories.map(cat => + categories.find(c => c.id === cat)?.label).join(', ')}

+

요일: {formData.weekdays.map(day => + weekdays.find(w => w.id === day)?.label).join(', ')}

+
+
+
+ + +
+ ); + + return ( + + {step === 'form' && renderFormStep()} + {step === 'verification' && renderVerificationStep()} + {step === 'success' && renderSuccessStep()} + + ); +}; + +export default SubscriptionModal; \ No newline at end of file diff --git a/src/components/common/SubscriptionSettings.tsx b/src/components/common/SubscriptionSettings.tsx new file mode 100644 index 0000000..fc0e116 --- /dev/null +++ b/src/components/common/SubscriptionSettings.tsx @@ -0,0 +1,175 @@ +import React, { useState } from 'react'; +import Modal from './Modal'; + +interface SubscriptionSettingsProps { + isOpen: boolean; + onClose: () => void; +} + +interface SettingsData { + email: string; + categories: string[]; + difficulty: string; + frequency: string; + timePreference: string; +} + +const SubscriptionSettings: React.FC = ({ isOpen, onClose }) => { + const [settings, setSettings] = useState({ + email: 'user@example.com', // 인증된 이메일 (수정 불가) + categories: ['알고리즘', '자료구조'], + difficulty: 'intermediate', + frequency: 'daily', + timePreference: 'morning' + }); + + const [isSaving, setIsSaving] = useState(false); + + const availableCategories = [ + '알고리즘', '자료구조', '운영체제', '네트워크', + '데이터베이스', '컴퓨터구조', '소프트웨어공학', '보안' + ]; + + const handleCategoryChange = (category: string) => { + setSettings(prev => ({ + ...prev, + categories: prev.categories.includes(category) + ? prev.categories.filter(c => c !== category) + : [...prev.categories, category] + })); + }; + + const handleSave = async () => { + setIsSaving(true); + + // 실제로는 API 호출을 하겠지만, 여기서는 시뮬레이션 + setTimeout(() => { + setIsSaving(false); + alert('설정이 저장되었습니다!'); + onClose(); + }, 1000); + }; + + return ( + +
+ {/* Email Section (Read-only) */} +
+ +
+ + + + + + 인증됨 + +
+
+ + {/* Categories */} +
+ +
+ {availableCategories.map((category) => ( + + ))} +
+
+ + {/* Difficulty Level */} +
+ + +
+ + {/* Frequency */} +
+ + +
+ + {/* Time Preference */} +
+ + +
+ + {/* Action Buttons */} +
+ + +
+ + {/* Unsubscribe */} +
+ +
+
+
+ ); +}; + +export default SubscriptionSettings; \ No newline at end of file diff --git a/src/components/sections/FeaturesSection.tsx b/src/components/sections/FeaturesSection.tsx new file mode 100644 index 0000000..ed2204e --- /dev/null +++ b/src/components/sections/FeaturesSection.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import Container from '../common/Container'; +import Section from '../common/Section'; + +const FeaturesSection: React.FC = () => { + const features = [ + { + icon: ( +
+ + + +
+ ), + title: 'AI 문제 생성', + description: '개인 수준에 맞는 CS 문제를 AI가 실시간 생성', + details: '학습 패턴을 분석해 최적화된 문제 제공' + }, + { + icon: ( +
+ + + +
+ ), + title: 'AI 해설', + description: '문제와 함께 AI가 생성한 상세한 해설 제공', + details: '코드 예시와 개념 설명으로 완벽 이해' + }, + { + icon: ( +
+ + + +
+ ), + title: '데일리 메일', + description: '매일 아침 새로운 CS 지식을 메일로 전송', + details: '알고리즘, 자료구조, 운영체제, 네트워크 등' + } + ]; + + return ( +
+ +
+

+ AI 기반 CS 학습의 혁신 +

+

+ AI가 생성하고 해설하는 개인화된 CS 지식 학습 경험 +

+
+ +
+ {features.map((feature, index) => ( +
+
+ {feature.icon} +
+ +

+ {feature.title} +

+ +

+ {feature.description} +

+ +

+ {feature.details} +

+
+ ))} +
+
+
+ ); +}; + +export default FeaturesSection; \ No newline at end of file diff --git a/src/components/sections/HeroSection.tsx b/src/components/sections/HeroSection.tsx new file mode 100644 index 0000000..3978d2f --- /dev/null +++ b/src/components/sections/HeroSection.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import Container from '../common/Container'; +import Section from '../common/Section'; + +interface HeroSectionProps { + onSubscribeClick: () => void; +} + +const HeroSection: React.FC = ({ onSubscribeClick }) => { + return ( +
+ +
+
+ 🤖 AI가 생성하고 해설하는 CS 지식 +
+ +

+ AI가 전하는
+ + 데일리 CS 지식 +
+ 메일로 만나보세요! +

+ +

+ AI가 매일 새로운 CS 문제를 생성하고 상세히 해설
+ 개인 수준에 맞는 알고리즘, 자료구조, 운영체제 등 +

+ +
+ + +
+
+
+
+
+
+ 이미 1,000+ 취준생이 사용 중 +
+
+
+
+
+ ); +}; + +export default HeroSection; \ No newline at end of file diff --git a/src/components/sections/QuizSection.tsx b/src/components/sections/QuizSection.tsx new file mode 100644 index 0000000..55f9208 --- /dev/null +++ b/src/components/sections/QuizSection.tsx @@ -0,0 +1,100 @@ +import React, { useState } from 'react'; +import Container from '../common/Container'; +import Section from '../common/Section'; +import QuizComponent from '../common/QuizComponent'; + +const QuizSection: React.FC = () => { + const [showResult, setShowResult] = useState(false); + const [userAnswer, setUserAnswer] = useState(null); + + // 샘플 퀴즈 데이터 + const sampleQuiz = { + question: "다음 중 시간 복잡도가 O(log n)인 알고리즘은?", + options: [ + { id: 1, text: "버블 정렬 (Bubble Sort)" }, + { id: 2, text: "이진 탐색 (Binary Search)" }, + { id: 3, text: "선형 탐색 (Linear Search)" }, + { id: 4, text: "삽입 정렬 (Insertion Sort)" } + ], + correctAnswer: 2, + explanation: "이진 탐색은 정렬된 배열에서 중간값과 비교하여 탐색 범위를 절반씩 줄여나가므로 O(log n)의 시간 복잡도를 가집니다." + }; + + const handleQuizSubmit = (selectedAnswer: number) => { + setUserAnswer(selectedAnswer); + setShowResult(true); + }; + + const resetQuiz = () => { + setShowResult(false); + setUserAnswer(null); + }; + + if (showResult) { + const isCorrect = userAnswer === sampleQuiz.correctAnswer; + return ( +
+ +
+
+ + {isCorrect ? '🎉 정답입니다!' : '❌ 틀렸습니다!'} + +
+ +
+

해설

+

{sampleQuiz.explanation}

+
+

+ 정답: {sampleQuiz.options.find(opt => opt.id === sampleQuiz.correctAnswer)?.text} +

+
+
+ + +
+
+
+ ); + } + + return ( +
+ +
+
+ 📚 오늘의 CS 문제 +
+ +

+ AI가 준비한 오늘의 문제 +

+ +

+ 매일 새로운 CS 지식을 확인하고 실력을 향상시켜보세요 +

+
+ +
+ +
+
+
+ ); +}; + +export default QuizSection; \ No newline at end of file diff --git a/src/components/sections/TodayEmailFormSection.tsx b/src/components/sections/TodayEmailFormSection.tsx new file mode 100644 index 0000000..e37e21c --- /dev/null +++ b/src/components/sections/TodayEmailFormSection.tsx @@ -0,0 +1,47 @@ +import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import Container from '../common/Container'; +import Section from '../common/Section'; +import EmailTemplate from '../common/EmailTemplate'; +import SubscriptionSettings from '../common/SubscriptionSettings'; + +const TodayEmailFormSection: React.FC = () => { + const [isSettingsOpen, setIsSettingsOpen] = useState(false); + const navigate = useNavigate(); + + return ( +
+ +
+
+ 📧 메일 미리보기 +
+ +

+ 매일 받게 될 CS 메일 +

+ +

+ 이런 형태의 맞춤형 메일을 매일 받아보실 수 있습니다 +

+
+ + {/* Email Template Preview */} +
+ +
+ + {/* Settings Modal */} + setIsSettingsOpen(false)} + /> +
+
+ ); +}; + +export default TodayEmailFormSection; \ No newline at end of file diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..8ca67aa --- /dev/null +++ b/src/index.css @@ -0,0 +1,19 @@ +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap'); + +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + margin: 0; + font-family: 'Pretendard Variable', 'Pretendard', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..032464f --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import './index.css'; +import App from './App'; +import reportWebVitals from './reportWebVitals'; + +const root = ReactDOM.createRoot( + document.getElementById('root') as HTMLElement +); +root.render( + + + +); + +// If you want to start measuring performance in your app, pass a function +// to log results (for example: reportWebVitals(console.log)) +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals +reportWebVitals(); diff --git a/src/logo.svg b/src/logo.svg new file mode 100644 index 0000000..9dfc1c0 --- /dev/null +++ b/src/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/reportWebVitals.ts b/src/reportWebVitals.ts new file mode 100644 index 0000000..49a2a16 --- /dev/null +++ b/src/reportWebVitals.ts @@ -0,0 +1,15 @@ +import { ReportHandler } from 'web-vitals'; + +const reportWebVitals = (onPerfEntry?: ReportHandler) => { + if (onPerfEntry && onPerfEntry instanceof Function) { + import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + getCLS(onPerfEntry); + getFID(onPerfEntry); + getFCP(onPerfEntry); + getLCP(onPerfEntry); + getTTFB(onPerfEntry); + }); + } +}; + +export default reportWebVitals; diff --git a/src/setupTests.ts b/src/setupTests.ts new file mode 100644 index 0000000..8f2609b --- /dev/null +++ b/src/setupTests.ts @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom'; diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..e53982b --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,44 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + "./src/**/*.{js,jsx,ts,tsx}", + "./public/index.html", + ], + theme: { + extend: { + fontFamily: { + 'pretendard': ['Pretendard', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'sans-serif'], + 'noto': ['Noto Sans KR', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'sans-serif'], + }, + colors: { + 'brand': { + 50: '#eff6ff', + 100: '#dbeafe', + 200: '#bfdbfe', + 300: '#93c5fd', + 400: '#60a5fa', + 500: '#3b82f6', + 600: '#2563eb', + 700: '#1d4ed8', + 800: '#1e40af', + 900: '#1e3a8a', + }, + 'navy': { + 50: '#f8fafc', + 100: '#f1f5f9', + 200: '#e2e8f0', + 300: '#cbd5e1', + 400: '#94a3b8', + 500: '#64748b', + 600: '#475569', + 700: '#334155', + 800: '#1e293b', + 900: '#0f172a', + }, + 'kakao': '#FEE500', + 'naver': '#03C75A', + }, + }, + }, + plugins: [], +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a273b0c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": [ + "src" + ] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..1708e65 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { + port: 3002, + open: true + }, + build: { + outDir: 'build' + } +}) \ No newline at end of file From 5513cea523819173e7c32f47b15b1f4bd75cdfe8 Mon Sep 17 00:00:00 2001 From: wannabeing Date: Thu, 12 Jun 2025 18:49:46 +0900 Subject: [PATCH 02/47] =?UTF-8?q?feat:=20ReactQuery=20=EC=A0=81=EC=9A=A9?= =?UTF-8?q?=20=EB=B0=8F=20=EB=B0=B1=EC=97=94=EB=93=9C=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 31 +- package.json | 1 + src/App.tsx | 62 +-- src/components/LandingPage.tsx | 2 + src/components/SubscriptionPage.tsx | 415 ------------------ src/components/common/EmailTemplate.tsx | 2 +- src/components/common/Header.tsx | 10 +- src/components/common/LoginForm.tsx | 13 +- src/components/common/Modal.tsx | 47 +- src/components/common/ModalManager.tsx | 30 ++ src/components/common/SocialButton.tsx | 10 +- src/components/common/SubscriptionModal.tsx | 222 ++++++++-- src/components/sections/QuizSection.tsx | 77 +++- .../sections/TodayEmailFormSection.tsx | 4 +- src/contexts/ModalContext.tsx | 80 ++++ src/hooks/index.ts | 4 + src/hooks/useAuth.ts | 53 +++ src/hooks/useModal.tsx | 72 +++ src/hooks/useQuiz.ts | 58 +++ src/hooks/useSubscription.ts | 86 ++++ src/utils/api.ts | 153 +++++++ vite.config.ts | 10 +- 22 files changed, 921 insertions(+), 521 deletions(-) delete mode 100644 src/components/SubscriptionPage.tsx create mode 100644 src/components/common/ModalManager.tsx create mode 100644 src/contexts/ModalContext.tsx create mode 100644 src/hooks/index.ts create mode 100644 src/hooks/useAuth.ts create mode 100644 src/hooks/useModal.tsx create mode 100644 src/hooks/useQuiz.ts create mode 100644 src/hooks/useSubscription.ts create mode 100644 src/utils/api.ts diff --git a/package-lock.json b/package-lock.json index 539b6b3..8ddd820 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,14 @@ { - "name": "cs25-fronted", + "name": "cs25-fe", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "cs25-fronted", + "name": "cs25-fe", "version": "0.1.0", "dependencies": { + "@tanstack/react-query": "^5.80.7", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", @@ -1252,6 +1253,32 @@ "win32" ] }, + "node_modules/@tanstack/query-core": { + "version": "5.80.7", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.80.7.tgz", + "integrity": "sha512-s09l5zeUKC8q7DCCCIkVSns8zZrK4ZDT6ryEjxNBFi68G4z2EBobBS7rdOY3r6W1WbUDpc1fe5oY+YO/+2UVUg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.80.7", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.80.7.tgz", + "integrity": "sha512-u2F0VK6+anItoEvB3+rfvTO9GEh2vb00Je05OwlUe/A0lkJBgW1HckiY3f9YZa+jx6IOe4dHPh10dyp9aY3iRQ==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.80.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", diff --git a/package.json b/package.json index d315126..91fc07a 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@tanstack/react-query": "^5.80.7", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", diff --git a/src/App.tsx b/src/App.tsx index 0d28363..9a22581 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,46 +1,52 @@ import { useState } from 'react'; import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import LandingPage from './components/LandingPage'; import QuizSection from './components/sections/QuizSection'; import TodayEmailFormSection from './components/sections/TodayEmailFormSection'; -import SubscriptionPage from './components/SubscriptionPage'; import VerificationEmailPage from './components/VerificationEmailPage'; import Header from './components/common/Header'; import Footer from './components/common/Footer'; import SubscriptionModal from './components/common/SubscriptionModal'; -import LoginForm from './components/common/LoginForm'; -import Modal from './components/common/Modal'; +import { ModalProvider } from './contexts/ModalContext'; +import ModalManager from './components/common/ModalManager'; import './App.css'; +// React Query client 생성 +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 1000 * 60 * 5, // 5분 + refetchOnWindowFocus: false, + }, + }, +}); + function App() { const [isSubscriptionModalOpen, setIsSubscriptionModalOpen] = useState(false); - const [isLoginModalOpen, setIsLoginModalOpen] = useState(false); return ( -
- -
setIsLoginModalOpen(true)} /> - - setIsSubscriptionModalOpen(true)} />} /> - } /> - } /> - } /> - } /> - -
- setIsSubscriptionModalOpen(false)} - /> - setIsLoginModalOpen(false)} - title="로그인" - > - - - -
+ + +
+ +
+ + setIsSubscriptionModalOpen(true)} />} /> + } /> + } /> + } /> + +
+ setIsSubscriptionModalOpen(false)} + /> + + +
+
+
); } diff --git a/src/components/LandingPage.tsx b/src/components/LandingPage.tsx index b03139c..105bb37 100644 --- a/src/components/LandingPage.tsx +++ b/src/components/LandingPage.tsx @@ -1,6 +1,7 @@ import React from 'react'; import HeroSection from './sections/HeroSection'; import FeaturesSection from './sections/FeaturesSection'; +import TodayEmailFormSection from './sections/TodayEmailFormSection'; interface LandingPageProps { onSubscribeClick: () => void; @@ -10,6 +11,7 @@ const LandingPage: React.FC = ({ onSubscribeClick }) => { return ( <> + ); diff --git a/src/components/SubscriptionPage.tsx b/src/components/SubscriptionPage.tsx deleted file mode 100644 index ef08d6d..0000000 --- a/src/components/SubscriptionPage.tsx +++ /dev/null @@ -1,415 +0,0 @@ -import React, { useState } from 'react'; -import Container from './common/Container'; -import Section from './common/Section'; - -interface FormData { - categories: string[]; - weekdays: string[]; - email: string; -} - -interface FormErrors { - category?: string; - weekdays?: string; - verification?: string; -} - -type Step = 'form' | 'verification' | 'success'; - -const SubscriptionPage: React.FC = () => { - const [step, setStep] = useState('form'); - const [isLoading, setIsLoading] = useState(false); - const [verificationCode, setVerificationCode] = useState(''); - const [emailError, setEmailError] = useState(''); - const [formErrors, setFormErrors] = useState({}); - const [formData, setFormData] = useState({ - categories: [], - weekdays: [], - email: '' - }); - - const categories = [ - { id: 'frontend', label: '프론트엔드' }, - { id: 'backend', label: '백엔드' }, - { id: 'certification', label: '자격증' } - ]; - - const weekdays = [ - { id: 'MONDAY', label: '월' }, - { id: 'TUESDAY', label: '화' }, - { id: 'WEDNESDAY', label: '수' }, - { id: 'THURSDAY', label: '목' }, - { id: 'FRIDAY', label: '금' }, - { id: 'SATURDAY', label: '토' }, - { id: 'SUNDAY', label: '일' } - ]; - - const handleCategoryChange = (categoryId: string) => { - setFormData(prev => ({ - ...prev, - categories: [categoryId] - })); - if (formErrors.category) { - setFormErrors(prev => ({ ...prev, category: undefined })); - } - }; - - const handleWeekdayChange = (weekdayId: string) => { - setFormData(prev => ({ - ...prev, - weekdays: prev.weekdays.includes(weekdayId) - ? prev.weekdays.filter(id => id !== weekdayId) - : [...prev.weekdays, weekdayId] - })); - if (formErrors.weekdays) { - setFormErrors(prev => ({ ...prev, weekdays: undefined })); - } - }; - - const validateEmail = (email: string): boolean => { - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - return emailRegex.test(email); - }; - - const handleEmailChange = (e: React.ChangeEvent) => { - const email = e.target.value; - setFormData(prev => ({ - ...prev, - email: email - })); - - if (email && !validateEmail(email)) { - setEmailError('유효하지 않은 이메일입니다.'); - } else { - setEmailError(''); - } - }; - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - }; - - const handleVerifyEmail = async () => { - const errors: FormErrors = {}; - - if (formData.categories.length === 0) { - errors.category = '최소 하나의 카테고리를 선택해주세요.'; - } - - if (formData.weekdays.length === 0) { - errors.weekdays = '최소 하나의 요일을 선택해주세요.'; - } - - if (!formData.email) { - setEmailError('이메일을 입력해주세요.'); - return; - } - - if (!validateEmail(formData.email)) { - setEmailError('유효하지 않은 이메일입니다.'); - return; - } - - if (Object.keys(errors).length > 0) { - setFormErrors(errors); - return; - } - - setFormErrors({}); - setEmailError(''); - setIsLoading(true); - - setTimeout(() => { - setStep('verification'); - setIsLoading(false); - }, 1000); - }; - - const handleVerificationSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - - setFormErrors({}); - setIsLoading(true); - - setTimeout(() => { - setStep('success'); - setIsLoading(false); - }, 1000); - }; - - const resetForm = () => { - setStep('form'); - setFormData({ categories: [], weekdays: [], email: '' }); - setVerificationCode(''); - setEmailError(''); - setFormErrors({}); - setIsLoading(false); - }; - - const renderFormStep = () => ( -
-
-

- 구독 설정 -

-

- 맞춤형 CS 문제를 받아보실 설정을 완료해주세요 -

-
- -
-
-
-

관심 있는 분야를 선택해주세요

-
- {categories.map(category => ( -
- -
-

문제를 받고 싶은 요일을 선택해주세요

-
- {weekdays.map(weekday => ( - - ))} -
-

* 최소 1개 이상 선택해주세요

- {formErrors.weekdays && ( -

- - - - {formErrors.weekdays} -

- )} -
- -
-

이메일 주소

-
-
- - {emailError && ( -

- - - - {emailError} -

- )} -
- -
-
- -
-
- ); - - const renderVerificationStep = () => ( -
-
-
- - - -
- -

인증 메일을 발송했습니다

-

{formData.email}로

-

인증번호가 포함된 메일을 발송했습니다.

-
- -
-
-
- { - setVerificationCode(e.target.value); - if (formErrors.verification) { - setFormErrors(prev => ({ ...prev, verification: undefined })); - } - }} - placeholder="인증번호 6자리 입력" - maxLength={6} - className="w-full px-4 py-4 text-center text-xl border border-gray-300 rounded-lg focus:ring-2 focus:ring-brand-400 focus:border-brand-400 outline-none tracking-widest" - /> - {formErrors.verification && ( -

- - - - {formErrors.verification} -

- )} -
- - -
- -
- -
-
-
- ); - - const renderSuccessStep = () => ( -
-
-
- - - -
- -

구독이 완료되었습니다!

-

- 선택하신 요일에 맞춰
- 기술 면접 문제를 발송해드리겠습니다. -

-
- -
-
-
-

이메일: {formData.email}

-

분야: {formData.categories.map(cat => - categories.find(c => c.id === cat)?.label).join(', ')}

-

요일: {formData.weekdays.map(day => - weekdays.find(w => w.id === day)?.label).join(', ')}

-
-
- - -
-
- ); - - return ( -
- - {step === 'form' && renderFormStep()} - {step === 'verification' && renderVerificationStep()} - {step === 'success' && renderSuccessStep()} - -
- ); -}; - -export default SubscriptionPage; \ No newline at end of file diff --git a/src/components/common/EmailTemplate.tsx b/src/components/common/EmailTemplate.tsx index 20c7a50..93da3d5 100644 --- a/src/components/common/EmailTemplate.tsx +++ b/src/components/common/EmailTemplate.tsx @@ -9,7 +9,7 @@ interface EmailTemplateProps { const EmailTemplate: React.FC = ({ toEmail, quizLink }) => { const navigate = useNavigate(); return ( -
+
{/* Header with Logo */}
diff --git a/src/components/common/Header.tsx b/src/components/common/Header.tsx index cdb2b50..ffcd1ab 100644 --- a/src/components/common/Header.tsx +++ b/src/components/common/Header.tsx @@ -1,11 +1,9 @@ import React, { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; +import { useLoginModal } from '../../hooks/useModal'; -interface HeaderProps { - onLoginClick: () => void; -} - -const Header: React.FC = ({ onLoginClick }) => { +const Header: React.FC = () => { + const { openLoginModal } = useLoginModal(); const [isScrolled, setIsScrolled] = useState(false); const navigate = useNavigate(); @@ -46,7 +44,7 @@ const Header: React.FC = ({ onLoginClick }) => {
-
+ {(title || closable) && ( +
+ {title &&

{title}

} + {closable && ( + + )} +
+ )} {/* Content */} -
+
{children}
diff --git a/src/components/common/ModalManager.tsx b/src/components/common/ModalManager.tsx new file mode 100644 index 0000000..2b0d791 --- /dev/null +++ b/src/components/common/ModalManager.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { useModalContext } from '../../contexts/ModalContext'; +import Modal from './Modal'; + +const ModalManager: React.FC = () => { + const { modals, closeModal } = useModalContext(); + + if (modals.length === 0) { + return null; + } + + return ( + <> + {modals.map((modal) => ( + closeModal(modal.id)} + title={modal.title} + size={modal.size} + closable={modal.closable} + > + {modal.content} + + ))} + + ); +}; + +export default ModalManager; \ No newline at end of file diff --git a/src/components/common/SocialButton.tsx b/src/components/common/SocialButton.tsx index 1887c0e..98ac567 100644 --- a/src/components/common/SocialButton.tsx +++ b/src/components/common/SocialButton.tsx @@ -2,11 +2,11 @@ import React from 'react'; interface SocialButtonProps { provider: 'kakao' | 'github' | 'naver'; - href: string; + onClick: () => void; children: React.ReactNode; } -const SocialButton: React.FC = ({ provider, href, children }) => { +const SocialButton: React.FC = ({ provider, onClick, children }) => { const getButtonStyles = () => { switch (provider) { case 'kakao': @@ -19,12 +19,12 @@ const SocialButton: React.FC = ({ provider, href, children }) }; return ( - {children} - + ); }; diff --git a/src/components/common/SubscriptionModal.tsx b/src/components/common/SubscriptionModal.tsx index f434282..7b71cc6 100644 --- a/src/components/common/SubscriptionModal.tsx +++ b/src/components/common/SubscriptionModal.tsx @@ -1,5 +1,7 @@ import React, { useState } from 'react'; import Modal from './Modal'; +import { useQuizCategories } from '../../hooks/useQuiz'; +import { useRequestEmailVerification, useVerifyCode, useCreateSubscription, useCheckEmail } from '../../hooks'; interface SubscriptionModalProps { isOpen: boolean; @@ -10,11 +12,13 @@ interface FormData { categories: string[]; weekdays: string[]; email: string; + period: string; } interface FormErrors { category?: string; weekdays?: string; + period?: string; verification?: string; } @@ -22,21 +26,45 @@ type Step = 'form' | 'verification' | 'success'; const SubscriptionModal: React.FC = ({ isOpen, onClose }) => { const [step, setStep] = useState('form'); - const [isLoading, setIsLoading] = useState(false); const [verificationCode, setVerificationCode] = useState(''); const [emailError, setEmailError] = useState(''); const [formErrors, setFormErrors] = useState({}); const [formData, setFormData] = useState({ categories: [], weekdays: [], - email: '' + email: '', + period: '' }); - const categories = [ - { id: 'frontend', label: '프론트엔드' }, - { id: 'backend', label: '백엔드' }, - { id: 'certification', label: '자격증' } - ]; + const { data: categoriesData, isLoading: categoriesLoading, error: categoriesError } = useQuizCategories(isOpen); + + // React Query 훅들 + const checkEmailMutation = useCheckEmail(); + const emailVerificationMutation = useRequestEmailVerification(); + const verifyCodeMutation = useVerifyCode(); + const createSubscriptionMutation = useCreateSubscription(); + + const getCategoryLabel = (category: string) => { + switch (category.toLowerCase()) { + case 'frontend': + return '프론트엔드'; + case 'backend': + return '백엔드'; + case 'certification': + return '자격증'; + default: + return category; + } + }; + + // API 응답이 {data: []} 형태인지 확인하고 처리 + const categoryList = (categoriesData && typeof categoriesData === 'object' && 'data' in categoriesData) + ? categoriesData.data + : categoriesData; + const categories = Array.isArray(categoryList) ? categoryList.map((category: string) => ({ + id: category, + label: getCategoryLabel(category) + })) : []; const weekdays = [ { id: 'MONDAY', label: '월' }, @@ -48,6 +76,13 @@ const SubscriptionModal: React.FC = ({ isOpen, onClose } { id: 'SUNDAY', label: '일' } ]; + const periods = [ + { id: 'ONE_MONTH', label: '1개월' }, + { id: 'THREE_MONTH', label: '3개월' }, + { id: 'SIX_MONTH', label: '6개월' }, + { id: 'ONE_YEAR', label: '1년' } + ]; + const handleCategoryChange = (categoryId: string) => { setFormData(prev => ({ ...prev, @@ -70,6 +105,16 @@ const SubscriptionModal: React.FC = ({ isOpen, onClose } } }; + const handlePeriodChange = (periodId: string) => { + setFormData(prev => ({ + ...prev, + period: periodId + })); + if (formErrors.period) { + setFormErrors(prev => ({ ...prev, period: undefined })); + } + }; + const validateEmail = (email: string): boolean => { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); @@ -106,6 +151,10 @@ const SubscriptionModal: React.FC = ({ isOpen, onClose } errors.weekdays = '최소 하나의 요일을 선택해주세요.'; } + if (!formData.period) { + errors.period = '구독 기간을 선택해주세요.'; + } + if (!formData.email) { setEmailError('이메일을 입력해주세요.'); return; @@ -123,36 +172,76 @@ const SubscriptionModal: React.FC = ({ isOpen, onClose } setFormErrors({}); setEmailError(''); - setIsLoading(true); - // 시뮬레이션: 1초 후 인증 메일 발송 - setTimeout(() => { - setStep('verification'); - setIsLoading(false); - }, 1000); + // 1단계: 이메일 중복 체크 + checkEmailMutation.mutate(formData.email, { + onSuccess: () => { + // 2단계: 이메일이 사용 가능하면 인증번호 전송 + emailVerificationMutation.mutate(formData.email, { + onSuccess: () => { + setStep('verification'); + }, + onError: (error) => { + console.error('이메일 인증 요청 실패:', error); + setEmailError('이메일 인증 요청에 실패했습니다. 다시 시도해주세요.'); + }, + }); + }, + onError: (error) => { + console.error('이메일 중복 체크 실패:', error); + setEmailError('이미 구독 중인 이메일입니다.'); + }, + }); }; const handleVerificationSubmit = async (e: React.FormEvent) => { e.preventDefault(); - // 인증번호 입력 시 바로 성공으로 간주 + if (!verificationCode) { + setFormErrors({ verification: '인증번호를 입력해주세요.' }); + return; + } + setFormErrors({}); - setIsLoading(true); - // 시뮬레이션: 1초 후 구독 완료 - setTimeout(() => { - setStep('success'); - setIsLoading(false); - }, 1000); + // 인증 코드 확인 + verifyCodeMutation.mutate( + { email: formData.email, code: verificationCode }, + { + onSuccess: () => { + // 인증 성공 후 구독 생성 + createSubscriptionMutation.mutate( + { + email: formData.email, + category: formData.categories[0], // 첫 번째 선택된 카테고리만 전송 + days: formData.weekdays, + period: formData.period, + }, + { + onSuccess: () => { + setStep('success'); + }, + onError: (error) => { + console.error('구독 생성 실패:', error); + setFormErrors({ verification: '구독 생성에 실패했습니다. 다시 시도해주세요.' }); + }, + } + ); + }, + onError: (error) => { + console.error('인증 실패:', error); + setFormErrors({ verification: '인증에 실패했습니다. 인증번호를 확인해주세요.' }); + }, + } + ); }; const handleModalClose = () => { setStep('form'); - setFormData({ categories: [], weekdays: [], email: '' }); + setFormData({ categories: [], weekdays: [], email: '', period: '' }); setVerificationCode(''); setEmailError(''); setFormErrors({}); - setIsLoading(false); onClose(); }; @@ -162,8 +251,21 @@ const SubscriptionModal: React.FC = ({ isOpen, onClose } {/* 질문 카테고리 */}

관심 있는 분야를 선택해주세요

-
- {categories.map(category => ( + {categoriesLoading ? ( +
+
카테고리를 불러오는 중...
+
+ ) : categoriesError ? ( +
+
카테고리를 불러오는데 실패했습니다.
+
+ ) : categories.length === 0 ? ( +
+
사용 가능한 카테고리가 없습니다.
+
+ ) : ( +
+ {categories.map(category => ( - ))} -
+ ))} +
+ )}

* 하나의 분야를 선택해주세요

{formErrors.category && (

@@ -264,17 +367,69 @@ const SubscriptionModal: React.FC = ({ isOpen, onClose } )}

+ {/* 구독 기간 선택 */} +
+

구독 기간을 선택해주세요

+
+ {periods.map(period => ( +
+ {/* 이메일 입력 */}

이메일 주소

= ({ isOpen, onClose }
@@ -345,16 +500,16 @@ const SubscriptionModal: React.FC = ({ isOpen, onClose }
diff --git a/src/components/sections/QuizSection.tsx b/src/components/sections/QuizSection.tsx index 55f9208..ca17730 100644 --- a/src/components/sections/QuizSection.tsx +++ b/src/components/sections/QuizSection.tsx @@ -2,13 +2,30 @@ import React, { useState } from 'react'; import Container from '../common/Container'; import Section from '../common/Section'; import QuizComponent from '../common/QuizComponent'; +import { useTodayQuiz, useSubmitQuizAnswer } from '../../hooks'; + +interface QuizData { + id: string; + question: string; + options: Array<{ + id: number; + text: string; + }>; + correctAnswer: number; + explanation: string; +} const QuizSection: React.FC = () => { const [showResult, setShowResult] = useState(false); const [userAnswer, setUserAnswer] = useState(null); - // 샘플 퀴즈 데이터 - const sampleQuiz = { + // React Query 훅 사용 + const { data: quiz, isLoading, error } = useTodayQuiz(); + const submitAnswerMutation = useSubmitQuizAnswer(); + + // 샘플 퀴즈 데이터 (API 실패시 fallback) + const sampleQuiz: QuizData = { + id: 'sample', question: "다음 중 시간 복잡도가 O(log n)인 알고리즘은?", options: [ { id: 1, text: "버블 정렬 (Bubble Sort)" }, @@ -20,8 +37,22 @@ const QuizSection: React.FC = () => { explanation: "이진 탐색은 정렬된 배열에서 중간값과 비교하여 탐색 범위를 절반씩 줄여나가므로 O(log n)의 시간 복잡도를 가집니다." }; - const handleQuizSubmit = (selectedAnswer: number) => { + const handleQuizSubmit = async (selectedAnswer: number) => { setUserAnswer(selectedAnswer); + + // 유효한 퀴즈 ID가 있는 경우에만 답안 제출 + const currentQuiz = (quiz as QuizData) || sampleQuiz; + if (currentQuiz?.id && currentQuiz.id !== 'sample') { + submitAnswerMutation.mutate( + { quizId: currentQuiz.id, answer: selectedAnswer }, + { + onError: (error) => { + console.error('답안 제출 실패:', error); + }, + } + ); + } + setShowResult(true); }; @@ -30,8 +61,38 @@ const QuizSection: React.FC = () => { setUserAnswer(null); }; + // 로딩 상태 + if (isLoading) { + return ( +
+ +
+
+

퀴즈를 불러오는 중...

+
+
+
+ ); + } + + // 에러 상태 처리 - 샘플 퀴즈로 fallback + const currentQuiz = (quiz as QuizData) || sampleQuiz; + + if (error && !currentQuiz) { + return ( +
+ +
+

퀴즈를 불러올 수 없습니다.

+

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

+
+
+
+ ); + } + if (showResult) { - const isCorrect = userAnswer === sampleQuiz.correctAnswer; + const isCorrect = userAnswer === currentQuiz.correctAnswer; return (
@@ -48,10 +109,10 @@ const QuizSection: React.FC = () => {

해설

-

{sampleQuiz.explanation}

+

{currentQuiz.explanation}

- 정답: {sampleQuiz.options.find(opt => opt.id === sampleQuiz.correctAnswer)?.text} + 정답: {currentQuiz.options.find(opt => opt.id === currentQuiz.correctAnswer)?.text}

@@ -87,8 +148,8 @@ const QuizSection: React.FC = () => {
diff --git a/src/components/sections/TodayEmailFormSection.tsx b/src/components/sections/TodayEmailFormSection.tsx index e37e21c..35e630c 100644 --- a/src/components/sections/TodayEmailFormSection.tsx +++ b/src/components/sections/TodayEmailFormSection.tsx @@ -18,11 +18,11 @@ const TodayEmailFormSection: React.FC = () => {

- 매일 받게 될 CS 메일 + 매일 받게 될 CS 지식

- 이런 형태의 맞춤형 메일을 매일 받아보실 수 있습니다 + 아래와 같은 형태의 맞춤형 메일을 받아보실 수 있습니다.

diff --git a/src/contexts/ModalContext.tsx b/src/contexts/ModalContext.tsx new file mode 100644 index 0000000..c2b7df1 --- /dev/null +++ b/src/contexts/ModalContext.tsx @@ -0,0 +1,80 @@ +import React, { createContext, useContext, useState, ReactNode } from 'react'; + +export interface ModalConfig { + id: string; + title?: string; + content: ReactNode; + onClose?: () => void; + size?: 'sm' | 'md' | 'lg' | 'xl'; + closable?: boolean; +} + +interface ModalContextType { + modals: ModalConfig[]; + openModal: (config: Omit) => string; + closeModal: (id: string) => void; + closeAllModals: () => void; +} + +const ModalContext = createContext(undefined); + +interface ModalProviderProps { + children: ReactNode; +} + +export const ModalProvider: React.FC = ({ children }) => { + const [modals, setModals] = useState([]); + + const openModal = (config: Omit): string => { + const id = Math.random().toString(36).substr(2, 9); + const modal: ModalConfig = { + ...config, + id, + closable: config.closable ?? true, + size: config.size ?? 'md', + }; + + setModals(prev => [...prev, modal]); + return id; + }; + + const closeModal = (id: string) => { + setModals(prev => { + const modal = prev.find(m => m.id === id); + if (modal?.onClose) { + modal.onClose(); + } + return prev.filter(m => m.id !== id); + }); + }; + + const closeAllModals = () => { + modals.forEach(modal => { + if (modal.onClose) { + modal.onClose(); + } + }); + setModals([]); + }; + + const value: ModalContextType = { + modals, + openModal, + closeModal, + closeAllModals, + }; + + return ( + + {children} + + ); +}; + +export const useModalContext = () => { + const context = useContext(ModalContext); + if (context === undefined) { + throw new Error('useModalContext must be used within a ModalProvider'); + } + return context; +}; \ No newline at end of file diff --git a/src/hooks/index.ts b/src/hooks/index.ts new file mode 100644 index 0000000..dfb023c --- /dev/null +++ b/src/hooks/index.ts @@ -0,0 +1,4 @@ +// 모든 커스텀 훅들을 한 곳에서 export +export * from './useQuiz'; +export * from './useSubscription'; +export * from './useAuth'; \ No newline at end of file diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts new file mode 100644 index 0000000..552a638 --- /dev/null +++ b/src/hooks/useAuth.ts @@ -0,0 +1,53 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { authAPI } from '../utils/api'; + +// 인증 관련 Query Keys +export const authKeys = { + all: ['auth'] as const, + user: () => [...authKeys.all, 'user'] as const, +}; + +// 로그인 +export const useLogin = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ email, password }: { email: string; password: string }) => + authAPI.login(email, password), + onSuccess: (data) => { + // 로그인 성공 시 사용자 정보 캐시에 저장 + queryClient.setQueryData(authKeys.user(), data); + }, + }); +}; + +// 회원가입 +export const useRegister = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ email, password }: { email: string; password: string }) => + authAPI.register(email, password), + onSuccess: (data) => { + // 회원가입 성공 시 사용자 정보 캐시에 저장 + queryClient.setQueryData(authKeys.user(), data); + }, + }); +}; + +// 로그아웃 +export const useLogout = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async () => { + // 로컬 스토리지에서 토큰 제거 등의 로직 + localStorage.removeItem('authToken'); + return Promise.resolve(); + }, + onSuccess: () => { + // 모든 캐시 클리어 + queryClient.clear(); + }, + }); +}; \ No newline at end of file diff --git a/src/hooks/useModal.tsx b/src/hooks/useModal.tsx new file mode 100644 index 0000000..bc7d19b --- /dev/null +++ b/src/hooks/useModal.tsx @@ -0,0 +1,72 @@ +import { ReactNode } from 'react'; +import { useModalContext, ModalConfig } from '../contexts/ModalContext'; +import LoginForm from '../components/common/LoginForm'; + +interface UseModalReturn { + openModal: (config: Omit) => string; + closeModal: (id: string) => void; + closeAllModals: () => void; + modals: ModalConfig[]; +} + +export const useModal = (): UseModalReturn => { + const { openModal, closeModal, closeAllModals, modals } = useModalContext(); + + return { + openModal, + closeModal, + closeAllModals, + modals, + }; +}; + +// 편의를 위한 특정 모달 훅들 +export const useLoginModal = () => { + const { openModal, closeModal } = useModal(); + + const openLoginModal = () => { + return openModal({ + content: , + size: 'md', + }); + }; + + return { openLoginModal, closeModal }; +}; + +export const useSubscriptionModal = () => { + const { openModal, closeModal } = useModal(); + + const openSubscriptionModal = () => { + return openModal({ + title: '', + content:
구독 모달이 여기에 들어갑니다
, + size: 'lg', + }); + }; + + return { openSubscriptionModal, closeModal }; +}; + +// 커스텀 모달을 쉽게 열 수 있는 헬퍼 +export const useCustomModal = () => { + const { openModal, closeModal } = useModal(); + + const openCustomModal = ( + title: string, + content: ReactNode, + options?: { + size?: 'sm' | 'md' | 'lg' | 'xl'; + closable?: boolean; + onClose?: () => void; + } + ) => { + return openModal({ + title, + content, + ...options, + }); + }; + + return { openCustomModal, closeModal }; +}; \ No newline at end of file diff --git a/src/hooks/useQuiz.ts b/src/hooks/useQuiz.ts new file mode 100644 index 0000000..df594fe --- /dev/null +++ b/src/hooks/useQuiz.ts @@ -0,0 +1,58 @@ +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { quizAPI } from '../utils/api'; + +// 퀴즈 관련 Query Keys +export const quizKeys = { + all: ['quiz'] as const, + categories: () => [...quizKeys.all, 'categories'] as const, + today: () => [...quizKeys.all, 'today'] as const, + history: (email: string, token: string) => [...quizKeys.all, 'history', email, token] as const, +}; + +// 퀴즈 카테고리 목록 조회 +export const useQuizCategories = (enabled: boolean = true) => { + return useQuery({ + queryKey: quizKeys.categories(), + queryFn: quizAPI.getQuizCategories, + staleTime: 1000 * 60 * 60, // 1시간간 fresh (카테고리는 자주 변경되지 않음) + gcTime: 1000 * 60 * 60 * 24, // 24시간 동안 캐시 유지 + retry: 1, + enabled, // 모달이 열릴 때만 요청 + refetchOnMount: false, // 컴포넌트 마운트 시 재요청 안함 + refetchOnWindowFocus: false, // 윈도우 포커스 시 재요청 안함 + }); +}; + +// 오늘의 퀴즈 조회 +export const useTodayQuiz = () => { + return useQuery({ + queryKey: quizKeys.today(), + queryFn: quizAPI.getTodayQuiz, + staleTime: 1000 * 60 * 30, // 30분간 fresh + retry: 1, + }); +}; + +// 퀴즈 답안 제출 +export const useSubmitQuizAnswer = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ quizId, answer }: { quizId: string; answer: number }) => + quizAPI.submitQuizAnswer(quizId, answer), + onSuccess: () => { + // 제출 후 히스토리 캐시 무효화 + queryClient.invalidateQueries({ queryKey: quizKeys.all }); + }, + }); +}; + +// 사용자 퀴즈 히스토리 조회 +export const useQuizHistory = (email: string, token: string, enabled: boolean = true) => { + return useQuery({ + queryKey: quizKeys.history(email, token), + queryFn: () => quizAPI.getUserQuizHistory(email, token), + enabled: enabled && !!email && !!token, + staleTime: 1000 * 60 * 10, // 10분간 fresh + }); +}; \ No newline at end of file diff --git a/src/hooks/useSubscription.ts b/src/hooks/useSubscription.ts new file mode 100644 index 0000000..78016d6 --- /dev/null +++ b/src/hooks/useSubscription.ts @@ -0,0 +1,86 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { subscriptionAPI } from '../utils/api'; + +// 구독 관련 Query Keys +export const subscriptionKeys = { + all: ['subscription'] as const, + settings: (email: string, token: string) => [...subscriptionKeys.all, 'settings', email, token] as const, +}; + +// 이메일 인증 요청 +export const useRequestEmailVerification = () => { + return useMutation({ + mutationFn: (email: string) => subscriptionAPI.requestEmailVerification(email), + }); +}; + +// 이메일 중복 체크 +export const useCheckEmail = () => { + return useMutation({ + mutationFn: (email: string) => subscriptionAPI.checkEmail(email), + }); +}; + +// 인증 코드 확인 +export const useVerifyCode = () => { + return useMutation({ + mutationFn: ({ email, code }: { email: string; code: string }) => + subscriptionAPI.verifyCode(email, code), + }); +}; + +// 구독 생성 +export const useCreateSubscription = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (data: { + email: string; + category: string; + days: string[]; + period: string; + }) => subscriptionAPI.createSubscription(data), + onSuccess: () => { + // 구독 생성 후 관련 캐시 무효화 + queryClient.invalidateQueries({ queryKey: subscriptionKeys.all }); + }, + }); +}; + +// 구독 설정 조회 +export const useSubscriptionSettings = (email: string, token: string, enabled: boolean = true) => { + return useQuery({ + queryKey: subscriptionKeys.settings(email, token), + queryFn: () => subscriptionAPI.getSubscriptionSettings(email, token), + enabled: enabled && !!email && !!token, + staleTime: 1000 * 60 * 5, // 5분간 fresh + }); +}; + +// 구독 설정 업데이트 +export const useUpdateSubscriptionSettings = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ + email, + token, + settings, + }: { + email: string; + token: string; + settings: { + categories: string[]; + difficulty: string; + frequency: string; + timePreference: string; + }; + }) => subscriptionAPI.updateSubscriptionSettings(email, token, settings), + onSuccess: (_, variables) => { + // 특정 사용자의 설정 캐시 무효화 + queryClient.invalidateQueries({ + queryKey: subscriptionKeys.settings(variables.email, variables.token) + }); + }, + }); +}; \ No newline at end of file diff --git a/src/utils/api.ts b/src/utils/api.ts new file mode 100644 index 0000000..a44b4f2 --- /dev/null +++ b/src/utils/api.ts @@ -0,0 +1,153 @@ +// API 유틸리티 함수들 + +const API_BASE_URL = '/api'; + +// 기본 fetch 래퍼 +async function apiRequest( + endpoint: string, + options: RequestInit = {} +): Promise { + const url = `${API_BASE_URL}${endpoint}`; + + const config: RequestInit = { + headers: { + 'Content-Type': 'application/json', + ...options.headers, + }, + ...options, + }; + + try { + const response = await fetch(url, config); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return await response.json(); + } catch (error) { + console.error('API request failed:', error); + throw error; + } +} + +// 구독 관련 API +export const subscriptionAPI = { + // 이메일 인증 요청 + requestEmailVerification: async (email: string) => { + return apiRequest('/emails/verifications', { + method: 'POST', + body: JSON.stringify({ email }), + }); + }, + + // 인증 코드 확인 + verifyCode: async (email: string, code: string) => { + return apiRequest('/emails/verifications/verify', { + method: 'POST', + body: JSON.stringify({ email, code }), + }); + }, + + // 이메일 중복 체크 + checkEmail: async (email: string) => { + return apiRequest(`/subscriptions/email/check?email=${encodeURIComponent(email)}`, { + method: 'GET', + }); + }, + + // 구독 생성 + createSubscription: async (data: { + email: string; + category: string; + days: string[]; + period: string; + }) => { + return apiRequest('/subscriptions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + ...data, + isActive: true + }), + }); + }, + + // 구독 설정 조회 + getSubscriptionSettings: async (email: string, token: string) => { + return apiRequest(`/subscription/settings?email=${email}&token=${token}`); + }, + + // 구독 설정 업데이트 + updateSubscriptionSettings: async ( + email: string, + token: string, + settings: { + categories: string[]; + difficulty: string; + frequency: string; + timePreference: string; + } + ) => { + return apiRequest(`/subscription/settings`, { + method: 'PUT', + body: JSON.stringify({ email, token, ...settings }), + }); + }, +}; + +// 퀴즈 관련 API +export const quizAPI = { + // 퀴즈 카테고리 목록 조회 + getQuizCategories: async (): Promise => { + return apiRequest('/quiz-categories'); + }, + + // 오늘의 퀴즈 조회 + getTodayQuiz: async () => { + return apiRequest('/quiz/today'); + }, + + // 퀴즈 답안 제출 + submitQuizAnswer: async (quizId: string, answer: number) => { + return apiRequest('/quiz/submit', { + method: 'POST', + body: JSON.stringify({ quizId, answer }), + }); + }, + + // 사용자별 퀴즈 히스토리 + getUserQuizHistory: async (email: string, token: string) => { + return apiRequest(`/quiz/history?email=${email}&token=${token}`); + }, +}; + +// 인증 관련 API +export const authAPI = { + // 로그인 + login: async (email: string, password: string) => { + return apiRequest('/auth/login', { + method: 'POST', + body: JSON.stringify({ email, password }), + }); + }, + + // 회원가입 + register: async (email: string, password: string) => { + return apiRequest('/auth/register', { + method: 'POST', + body: JSON.stringify({ email, password }), + }); + }, + + // 소셜 로그인 + socialLogin: async (provider: 'kakao' | 'github' | 'naver') => { + const url = `/oauth2/authorization/${provider}`; + const backendURI = "http://localhost:8080" // FIXME: 수정해야 함 + window.location.href = backendURI + url; + } +}; + +export default apiRequest; \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index 1708e65..730b6b4 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,8 +5,14 @@ import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], server: { - port: 3002, - open: true + open: true, + proxy: { + '/api': { + target: 'http://localhost:8080', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, '') + } + } }, build: { outDir: 'build' From a13dbbdb4471e780716d89a1517529a00e4c9108 Mon Sep 17 00:00:00 2001 From: Ksr-ccb Date: Thu, 12 Jun 2025 20:19:39 +0900 Subject: [PATCH 03/47] =?UTF-8?q?fix:=20periods=20=EC=98=A4=ED=83=80?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/SubscriptionModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/common/SubscriptionModal.tsx b/src/components/common/SubscriptionModal.tsx index 7b71cc6..9334f4a 100644 --- a/src/components/common/SubscriptionModal.tsx +++ b/src/components/common/SubscriptionModal.tsx @@ -78,8 +78,8 @@ const SubscriptionModal: React.FC = ({ isOpen, onClose } const periods = [ { id: 'ONE_MONTH', label: '1개월' }, - { id: 'THREE_MONTH', label: '3개월' }, - { id: 'SIX_MONTH', label: '6개월' }, + { id: 'THREE_MONTHS', label: '3개월' }, + { id: 'SIX_MONTHS', label: '6개월' }, { id: 'ONE_YEAR', label: '1년' } ]; From d4e927c86c6440aa36db8fe55afd5c099f2cfadf Mon Sep 17 00:00:00 2001 From: wannabeing Date: Thu, 12 Jun 2025 23:21:05 +0900 Subject: [PATCH 04/47] =?UTF-8?q?feat:=20nginx=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +++ nginx.conf | 66 ++++++++++++++++++++++++++++++++++++++++++++++++ src/utils/api.ts | 8 +++--- 3 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 nginx.conf diff --git a/.gitignore b/.gitignore index accdc86..998983a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +# intellij +/.idea + # claude /.claude diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..a59a8d1 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,66 @@ +server { + listen 80; + server_name cs25; + root /usr/share/nginx/html; + index index.html; + + # React Router를 위한 설정 - 모든 경로를 index.html로 리다이렉트 + location / { + try_files $uri $uri/ /index.html; + } + + # API 요청을 백엔드로 프록시 + location /api/ { + proxy_pass http://localhost:8080/api/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # CORS 헤더 설정 + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always; + + # OPTIONS 요청 처리 + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'text/plain; charset=utf-8'; + add_header 'Content-Length' 0; + return 204; + } + } + + # OAuth 인증 요청을 백엔드로 프록시 + location /oauth2/ { + proxy_pass http://localhost:8080/oauth2/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # 정적 파일 캐싱 + location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # gzip 압축 설정 + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied expired no-cache no-store private must-revalidate auth; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/x-javascript + application/xml+rss + application/javascript + application/json; +} \ No newline at end of file diff --git a/src/utils/api.ts b/src/utils/api.ts index a44b4f2..e9add8b 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -102,7 +102,9 @@ export const subscriptionAPI = { export const quizAPI = { // 퀴즈 카테고리 목록 조회 getQuizCategories: async (): Promise => { - return apiRequest('/quiz-categories'); + return apiRequest('/quiz-categories', { + method: 'GET' + }); }, // 오늘의 퀴즈 조회 @@ -144,9 +146,7 @@ export const authAPI = { // 소셜 로그인 socialLogin: async (provider: 'kakao' | 'github' | 'naver') => { - const url = `/oauth2/authorization/${provider}`; - const backendURI = "http://localhost:8080" // FIXME: 수정해야 함 - window.location.href = backendURI + url; + window.location.href = `/oauth2/authorization/${provider}`; } }; From 26f364b2b720412b92c030fe7f1adba00d20be11 Mon Sep 17 00:00:00 2001 From: wannabeing Date: Fri, 13 Jun 2025 02:03:00 +0900 Subject: [PATCH 05/47] =?UTF-8?q?feat:=20Docker,=20Nginx=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 11 ++++ .gitignore | 1 + Dockerfile | 38 ++++++++++++++ docker-compose.yml | 11 ++++ nginx.conf | 121 +++++++++++++++++++++++--------------------- nginx.conf.template | 69 +++++++++++++++++++++++++ package.json | 5 +- src/utils/api.ts | 3 +- src/vite-env.d.ts | 9 ++++ 9 files changed, 206 insertions(+), 62 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 nginx.conf.template create mode 100644 src/vite-env.d.ts diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b06adf7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +node_modules +npm-debug.log +build +.git +.gitignore +README.md +.env +.nyc_output +coverage +.vscode +.idea \ No newline at end of file diff --git a/.gitignore b/.gitignore index 998983a..3bef82f 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ # misc .DS_Store +.env .env.local .env.development.local .env.test.local diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..67c8d80 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,38 @@ +# 멀티 스테이지 빌드 +# 1단계: 빌드 스테이지 +FROM node:20-alpine AS builder + +WORKDIR /app + +# package.json과 package-lock.json 복사 +COPY package*.json ./ + +# 의존성 설치 (빌드에 필요한 devDependencies 포함) +RUN npm ci + +# 소스 코드 복사 +COPY . . + +# Vite로 빌드 +RUN npm run build + +# 2단계: 프로덕션 스테이지 +FROM nginx:alpine + +# 기본 nginx 설정 제거 +RUN rm /etc/nginx/conf.d/default.conf + +# nginx 템플릿 복사 +COPY nginx.conf.template /etc/nginx/templates/ + +# 환경변수 설정 +ENV BACKEND_URL=http://localhost:8080 + +# 빌드된 정적 파일 복사 +COPY --from=builder /app/build /usr/share/nginx/html + +# 포트 80 노출 +EXPOSE 80 + +# nginx 실행 +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9e254d1 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +version: '3.8' + +services: + frontend: + build: . + ports: + - "5173:80" + +networks: + app-network: + driver: bridge \ No newline at end of file diff --git a/nginx.conf b/nginx.conf index a59a8d1..627d413 100644 --- a/nginx.conf +++ b/nginx.conf @@ -1,66 +1,69 @@ server { - listen 80; - server_name cs25; - root /usr/share/nginx/html; - index index.html; + listen 80; + server_name cs25; + root /usr/share/nginx/html; + index index.html; - # React Router를 위한 설정 - 모든 경로를 index.html로 리다이렉트 - location / { - try_files $uri $uri/ /index.html; - } + # React Router를 위한 설정 + location / { + try_files $uri $uri/ /index.html; + } - # API 요청을 백엔드로 프록시 - location /api/ { - proxy_pass http://localhost:8080/api/; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - # CORS 헤더 설정 - add_header 'Access-Control-Allow-Origin' '*' always; - add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; - add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always; - - # OPTIONS 요청 처리 - if ($request_method = 'OPTIONS') { - add_header 'Access-Control-Allow-Origin' '*'; - add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS'; - add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization'; - add_header 'Access-Control-Max-Age' 1728000; - add_header 'Content-Type' 'text/plain; charset=utf-8'; - add_header 'Content-Length' 0; - return 204; - } - } + # API 요청 프록시 + location /api/ { + proxy_pass ${API_URL}/api/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; - # OAuth 인증 요청을 백엔드로 프록시 - location /oauth2/ { - proxy_pass http://localhost:8080/oauth2/; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } + # CORS 설정 + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always; - # 정적 파일 캐싱 - location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg)$ { - expires 1y; - add_header Cache-Control "public, immutable"; + # OPTIONS 프리플라이트 응답 + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always; + add_header 'Access-Control-Max-Age' 1728000 always; + add_header 'Content-Type' 'text/plain; charset=utf-8'; + add_header 'Content-Length' 0; + return 204; } + } + + # OAuth2 인증 프록시 + location /oauth2/ { + proxy_pass ${API_URL}/oauth2/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + } + + # 정적 자산 캐싱 + location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg)$ { + expires 1y; + add_header Cache-Control "public, max-age=31536000, immutable"; + } + + # gzip 설정 + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied expired no-cache no-store private auth; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/x-javascript + application/xml+rss + application/javascript + application/json; - # gzip 압축 설정 - gzip on; - gzip_vary on; - gzip_min_length 1024; - gzip_proxied expired no-cache no-store private must-revalidate auth; - gzip_types - text/plain - text/css - text/xml - text/javascript - application/x-javascript - application/xml+rss - application/javascript - application/json; -} \ No newline at end of file +} diff --git a/nginx.conf.template b/nginx.conf.template new file mode 100644 index 0000000..7df78e1 --- /dev/null +++ b/nginx.conf.template @@ -0,0 +1,69 @@ +server { + listen 80; + server_name cs25; + root /usr/share/nginx/html; + index index.html; + + # React Router를 위한 설정 + location / { + try_files $uri $uri/ /index.html; + } + + # API 요청 프록시 + location /api/ { + proxy_pass ${BACKEND_URL}/api/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # CORS 설정 + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always; + + # OPTIONS 프리플라이트 응답 + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always; + add_header 'Access-Control-Max-Age' 1728000 always; + add_header 'Content-Type' 'text/plain; charset=utf-8'; + add_header 'Content-Length' 0; + return 204; + } + } + + # OAuth2 인증 프록시 + location /oauth2/ { + proxy_pass ${BACKEND_URL}/oauth2/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + } + + # 정적 자산 캐싱 + location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg)$ { + expires 1y; + add_header Cache-Control "public, max-age=31536000, immutable"; + } + + # gzip 설정 + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied expired no-cache no-store private auth; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/x-javascript + application/xml+rss + application/javascript + application/json; + +} \ No newline at end of file diff --git a/package.json b/package.json index 91fc07a..a6ec664 100644 --- a/package.json +++ b/package.json @@ -18,10 +18,11 @@ "web-vitals": "^2.1.4" }, "scripts": { - "dev": "vite", "build": "tsc && vite build", "preview": "vite preview", - "start": "vite" + "start": "vite", + "d-build": "docker build -t cs25-frontend . && docker run -d -p 80:80 --name cs25-front cs25-frontend", + "dev": "docker-compose down && docker-compose up --build" }, "devDependencies": { "@types/node": "^20.19.0", diff --git a/src/utils/api.ts b/src/utils/api.ts index e9add8b..469e01b 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -146,7 +146,8 @@ export const authAPI = { // 소셜 로그인 socialLogin: async (provider: 'kakao' | 'github' | 'naver') => { - window.location.href = `/oauth2/authorization/${provider}`; + const apiUrl = import.meta.env.VITE_API_URL || "http://localhost:8080"; + window.location.href = `${apiUrl}/oauth2/authorization/${provider}`; } }; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..476b1e1 --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1,9 @@ +/// + +interface ImportMetaEnv { + readonly VITE_API_URL: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} \ No newline at end of file From d082529cf2646cfcdeb403781a8f729c0a31d947 Mon Sep 17 00:00:00 2001 From: wannabeing Date: Fri, 13 Jun 2025 02:03:25 +0900 Subject: [PATCH 06/47] =?UTF-8?q?feat:=20git=20actions=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..51612dc --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,32 @@ +name: Frontend CD with Docker Hub +on: + push: + branches: + - dev # dev push되었을 때 yml 실행 +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{secrets.DOCKERHUB_USERNAME}} # 도커 허브 이름 + password: ${{secrets.DOCKERHUB_TOKEN}} # 도커 허브 access token + - name: Build and Release + run: | + docker build -t ${{secrets.DOCKERHUB_REPO}} . + docker tag ${{secrets.DOCKERHUB_REPO}}:latest ${{secrets.DOCKERHUB_USERNAME}}/${{secrets.DOCKERHUB_REPO}}:latest + docker push ${{secrets.DOCKERHUB_USERNAME}}/${{secrets.DOCKERHUB_REPO}}:latest + - name: Deploy to server + uses: appleboy/ssh-action@master + id: deploy + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USERNAME }} + key: ${{ secrets.KEY }} + script: | + sudo docker rm -f $(docker ps -aqf "name=^${{secrets.DOCKERHUB_REPO}}") # 컨테이너 찾아서 종료 + sudo docker pull ${{secrets.DOCKERHUB_USERNAME}}/${{secrets.DOCKERHUB_REPO}}:latest + sudo docker run -d -p 80:80 --name ${{secrets.DOCKERHUB_REPO}} ${{secrets.DOCKERHUB_USERNAME}}/${{secrets.DOCKERHUB_REPO}}:latest + sudo docker image prune -f \ No newline at end of file From 454dffa3d09ba798cabd56832544bccd55e09b0e Mon Sep 17 00:00:00 2001 From: wannabeing Date: Fri, 13 Jun 2025 12:43:49 +0900 Subject: [PATCH 07/47] =?UTF-8?q?chore:=20=ED=99=98=EA=B2=BD=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 6 +++++- Dockerfile | 3 --- vite.config.ts | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 51612dc..68ea2d2 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -28,5 +28,9 @@ jobs: script: | sudo docker rm -f $(docker ps -aqf "name=^${{secrets.DOCKERHUB_REPO}}") # 컨테이너 찾아서 종료 sudo docker pull ${{secrets.DOCKERHUB_USERNAME}}/${{secrets.DOCKERHUB_REPO}}:latest - sudo docker run -d -p 80:80 --name ${{secrets.DOCKERHUB_REPO}} ${{secrets.DOCKERHUB_USERNAME}}/${{secrets.DOCKERHUB_REPO}}:latest + sudo docker run \ + -e VITE_API_URL=${{ secrets.VITE_API_URL }} \ + -d -p 80:80 \ + --name ${{secrets.DOCKERHUB_REPO}} \ + ${{secrets.DOCKERHUB_USERNAME}}/${{secrets.DOCKERHUB_REPO}}:latest sudo docker image prune -f \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 67c8d80..b4eefd2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,9 +25,6 @@ RUN rm /etc/nginx/conf.d/default.conf # nginx 템플릿 복사 COPY nginx.conf.template /etc/nginx/templates/ -# 환경변수 설정 -ENV BACKEND_URL=http://localhost:8080 - # 빌드된 정적 파일 복사 COPY --from=builder /app/build /usr/share/nginx/html diff --git a/vite.config.ts b/vite.config.ts index 730b6b4..e4de24c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -8,7 +8,7 @@ export default defineConfig({ open: true, proxy: { '/api': { - target: 'http://localhost:8080', + target: process.env.VITE_API_URL || 'http://localhost:8080', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') } From 7c1f449f98a3e22cd113d0c446b8df89931666bd Mon Sep 17 00:00:00 2001 From: wannabeing Date: Fri, 13 Jun 2025 12:49:43 +0900 Subject: [PATCH 08/47] =?UTF-8?q?chore:=20=ED=99=98=EA=B2=BD=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nginx.conf | 4 ++-- nginx.conf.template | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nginx.conf b/nginx.conf index 627d413..a959f20 100644 --- a/nginx.conf +++ b/nginx.conf @@ -11,7 +11,7 @@ server { # API 요청 프록시 location /api/ { - proxy_pass ${API_URL}/api/; + proxy_pass ${VITE_API_URL}/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -36,7 +36,7 @@ server { # OAuth2 인증 프록시 location /oauth2/ { - proxy_pass ${API_URL}/oauth2/; + proxy_pass ${VITE_API_URL}/oauth2/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/nginx.conf.template b/nginx.conf.template index 7df78e1..1aa3c80 100644 --- a/nginx.conf.template +++ b/nginx.conf.template @@ -11,7 +11,7 @@ server { # API 요청 프록시 location /api/ { - proxy_pass ${BACKEND_URL}/api/; + proxy_pass ${VITE_API_URL}/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -36,7 +36,7 @@ server { # OAuth2 인증 프록시 location /oauth2/ { - proxy_pass ${BACKEND_URL}/oauth2/; + proxy_pass ${VITE_API_URL}/oauth2/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; From 675cac44a81c0afc8c3657cc4b884fbb1ba40977 Mon Sep 17 00:00:00 2001 From: Ksr-ccb Date: Fri, 13 Jun 2025 14:12:23 +0900 Subject: [PATCH 09/47] =?UTF-8?q?chore:=20SSL=20=EC=9D=B8=EC=A6=9D?= =?UTF-8?q?=EC=84=9C=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 4 ++++ nginx.conf | 17 +++++++++++++---- nginx.conf.template | 17 +++++++++++++---- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 9e254d1..1ded587 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,10 @@ services: build: . ports: - "5173:80" + volumes: + - /etc/letsencrypt:/etc/letsencrypt:ro #인증서 마운트 + networks: + - app-network networks: app-network: diff --git a/nginx.conf b/nginx.conf index a959f20..009822e 100644 --- a/nginx.conf +++ b/nginx.conf @@ -1,11 +1,14 @@ server { - listen 80; - server_name cs25; - root /usr/share/nginx/html; - index index.html; + listen 443 ssl; + server_name cs25.co.kr www.cs25.co.kr; + + ssl_certificate /etc/letsencrypt/live/cs25.co.kr/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/cs25.co.kr/privkey.pem; # React Router를 위한 설정 location / { + root /usr/share/nginx/html; + index index.html; try_files $uri $uri/ /index.html; } @@ -67,3 +70,9 @@ server { application/json; } + +server { + listen 80; + server_name cs25.co.kr www.cs25.co.kr; + return 301 https://$host$request_uri; +} \ No newline at end of file diff --git a/nginx.conf.template b/nginx.conf.template index 1aa3c80..009822e 100644 --- a/nginx.conf.template +++ b/nginx.conf.template @@ -1,11 +1,14 @@ server { - listen 80; - server_name cs25; - root /usr/share/nginx/html; - index index.html; + listen 443 ssl; + server_name cs25.co.kr www.cs25.co.kr; + + ssl_certificate /etc/letsencrypt/live/cs25.co.kr/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/cs25.co.kr/privkey.pem; # React Router를 위한 설정 location / { + root /usr/share/nginx/html; + index index.html; try_files $uri $uri/ /index.html; } @@ -66,4 +69,10 @@ server { application/javascript application/json; +} + +server { + listen 80; + server_name cs25.co.kr www.cs25.co.kr; + return 301 https://$host$request_uri; } \ No newline at end of file From 5307f2bdd956f6bc737c3055b60cc29c5dce5a7e Mon Sep 17 00:00:00 2001 From: Ksr-ccb Date: Fri, 13 Jun 2025 14:26:32 +0900 Subject: [PATCH 10/47] =?UTF-8?q?chore:=20SSL=20=EC=9D=B8=EC=A6=9D?= =?UTF-8?q?=EC=84=9C=20=EB=A7=88=EC=9A=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 1 + docker-compose.yml | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 68ea2d2..e712047 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -30,6 +30,7 @@ jobs: sudo docker pull ${{secrets.DOCKERHUB_USERNAME}}/${{secrets.DOCKERHUB_REPO}}:latest sudo docker run \ -e VITE_API_URL=${{ secrets.VITE_API_URL }} \ + -v /etc/letsencrypt:/etc/letsencrypt:ro \ -d -p 80:80 \ --name ${{secrets.DOCKERHUB_REPO}} \ ${{secrets.DOCKERHUB_USERNAME}}/${{secrets.DOCKERHUB_REPO}}:latest diff --git a/docker-compose.yml b/docker-compose.yml index 1ded587..8e1fc9e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,8 +5,6 @@ services: build: . ports: - "5173:80" - volumes: - - /etc/letsencrypt:/etc/letsencrypt:ro #인증서 마운트 networks: - app-network From 39187988ef45ad7d2f6770a640a429bf7616107b Mon Sep 17 00:00:00 2001 From: Ksr-ccb Date: Fri, 13 Jun 2025 14:32:33 +0900 Subject: [PATCH 11/47] =?UTF-8?q?chore:=20https=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e712047..30a16da 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -31,7 +31,7 @@ jobs: sudo docker run \ -e VITE_API_URL=${{ secrets.VITE_API_URL }} \ -v /etc/letsencrypt:/etc/letsencrypt:ro \ - -d -p 80:80 \ + -d -p 80:80 -p 443:443 \ --name ${{secrets.DOCKERHUB_REPO}} \ ${{secrets.DOCKERHUB_USERNAME}}/${{secrets.DOCKERHUB_REPO}}:latest sudo docker image prune -f \ No newline at end of file From 030385608ad1bff37cff41432e69de503c12048d Mon Sep 17 00:00:00 2001 From: Ksr-ccb Date: Fri, 13 Jun 2025 14:41:51 +0900 Subject: [PATCH 12/47] =?UTF-8?q?fix:=20index=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=ED=8F=AC=EC=9B=8C=EB=94=A9=20=EB=AC=B8=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nginx.conf | 13 +++++++------ nginx.conf.template | 15 +++++++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/nginx.conf b/nginx.conf index 009822e..82b1559 100644 --- a/nginx.conf +++ b/nginx.conf @@ -1,14 +1,15 @@ server { - listen 443 ssl; - server_name cs25.co.kr www.cs25.co.kr; + listen 443 ssl; + server_name cs25.co.kr www.cs25.co.kr; - ssl_certificate /etc/letsencrypt/live/cs25.co.kr/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/cs25.co.kr/privkey.pem; + ssl_certificate /etc/letsencrypt/live/cs25.co.kr/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/cs25.co.kr/privkey.pem; + + root /usr/share/nginx/html; + index index.html; # React Router를 위한 설정 location / { - root /usr/share/nginx/html; - index index.html; try_files $uri $uri/ /index.html; } diff --git a/nginx.conf.template b/nginx.conf.template index 009822e..df39f61 100644 --- a/nginx.conf.template +++ b/nginx.conf.template @@ -1,17 +1,20 @@ server { - listen 443 ssl; - server_name cs25.co.kr www.cs25.co.kr; + listen 443 ssl; + server_name cs25.co.kr www.cs25.co.kr; + + ssl_certificate /etc/letsencrypt/live/cs25.co.kr/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/cs25.co.kr/privkey.pem; + + root /usr/share/nginx/html; + index index.html; - ssl_certificate /etc/letsencrypt/live/cs25.co.kr/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/cs25.co.kr/privkey.pem; # React Router를 위한 설정 location / { - root /usr/share/nginx/html; - index index.html; try_files $uri $uri/ /index.html; } + # API 요청 프록시 location /api/ { proxy_pass ${VITE_API_URL}/api/; From e6ba85f639f4ed7e285b663813eafca0e31d2c7e Mon Sep 17 00:00:00 2001 From: wannabeing Date: Fri, 13 Jun 2025 17:41:47 +0900 Subject: [PATCH 13/47] =?UTF-8?q?chore:=20=ED=99=98=EA=B2=BD=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=EC=84=A4=EC=A0=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 4 +++- Dockerfile | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 30a16da..b6f29ad 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -15,7 +15,9 @@ jobs: password: ${{secrets.DOCKERHUB_TOKEN}} # 도커 허브 access token - name: Build and Release run: | - docker build -t ${{secrets.DOCKERHUB_REPO}} . + docker build \ + --build-arg VITE_API_URL=${{ secrets.VITE_API_URL }} \ + -t ${{secrets.DOCKERHUB_REPO}} . docker tag ${{secrets.DOCKERHUB_REPO}}:latest ${{secrets.DOCKERHUB_USERNAME}}/${{secrets.DOCKERHUB_REPO}}:latest docker push ${{secrets.DOCKERHUB_USERNAME}}/${{secrets.DOCKERHUB_REPO}}:latest - name: Deploy to server diff --git a/Dockerfile b/Dockerfile index b4eefd2..cd44774 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,12 @@ RUN npm ci # 소스 코드 복사 COPY . . +# build-time ARG 정의 +ARG VITE_API_URL +ENV VITE_API_URL=$VITE_API_URL + # Vite로 빌드 +RUN echo "VITE_API_URL=$VITE_API_URL" RUN npm run build # 2단계: 프로덕션 스테이지 From 0c45af49814af1d4f93272e5cdda599bf9e918a3 Mon Sep 17 00:00:00 2001 From: wannabeing Date: Fri, 13 Jun 2025 17:56:14 +0900 Subject: [PATCH 14/47] =?UTF-8?q?chore:=20OAuth2=20=ED=94=84=EB=A1=9D?= =?UTF-8?q?=EC=8B=9C=20=EC=A3=BC=EC=86=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nginx.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nginx.conf b/nginx.conf index 82b1559..a952a0d 100644 --- a/nginx.conf +++ b/nginx.conf @@ -15,7 +15,7 @@ server { # API 요청 프록시 location /api/ { - proxy_pass ${VITE_API_URL}/api/; + proxy_pass ${VITE_API_URL}; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -40,7 +40,7 @@ server { # OAuth2 인증 프록시 location /oauth2/ { - proxy_pass ${VITE_API_URL}/oauth2/; + proxy_pass proxy_pass ${VITE_API_URL}; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; From 55cdcd557459eff2598184ff5874a83bd708cdc7 Mon Sep 17 00:00:00 2001 From: wannabeing Date: Fri, 13 Jun 2025 19:47:55 +0900 Subject: [PATCH 15/47] =?UTF-8?q?chore:=20HTTP=20=EC=9A=94=EC=B2=AD=20URL?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/api.ts b/src/utils/api.ts index 469e01b..3bfeb71 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -1,6 +1,6 @@ // API 유틸리티 함수들 -const API_BASE_URL = '/api'; +const API_BASE_URL = `${import.meta.env.VITE_API_URL}/api`; // 기본 fetch 래퍼 async function apiRequest( From 106f0b0353fb77ebda44742e085cdf34f82c464a Mon Sep 17 00:00:00 2001 From: wannabeing Date: Fri, 13 Jun 2025 19:50:10 +0900 Subject: [PATCH 16/47] =?UTF-8?q?chore:=20BASE=20URL=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/api.ts b/src/utils/api.ts index 3bfeb71..e585afd 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -1,6 +1,6 @@ // API 유틸리티 함수들 -const API_BASE_URL = `${import.meta.env.VITE_API_URL}/api`; +const API_BASE_URL = `${import.meta.env.VITE_API_URL}`; // 기본 fetch 래퍼 async function apiRequest( From 8ea7532d12cafdd150f4ff9799737a9eb27a837d Mon Sep 17 00:00:00 2001 From: wannabeing Date: Fri, 13 Jun 2025 20:03:21 +0900 Subject: [PATCH 17/47] =?UTF-8?q?chore:=20fetch=20=EB=9E=98=ED=8D=BC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/api.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/utils/api.ts b/src/utils/api.ts index e585afd..f9915b3 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -65,9 +65,6 @@ export const subscriptionAPI = { }) => { return apiRequest('/subscriptions', { method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, body: JSON.stringify({ ...data, isActive: true @@ -77,7 +74,9 @@ export const subscriptionAPI = { // 구독 설정 조회 getSubscriptionSettings: async (email: string, token: string) => { - return apiRequest(`/subscription/settings?email=${email}&token=${token}`); + return apiRequest(`/subscription/settings?email=${email}&token=${token}`, { + method: 'GET' + }); }, // 구독 설정 업데이트 @@ -109,7 +108,9 @@ export const quizAPI = { // 오늘의 퀴즈 조회 getTodayQuiz: async () => { - return apiRequest('/quiz/today'); + return apiRequest('/quiz/today', { + method: 'GET' + }); }, // 퀴즈 답안 제출 @@ -122,7 +123,9 @@ export const quizAPI = { // 사용자별 퀴즈 히스토리 getUserQuizHistory: async (email: string, token: string) => { - return apiRequest(`/quiz/history?email=${email}&token=${token}`); + return apiRequest(`/quiz/history?email=${email}&token=${token}`, { + method: 'GET' + }); }, }; From cc00a40404bc33b773a065ae90a6a2ff81888664 Mon Sep 17 00:00:00 2001 From: wannabeing Date: Tue, 17 Jun 2025 17:23:49 +0900 Subject: [PATCH 18/47] =?UTF-8?q?feat:=20=EC=98=A4=EB=8A=98=EC=9D=98=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=ED=85=9C=ED=94=8C=EB=A6=BF=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 2 + src/components/TodayQuizPage.tsx | 188 ++++++++++++++++++ .../sections/TodayEmailFormSection.tsx | 2 +- src/utils/api.ts | 17 +- 4 files changed, 206 insertions(+), 3 deletions(-) create mode 100644 src/components/TodayQuizPage.tsx diff --git a/src/App.tsx b/src/App.tsx index 9a22581..0e3e831 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,6 +5,7 @@ import LandingPage from './components/LandingPage'; import QuizSection from './components/sections/QuizSection'; import TodayEmailFormSection from './components/sections/TodayEmailFormSection'; import VerificationEmailPage from './components/VerificationEmailPage'; +import TodayQuizPage from './components/TodayQuizPage'; import Header from './components/common/Header'; import Footer from './components/common/Footer'; import SubscriptionModal from './components/common/SubscriptionModal'; @@ -33,6 +34,7 @@ function App() {
setIsSubscriptionModalOpen(true)} />} /> + } /> } /> } /> } /> diff --git a/src/components/TodayQuizPage.tsx b/src/components/TodayQuizPage.tsx new file mode 100644 index 0000000..979e455 --- /dev/null +++ b/src/components/TodayQuizPage.tsx @@ -0,0 +1,188 @@ +import React, { useState } from 'react'; +import { useSearchParams } from 'react-router-dom'; +import { useQuery } from '@tanstack/react-query'; +import { quizAPI } from '../utils/api'; +import Container from './common/Container'; +import Section from './common/Section'; + +interface QuizData { + quiz: string; + choice1: string; + choice2: string; + choice3: string; + choice4: string; +} + +interface AnswerResult { + isCorrect: boolean; + answer: string; +} + +// 임시 데이터 +const fakeTodayQuiz: QuizData = { + quiz: "다음 중 JavaScript에서 변수를 선언하는 올바른 방법은?", + choice1: "1. variable myVar = 10;", + choice2: "2. let myVar = 10;", + choice3: "3. declare myVar = 10;", + choice4: "4. set myVar = 10;" +}; + +const fakeAnswer: AnswerResult = { + isCorrect: true, + answer: "2. let myVar = 10;" +}; + +const TodayQuizPage: React.FC = () => { + const [searchParams] = useSearchParams(); + const [selectedAnswer, setSelectedAnswer] = useState(null); + const [isSubmitted, setIsSubmitted] = useState(false); + const [answerResult, setAnswerResult] = useState(null); + + const subscriptionId = searchParams.get('subscriptionId'); + const quizId = searchParams.get('quizId'); + + const { data: quiz, isLoading, error } = useQuery({ + queryKey: ['todayQuiz', subscriptionId, quizId], + queryFn: async () => { + const response = await quizAPI.getTodayQuiz(subscriptionId || undefined, quizId || undefined); + console.log('Quiz response:', response); + + return response as QuizData; + }, + enabled: !!(subscriptionId && quizId), + }); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (selectedAnswer === null) { + alert('선택지를 먼저 클릭해주세요!'); + return; + } + + if (quizId && subscriptionId) { + try { + const result = await quizAPI.submitTodayQuizAnswer(quizId, selectedAnswer, subscriptionId); + setAnswerResult(result as AnswerResult); + setIsSubmitted(true); + } catch (error) { + console.error('Failed to submit answer:', error); + // API 실패 시 fake 데이터 사용 + setAnswerResult(fakeAnswer); + setIsSubmitted(true); + } + } + }; + + const handleOptionClick = (value: number) => { + if (!isSubmitted) { + setSelectedAnswer(value); + } + }; + + if (isLoading) { + return ( +
+ +
+
+

퀴즈를 불러오는 중...

+
+
+
+ ); + } + + // API 실패 시 fake 데이터 사용 + const displayQuiz = quiz || fakeTodayQuiz; + + if (isSubmitted && answerResult) { + const isCorrect = answerResult.isCorrect; + return ( +
+ +
+
+ + {isCorrect ? '🎉 정답입니다!' : '❌ 틀렸습니다!'} + +
+ +
+

결과

+

답안이 성공적으로 제출되었습니다.

+
+

+ 정답: {answerResult.answer} +

+
+
+
+
+
+ ); + } + + return ( +
+ +
+
+ 📚 오늘의 CS 문제 +
+ +

+ AI가 준비한 오늘의 문제 +

+ +

+ 매일 새로운 CS 지식을 확인하고 실력을 향상시켜보세요 +

+
+ +
+
+ {/* Question Box */} +
+ Q. {displayQuiz.quiz} +
+ + {/* Quiz Form */} +
+ {/* Options List - 세로 배치로 변경 */} +
+ {[displayQuiz.choice1, displayQuiz.choice2, displayQuiz.choice3, displayQuiz.choice4].map((choice, index) => ( +
handleOptionClick(index + 1)} + className={`border-2 p-4 rounded-xl cursor-pointer transition-all duration-200 hover:shadow-md text-left ${ + selectedAnswer === index + 1 + ? 'border-brand-500 bg-brand-100 text-brand-800' + : 'border-gray-300 bg-white text-gray-700 hover:border-brand-300' + }`} + > + {choice} +
+ ))} +
+ + {/* Submit Button */} + +
+
+
+
+
+ ); +}; + +export default TodayQuizPage; \ No newline at end of file diff --git a/src/components/sections/TodayEmailFormSection.tsx b/src/components/sections/TodayEmailFormSection.tsx index 35e630c..965617d 100644 --- a/src/components/sections/TodayEmailFormSection.tsx +++ b/src/components/sections/TodayEmailFormSection.tsx @@ -30,7 +30,7 @@ const TodayEmailFormSection: React.FC = () => {
diff --git a/src/utils/api.ts b/src/utils/api.ts index f9915b3..5ed1cb8 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -107,8 +107,13 @@ export const quizAPI = { }, // 오늘의 퀴즈 조회 - getTodayQuiz: async () => { - return apiRequest('/quiz/today', { + getTodayQuiz: async (subscriptionId?: string, quizId?: string) => { + const params = new URLSearchParams(); + if (subscriptionId) params.append('subscriptionId', subscriptionId); + if (quizId) params.append('quizId', quizId); + + const endpoint = params.toString() ? `/todayQuiz?${params.toString()}` : '/todayQuiz'; + return apiRequest(endpoint, { method: 'GET' }); }, @@ -121,6 +126,14 @@ export const quizAPI = { }); }, + // TodayQuiz 답안 제출 + submitTodayQuizAnswer: async (quizId: string, answer: number, subscriptionId: string) => { + return apiRequest(`/quizzes/${quizId}`, { + method: 'POST', + body: JSON.stringify({ answer, subscriptionId }), + }); + }, + // 사용자별 퀴즈 히스토리 getUserQuizHistory: async (email: string, token: string) => { return apiRequest(`/quiz/history?email=${email}&token=${token}`, { From bf61642f48ce995636dffdb2863794eeeb9e9c4b Mon Sep 17 00:00:00 2001 From: wannabeing Date: Tue, 17 Jun 2025 17:43:25 +0900 Subject: [PATCH 19/47] =?UTF-8?q?fix:=20=EB=B9=8C=EB=93=9C=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/EmailTemplate.tsx | 1 - src/contexts/ModalContext.tsx | 2 +- src/hooks/useQuiz.ts | 2 +- src/utils/api.ts | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/common/EmailTemplate.tsx b/src/components/common/EmailTemplate.tsx index 93da3d5..b6874ba 100644 --- a/src/components/common/EmailTemplate.tsx +++ b/src/components/common/EmailTemplate.tsx @@ -58,7 +58,6 @@ const EmailTemplate: React.FC = ({ toEmail, quizLink }) => { {/* Subscription Settings */}
diff --git a/src/utils/api.ts b/src/utils/api.ts index 29e76b8..d549c2e 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -21,7 +21,18 @@ async function apiRequest( const response = await fetch(url, config); if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); + // 에러 응답 본문 파싱 + let errorData; + try { + errorData = await response.json(); + } catch { + errorData = { message: `HTTP error! status: ${response.status}` }; + } + + const error = new Error(errorData.message || `HTTP error! status: ${response.status}`); + (error as any).status = response.status; + (error as any).data = errorData; + throw error; } return await response.json(); @@ -127,10 +138,13 @@ export const quizAPI = { }, // TodayQuiz 답안 제출 - submitTodayQuizAnswer: async (quizId: string, answer: number, subscriptionId: string) => { + submitTodayQuizAnswer: async (quizId: string, answerNumber: number, subscriptionId: string) => { return apiRequest(`/quizzes/${quizId}`, { method: 'POST', - body: JSON.stringify({ answer, subscriptionId }), + body: JSON.stringify({ + answer: answerNumber.toString(), + subscriptionId: parseInt(subscriptionId) + }), }); }, From c0ff10b7a07a65bb6d9b70eec37eba4500a90b06 Mon Sep 17 00:00:00 2001 From: wannabeing Date: Fri, 20 Jun 2025 01:40:24 +0900 Subject: [PATCH 24/47] =?UTF-8?q?feat:=20=EA=B5=AC=EB=8F=85=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=88=98=EC=A0=95=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 4 +- .../sections/SubscriptionEditSection.tsx | 625 ++++++++++++++++++ .../TodayQuizSection.tsx} | 12 +- src/hooks/useSubscription.ts | 54 +- src/utils/api.ts | 33 +- 5 files changed, 677 insertions(+), 51 deletions(-) create mode 100644 src/components/sections/SubscriptionEditSection.tsx rename src/components/{TodayQuizPage.tsx => sections/TodayQuizSection.tsx} (98%) diff --git a/src/App.tsx b/src/App.tsx index 0e3e831..12f7e71 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,7 +5,8 @@ import LandingPage from './components/LandingPage'; import QuizSection from './components/sections/QuizSection'; import TodayEmailFormSection from './components/sections/TodayEmailFormSection'; import VerificationEmailPage from './components/VerificationEmailPage'; -import TodayQuizPage from './components/TodayQuizPage'; +import TodayQuizPage from './components/sections/TodayQuizSection'; +import SubscriptionEditSection from './components/sections/SubscriptionEditSection'; import Header from './components/common/Header'; import Footer from './components/common/Footer'; import SubscriptionModal from './components/common/SubscriptionModal'; @@ -38,6 +39,7 @@ function App() { } /> } /> } /> + } />