diff --git a/README.md b/README.md index 0e1211217..1317a557d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,15 @@ -# [your app name here] +# Sona CodePath WEB103 Final Project -Designed and developed by: [your names here] +Designed and developed by: + +- Gustave Ntwali +- Abdul Khan +- Carolina Herrera +- Chau Cao +- Dev Goswami +- Armin Erika Polanco πŸ”— Link to deployed app: @@ -10,39 +17,86 @@ Designed and developed by: [your names here] ### Description and Purpose -[text goes here] +**Sona** is a full-stack web app where users can browse artist profiles, follow their favorite artists, view upcoming concert dates, and shop artist merch all in one place. + +Music fans often have to juggle multiple platforms to keep up with an artist. From social media for updates, ticketing sites for shows, separate stores for merch. **Sona** exists to bring that experience together in one app, making it easier for fans to stay connected and for artists to reach their community directly. ### Inspiration -[text goes here] +This project builds on ideas from several of our previous coursework projects. Multiple members of our group have worked on music-themed projects earlier in the course that explored artist/music reference content, vinyl and merch customization, and community-oriented spaces. **Sona** brings these ideas together into a single hub where fans can follow artists, engage with a community built around music, and shop artist merch. Furthermore, we take inspiration from existing platforms, such as Ticketmaster and Bandcamp, to build from. ## Tech Stack -Frontend: - -Backend: +- Frontend: React (Vite), React Router +- Backend: Express, Node.js, PostgreSQL ## Features -### [Name of Feature 1] +### βœ… Artist Directory + +- Browse all artists with genre, bio, and profile image. Search bar w/ Filter by genre. + +- ![Artist Directory](https://i.imgur.com/aYOxXjl.gif) + +### βœ… Follow Artists + +- Users can follow/unfollow artists to build a personalized list of who they're keeping up with, with an optional "notify me on release" toggle. + +- ![Follow Artists](https://i.imgur.com/foZSTEV.gif) + +### βœ… Artist Posts + +Artist Admins can post updates like new merch drops, show announcements, news, etc that will be visible to their followers. + +- ![Artist Posts](https://i.imgur.com/Vj749I2.gif) + +### βœ… Artist Quick-View Panel + +- Slide-out modal showing artist details and discography without navigating away from the current page. + +- ![Artist Quick-View Panel](https://i.imgur.com/8dW0ZTp.gif) + +### Merch Shop with Filtering + +- Browse merch (vinyl, CD, apparel) filtered by artist, genre, or format. + +- [gif goes here] + +### Order System + +- Add merch to a cart and place an order; view order history. + +- [gif goes here] + +### Concert Listings + +- View upcoming shows per artist, filterable by city and/or date. + +- [gif goes here] + +### Following Feed + +- A personalized feed combining posts, upcoming shows, and merch drops from every artist a fan follows. + +- [gif goes here] -[short description goes here] +### (Stretch) Spotify Connect -[gif goes here] +- Link a Spotify account to import playlists and generate personalized recommendations. -### [Name of Feature 2] +- [gif goes here] -[short description goes here] +### (Stretch) Sona Mix -[gif goes here] +- Auto-generate a custom playlist from the artists you follow, featured right on your profile. -### [Name of Feature 3] +- [gif goes here] -[short description goes here] +### (Stretch) Real Payment Integration -[gif goes here] +- Real checkout system built on top of the existing order system. -### [ADDITIONAL FEATURES GO HERE - ADD ALL FEATURES HERE IN THE FORMAT ABOVE; you will check these off and add gifs as you complete them] +- [gif goes here] ## Installation Instructions diff --git a/milestones/milestone1.md b/milestones/milestone1.md index 52b9b0038..6770d0ca8 100644 --- a/milestones/milestone1.md +++ b/milestones/milestone1.md @@ -6,27 +6,27 @@ This document should be completed and submitted during **Unit 5** of this course This unit, be sure to complete all tasks listed below. To complete a task, place an `x` between the brackets. -- [ ] Read and understand all required features - - [ ] Understand you **must** implement **all** baseline features and **two** custom features -- [ ] In `readme.md`: update app name to your app's name -- [ ] In `readme.md`: add all group members' names -- [ ] In `readme.md`: complete the **Description and Purpose** section -- [ ] In `readme.md`: complete the **Inspiration** section -- [ ] In `readme.md`: list a name and description for all features (minimum 6 for full points) you intend to include in your app (in future units, you will check off features as you complete them and add GIFs demonstrating the features) -- [ ] In `planning/user_stories.md`: add all user stories (minimum 10 for full points) -- [ ] In `planning/user_stories.md`: use 1-3 unique user roles in your user stories -- [ ] In this document, complete all thre questions in the **Reflection** section below +- [x] Read and understand all required features + - [x] Understand you **must** implement **all** baseline features and **two** custom features +- [x] In `readme.md`: update app name to your app's name +- [x] In `readme.md`: add all group members' names +- [x] In `readme.md`: complete the **Description and Purpose** section +- [x] In `readme.md`: complete the **Inspiration** section +- [x] In `readme.md`: list a name and description for all features (minimum 6 for full points) you intend to include in your app (in future units, you will check off features as you complete them and add GIFs demonstrating the features) +- [x] In `planning/user_stories.md`: add all user stories (minimum 10 for full points) +- [x] In `planning/user_stories.md`: use 1-3 unique user roles in your user stories +- [x] In this document, complete all thre questions in the **Reflection** section below ## Reflection ### 1. What went well during this unit? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +We agreed on a concept quickly. ### 2. What were some challenges your group faced in this unit? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +Finalizing details across all members can be challenging but we will hopefully implement a better system going forward. ### 3. What additional support will you need in upcoming units as you continue to work on your final project? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +General guidance on keeping the team coordinated and on track as we move into the more technical milestones. diff --git a/milestones/milestone2.md b/milestones/milestone2.md index e3178cd81..0c1dc8fcf 100644 --- a/milestones/milestone2.md +++ b/milestones/milestone2.md @@ -6,24 +6,24 @@ This document should be completed and submitted during **Unit 6** of this course This unit, be sure to complete all tasks listed below. To complete a task, place an `x` between the brackets. -- [ ] In `planning/wireframes.md`: add wireframes for at least three pages in your web app. - - [ ] Include a list of pages in your app -- [ ] In `planning/entity_relationship_diagram.md`: add the entity relationship diagram you developed for your database. - - [ ] Your entity relationship diagram should include the tables in your database. -- [ ] Prepare your three-minute pitch presentation, to be presented during Unit 7 (the next unit). - - [ ] You do **not** need to submit any materials in advance of your pitch. -- [ ] In this document, complete all three questions in the **Reflection** section below +- [x] In `planning/wireframes.md`: add wireframes for at least three pages in your web app. + - [x] Include a list of pages in your app +- [x] In `planning/entity_relationship_diagram.md`: add the entity relationship diagram you developed for your database. + - [x] Your entity relationship diagram should include the tables in your database. +- [x] Prepare your three-minute pitch presentation, to be presented during Unit 7 (the next unit). + - [x] You do **not** need to submit any materials in advance of your pitch. +- [x] In this document, complete all three questions in the **Reflection** section below ## Reflection ### 1. What went well during this unit? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +Our team successesfuly collaborated and made decisions as a group. ### 2. What were some challenges your group faced in this unit? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +It can be difficult to find a time where everyone is available. ### 3. What additional support will you need in upcoming units as you continue to work on your final project? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +Delegating the coding portion between all group members moving forward. diff --git a/milestones/milestone3.md b/milestones/milestone3.md index b45ad8561..8fc7692eb 100644 --- a/milestones/milestone3.md +++ b/milestones/milestone3.md @@ -8,44 +8,45 @@ This unit, be sure to complete all tasks listed below. To complete a task, place You will need to reference the GitHub Project Management guide in the course portal for more information about how to complete each of these steps. -- [ ] In your repo, create a project board. - - *Please be sure to share your project board with the grading team's GitHub **codepathreview**. This is separate from your repository's sharing settings.* -- [ ] In your repo, create at least 5 issues from the features on your feature list. +- [x] In your repo, create a project board. + - _Please be sure to share your project board with the grading team's GitHub **codepathreview**. This is separate from your repository's sharing settings._ +- [x] In your repo, create at least 5 issues from the features on your feature list. - List the title of each issue you created: - 1. - 2. - 3. - 4. - 5. -- [ ] In your repo, update the status of issues in your project board. -- [ ] In your repo, create a GitHub Milestone for each final project unit, corresponding to each of the 5 milestones in your `milestones/` directory. + > 1. Set up Express + PostgreSQL project structure & schema + > 2. Artist CRUD + Directory + Detail Page + > 3. Follow / Unfollow + > 4. Artist Posts + > 5. Quick View Slide-Out Panel +- [x] In your repo, update the status of issues in your project board. +- [x] In your repo, create a GitHub Milestone for each final project unit, corresponding to each of the 5 milestones in your `milestones/` directory. - List the name of each milestone you created: - 1. - 2. - 3. - - [ ] Set the completion percentage of each milestone. The GitHub Milestone for this unit (Milestone 3 - Unit 7) should be 100% completed when you submit for full points. -- [ ] In `readme.md`, check off the features you have completed in this unit by adding a βœ… emoji in front of the feature's name. - - [ ] Under each feature you have completed, include a GIF showing feature functionality. -- [ ] In this documents, complete all five questions in the **Reflection** section below. + > 1. Milestone 3 + > 2. Milestone 4 + > 3. Milestone 5 + > 4. Stretch Goals + - [x] Set the completion percentage of each milestone. The GitHub Milestone for this unit (Milestone 3 - Unit 7) should be 100% completed when you submit for full points. +- [x] In `readme.md`, check off the features you have completed in this unit by adding a βœ… emoji in front of the feature's name. + - [x] Under each feature you have completed, include a GIF showing feature functionality. +- [x] In this documents, complete all five questions in the **Reflection** section below. ## Reflection ### 1. What went well during this unit? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +> Learned alot about project management and organization for coding projects and had a succesful first sprint. ### 2. What were some challenges your group faced in this unit? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +> Coordinating around issue dependencies took some time to get right with planning and thinking ahead. ### Did you finish all of your tasks in your sprint plan for this week? If you did not finish all of the planned tasks, how would you prioritize the remaining tasks on your list? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +> Yes. We set up our GitHub Project board, issues, and milestones, and got real backend/database work started, including both of our required one-to-one relationships and our full-CRUD entity. ### Which features and user stories would you consider β€œat risk”? How will you change your plan if those items remain β€œat risk”? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +> Nothing is currently at risk, everything planned for this unit was finished. Going forward, the next sprints will have more complicated features so hoping the best for that. ### 5. What additional support will you need in upcoming units as you continue to work on your final project? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +> Guidance on authentication once we reach that unit β€” several features (Artist Admin permissions, a real "who's logged in" concept) are currently using temporary placeholders until real login is covered in class. diff --git a/milestones/progress_report_3.md b/milestones/progress_report_3.md new file mode 100644 index 000000000..e302db14e --- /dev/null +++ b/milestones/progress_report_3.md @@ -0,0 +1,69 @@ +# Milestone 3 Progress Report + +## GitHub Project Setup + +Project board created: + +> YES + +Project board shared with codepathreview: + +> YES + +## Issues Created + +List the title of each issue you created, one per line: + +> - Issue 0: Local Dev Environment Setup +> - Issue 1: Set up Express + PostgreSQL project structure & schema +> - Issue 2: Artist CRUD + Directory + Detail Page +> - Issue 3: Follow / Unfollow +> - Issue 4: Artist Posts +> - Issue 5: Quick View Slide-Out Panel + +## GitHub Milestones Created + +List the name of each GitHub Milestone you created, one per line: + +> - Milestone 3 +> - Milestone 4 +> - Milestone 5 +> - Stretch Goals + +## Milestone 3 Completion + +Replace the blank before the percent sign with a number from 0 to 100. + +> Completion percentage for Milestone 3: 100% + +List each issue you completed this unit and the main file or folder where that work lives, one per line: + +- Example: User login - server/routes/auth.js + +> - Project Setup + Schema +> - server/config/ +> - Artist CRUD + Directory + Detail Page +> - server/routes/artists.js +> - client/src/pages/Directory.jsx +> - client/src/pages/ArtistDetail.jsx +> - Follow / Unfollow +> - server/routes/follows.jsx +> - client/src/components/FollowButton.jsx +> - Artist Posts +> - server/routes/posts.js +> - client/src/pages/PostCreateForm.jsx +> - client/src/pages/PostEditForm.jsx +> - client/src/components/ArtistPost.jsx +> - Quick View Slide-Out Panel +> - client/src/components/QuickViewPanel.jsx + +## Features Completed This Unit + +List each feature you completed and checked off in readme.md this unit, one per line: + +- Example: Search events by category + +> - Artist Directory (browse, search, genre filter) +> - Follow / Unfollow artists +> - Artist Posts +> - Quick View Slide-Out Panel diff --git a/planning/entity_relationship_diagram.md b/planning/entity_relationship_diagram.md index 12c25f62c..3bdbc4964 100644 --- a/planning/entity_relationship_diagram.md +++ b/planning/entity_relationship_diagram.md @@ -4,14 +4,16 @@ Reference the Creating an Entity Relationship Diagram final project guide in the ## Create the List of Tables -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ List each table in your diagram] +1. users +2. artists +3. admin +4. profile +5. follows +6. concerts +7. merch +8. albums +9. purchases ## Add the Entity Relationship Diagram -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ Include an image or images of the diagram below. You may also wish to use the following markdown syntax to outline each table, as per your preference.] - -| Column Name | Type | Description | -|-------------|------|-------------| -| id | integer | primary key | -| name | text | name of the shoe model | -| ... | ... | ... | +![sona erd](https://i.imgur.com/jX0Vh5t.png) diff --git a/planning/user_stories.md b/planning/user_stories.md index 1e55ecbcd..d009a6103 100644 --- a/planning/user_stories.md +++ b/planning/user_stories.md @@ -4,10 +4,25 @@ Reference the Writing User Stories final project guide in the course portal for ## Outline User Roles -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ Include at least at least 1, but no more than 3, user roles.] +- Fan +- Artist Admin ## Draft User Stories -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ Include at least at least 10 user stories in this format:] +Include at least at least 10 user stories in this format: -1. As a [user role], I want to [what], so that [why]. +_As a [user role], I want to [what], so that [why]._ + +1. As a Fan, I want to create an account and log in, so that I can save my preferences and see order history. +2. As a Fan, I want to browse a list of artists, so that I can discover new music. +3. As a Fan, I want to view an individual artist's profile page, so that I can see their bio, socials, and Spotify link. +4. As a Fan, I want to follow an artist, so that I get updates when they release new merch or announce shows. +5. As a Fan, I want to unfollow an artist, so that I can curate who I see updates from. +6. As a Fan, I want to browse merch filtered by artist or format, so that I can find what I want quickly. +7. As a Fan, I want to add items to a cart and place an order, so that I can purchase merch. +8. As a Fan, I want to view my order history, so that I can track past purchases. +9. As a Fan, I want to see upcoming concerts for an artist I follow, filtered by city, so that I can find shows near me. +10. As a Fan, I want to see a quick-view panel when I click an artist, so that I can preview their info without leaving the page. +11. As an Artist Admin, I want to create/edit my artist profile, so that fans have accurate bio and social info. +12. As an Artist Admin, I want to add new merch items to my store, so that fans can purchase them. +13. As an Artist Admin, I want to post upcoming concert dates, so that fans know where to see me live. diff --git a/planning/wireframes.md b/planning/wireframes.md index fbcd15a0c..5793d52f5 100644 --- a/planning/wireframes.md +++ b/planning/wireframes.md @@ -4,18 +4,38 @@ Reference the Creating an Entity Relationship Diagram final project guide in the ## List of Pages -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ List the pages you expect to have in your app, with a ⭐ next to pages you have wireframed] +- Login +- Edit User +- Edit Artist_User +- Artist Directory ⭐ +- Artist Directory Side-Panel ⭐ +- Artist Detailed ⭐ +- Merch Shop ⭐ +- User's Profile > Following ⭐ +- User's Profile > Order History ⭐ +- Checkout Cart +- Error Default -## Wireframe 1: [page title] +## Wireframe 1: Artist Directory -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ include wireframe 1] +![artist directory](https://i.imgur.com/EeJvvYD.png) -## Wireframe 2: [page title] +## Wireframe 2: Artist Directory Side-Panel -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ include wireframe 2] +![Artist Directory but with a side-view panel](https://i.imgur.com/ZjfTBHA.png) -## Wireframe 3: [page title] +## Wireframe 3: Artist Detailed -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ include wireframe 3] +![Artist Detailed Page](https://i.imgur.com/3iKhYRY.png) -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ include more wireframes as desired] +## Wireframe 4: Merch Shop + +![Merch Shop Page](https://i.imgur.com/tjC6Gx6.png) + +## Wireframe 5: User's Profile > Following Tab + +![User's Profile Following Tab](https://i.imgur.com/DCR2sVQ.png) + +## Wireframe 6: User's Profile > Order History Tab + +![User's Profile Order History Tab](https://i.imgur.com/dDn9gBX.png) diff --git a/sona-app/.gitignore b/sona-app/.gitignore new file mode 100644 index 000000000..91ffe1b28 --- /dev/null +++ b/sona-app/.gitignore @@ -0,0 +1,27 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Credentials +.env \ No newline at end of file diff --git a/sona-app/client/.oxlintrc.json b/sona-app/client/.oxlintrc.json new file mode 100644 index 000000000..125507823 --- /dev/null +++ b/sona-app/client/.oxlintrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["react", "oxc"], + "rules": { + "react/rules-of-hooks": "error", + "react/only-export-components": ["warn", { "allowConstantExport": true }] + } +} diff --git a/sona-app/client/index.html b/sona-app/client/index.html new file mode 100644 index 000000000..a9e01e819 --- /dev/null +++ b/sona-app/client/index.html @@ -0,0 +1,27 @@ + + + + + + + + + + + Sona + + +
+ + + diff --git a/sona-app/client/package-lock.json b/sona-app/client/package-lock.json new file mode 100644 index 000000000..e8ba721f0 --- /dev/null +++ b/sona-app/client/package-lock.json @@ -0,0 +1,1390 @@ +{ + "name": "client", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "client", + "version": "0.0.0", + "dependencies": { + "react": "^19.2.7", + "react-dom": "^19.2.7", + "react-router-dom": "^7.18.1" + }, + "devDependencies": { + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.3", + "oxlint": "^1.71.0", + "vite": "^8.1.1" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@oxlint/binding-android-arm-eabi": { + "version": "1.74.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.74.0.tgz", + "integrity": "sha512-+gHd12muVI9ZLBaWLPkHt3Fj7jihFjgQ1MGtBaRL8vWrWrI0P7dLUty/cHrHS0oqPYIRgQUJsPu2CExQuMcwNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-android-arm64": { + "version": "1.74.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.74.0.tgz", + "integrity": "sha512-xjKdoMB+H+RCOByv/7l7nfIGW9mlOisqYdcyC75UqYuQecLpReAeEYUf2CNeDEI3KtmUgxpRw/+c63y4AeF/Bw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-arm64": { + "version": "1.74.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.74.0.tgz", + "integrity": "sha512-iUK7wvc6sejMKsC+Pt67mntoF5weFcyEunhZfLJceU6gL419mexz5wBkSx/EnkFBExMLNtOi9fnDSc5xfK0IzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-x64": { + "version": "1.74.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.74.0.tgz", + "integrity": "sha512-ggKc/tn5SJ1u2yG2izC6VKODfYKV8MQ2AicJlNzOjuyrC29udvOef6/JzK2r32xqCnBDLFouR1VCkjzEI0/N9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-freebsd-x64": { + "version": "1.74.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.74.0.tgz", + "integrity": "sha512-u++dH/43jy9hTLbneaWlS0gla/Bp1JdwJ2zgevCl8nDFUh6qRCGMxcL0f0lb7By3A9p/LfFr+7cG4HU1hG856g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-gnueabihf": { + "version": "1.74.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.74.0.tgz", + "integrity": "sha512-Sj1zmtFDVTPeIbIz4ZfcXAbFHqCmKCXdCUlAJzvTF7I20NTH1RDpoF2PhkqNODutJzVhJYmm3oz0GwgY+tvE2g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-musleabihf": { + "version": "1.74.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.74.0.tgz", + "integrity": "sha512-//PKyQb/tQXcHArx2f7z+oVI/eMS2Jpv+edNuAtOrgIhWdGcpHxogveAxzmF2rpH1AIHp4Hq04RF/rgJdiICnQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-gnu": { + "version": "1.74.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.74.0.tgz", + "integrity": "sha512-/k1Me+aX2tjuH10K62mLS0y8cLkJBHX6Ce0xPK+eWeel4bSdEGZ8dv4+hYMzg0GrSmjwy4yAYsDPeEeKBft/2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-musl": { + "version": "1.74.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.74.0.tgz", + "integrity": "sha512-3tFSjBxc5D8/zvjEuLvOqcA8ZXKD0+6NuaVO/edeamNc49MoAsbfaC9s1UiwODwgF6slGaF8yJA2TPkukd77tg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-ppc64-gnu": { + "version": "1.74.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.74.0.tgz", + "integrity": "sha512-9QggtPkSPXOCTu8Szis7auOK/sC7KdQaN+/TujP7YVVhzCAOhgdRfgv8uEz0r2tk5xdgus5rLYUrCDoZNtiRUw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-gnu": { + "version": "1.74.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.74.0.tgz", + "integrity": "sha512-VM5VPUJ4DJIWiK+AZn8FScUqMr6OFrCAYybMYjEEi7W13ParI64MByiXTkKMqZpBmvQ9zxl9Ebq2VUOiZRJYUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-musl": { + "version": "1.74.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.74.0.tgz", + "integrity": "sha512-SaDY1gh9rOA592J54g+gu5hkOFFQBZsMmIYHs+NRHG+Uq0OxtuuCXMWQ3vu1830Eugv5uMXyjG+bv2Z9y4IXjw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-s390x-gnu": { + "version": "1.74.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.74.0.tgz", + "integrity": "sha512-ZATQeHZCyr6MbDveg0obD5sxLHFOghtOdC5jwVwYlvFWqtFOxctgFEG6Ef/64hYvZrWyhyCckB10AelqLopeDA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-gnu": { + "version": "1.74.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.74.0.tgz", + "integrity": "sha512-+aIvJyrdeD7LwCQ2WYLMUWNmnbeDRSPb40aBYtPjD9+PTqUwgJnk+HK5yLfSMeqXrMrDhE9uTmtt2y50tvjhHw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-musl": { + "version": "1.74.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.74.0.tgz", + "integrity": "sha512-XyktaR8lhK2qWiCK0Tk8oYD+/cgn+oHA6ddRnxSSXUKkkojkV78CmShZUxQF+yrBFs0SuW+JBOPG6hecyc/iZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-openharmony-arm64": { + "version": "1.74.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.74.0.tgz", + "integrity": "sha512-mzbjrPl4neaVUiJ1fUiEUxTGaSZBoiKtaoB6jmIpz9S+VOA2vDYmJpihQ82w6178V5jxziclTg8Cgj5yF6tTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-arm64-msvc": { + "version": "1.74.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.74.0.tgz", + "integrity": "sha512-vUAe9okpS2Oa5+lX67lqHMuNUvfkleRKwrUDJ/WJBsgmddvZ1mrsh2HVmuFDRzqFELhaJhFaCNOuR6a7L3rtIA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-ia32-msvc": { + "version": "1.74.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.74.0.tgz", + "integrity": "sha512-yyXXJyYYSXL4I8K8jAWjJs+J3fa9gH2JmEbo4f5adm+1tNC9itseicBNuwK7BDHvqQ5J534s+yDULu89vYL2ZQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-x64-msvc": { + "version": "1.74.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.74.0.tgz", + "integrity": "sha512-VTC9IYTIMrVUk/i6Ms1ohzzDKZFkWn0KU2OBbPBzgmVZ2V30165T/zK4LztTr0Xgp9fZ1qQZ1rsZAu/rEmySlA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz", + "integrity": "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/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/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "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/oxlint": { + "version": "1.74.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.74.0.tgz", + "integrity": "sha512-odGl2s2x5IOJoj3A0v1k0PGBXVFBZeZ2+AK/+K2MJur7Ghi3bkyX5NuLUWHKqa4js1wjep3hJeuTQJOlr+4+dA==", + "dev": true, + "license": "MIT", + "bin": { + "oxlint": "bin/oxlint" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxlint/binding-android-arm-eabi": "1.74.0", + "@oxlint/binding-android-arm64": "1.74.0", + "@oxlint/binding-darwin-arm64": "1.74.0", + "@oxlint/binding-darwin-x64": "1.74.0", + "@oxlint/binding-freebsd-x64": "1.74.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.74.0", + "@oxlint/binding-linux-arm-musleabihf": "1.74.0", + "@oxlint/binding-linux-arm64-gnu": "1.74.0", + "@oxlint/binding-linux-arm64-musl": "1.74.0", + "@oxlint/binding-linux-ppc64-gnu": "1.74.0", + "@oxlint/binding-linux-riscv64-gnu": "1.74.0", + "@oxlint/binding-linux-riscv64-musl": "1.74.0", + "@oxlint/binding-linux-s390x-gnu": "1.74.0", + "@oxlint/binding-linux-x64-gnu": "1.74.0", + "@oxlint/binding-linux-x64-musl": "1.74.0", + "@oxlint/binding-openharmony-arm64": "1.74.0", + "@oxlint/binding-win32-arm64-msvc": "1.74.0", + "@oxlint/binding-win32-ia32-msvc": "1.74.0", + "@oxlint/binding-win32-x64-msvc": "1.74.0" + }, + "peerDependencies": { + "oxlint-tsgolint": ">=0.24.0", + "vite-plus": "*" + }, + "peerDependenciesMeta": { + "oxlint-tsgolint": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/react-router": { + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.1.tgz", + "integrity": "sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==", + "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.18.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.1.tgz", + "integrity": "sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg==", + "license": "MIT", + "dependencies": { + "react-router": "7.18.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/rolldown": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.139.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "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/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/vite": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", + "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.17", + "rolldown": "~1.1.5", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/sona-app/client/package.json b/sona-app/client/package.json new file mode 100644 index 000000000..555ec8c9c --- /dev/null +++ b/sona-app/client/package.json @@ -0,0 +1,24 @@ +{ + "name": "sona-client", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "oxlint", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.2.7", + "react-dom": "^19.2.7", + "react-router-dom": "^7.18.1" + }, + "devDependencies": { + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.3", + "oxlint": "^1.71.0", + "vite": "^8.1.1" + } +} diff --git a/sona-app/client/public/favicon-16x16.png b/sona-app/client/public/favicon-16x16.png new file mode 100644 index 000000000..81ad1921f Binary files /dev/null and b/sona-app/client/public/favicon-16x16.png differ diff --git a/sona-app/client/public/favicon-180x180.png b/sona-app/client/public/favicon-180x180.png new file mode 100644 index 000000000..323e70182 Binary files /dev/null and b/sona-app/client/public/favicon-180x180.png differ diff --git a/sona-app/client/public/favicon-192x192.png b/sona-app/client/public/favicon-192x192.png new file mode 100644 index 000000000..1dd406fd3 Binary files /dev/null and b/sona-app/client/public/favicon-192x192.png differ diff --git a/sona-app/client/public/favicon-32x32.png b/sona-app/client/public/favicon-32x32.png new file mode 100644 index 000000000..796068642 Binary files /dev/null and b/sona-app/client/public/favicon-32x32.png differ diff --git a/sona-app/client/public/favicon-48x48.png b/sona-app/client/public/favicon-48x48.png new file mode 100644 index 000000000..adaf0e0bc Binary files /dev/null and b/sona-app/client/public/favicon-48x48.png differ diff --git a/sona-app/client/public/favicon-512x512.png b/sona-app/client/public/favicon-512x512.png new file mode 100644 index 000000000..ff801115e Binary files /dev/null and b/sona-app/client/public/favicon-512x512.png differ diff --git a/sona-app/client/public/favicon.ico b/sona-app/client/public/favicon.ico new file mode 100644 index 000000000..947f57d79 Binary files /dev/null and b/sona-app/client/public/favicon.ico differ diff --git a/sona-app/client/public/favicon.svg b/sona-app/client/public/favicon.svg new file mode 100644 index 000000000..6f9ae428d --- /dev/null +++ b/sona-app/client/public/favicon.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + S + \ No newline at end of file diff --git a/sona-app/client/src/App.jsx b/sona-app/client/src/App.jsx new file mode 100644 index 000000000..91e080cf9 --- /dev/null +++ b/sona-app/client/src/App.jsx @@ -0,0 +1,30 @@ +import { Link, Outlet } from "react-router-dom"; +import logo from "./assets/sona-logo-tagline.svg"; + +function App() { + return ( + <> + +
+ +
+ + ); +} + +export default App; diff --git a/sona-app/client/src/api.js b/sona-app/client/src/api.js new file mode 100644 index 000000000..8426cf351 --- /dev/null +++ b/sona-app/client/src/api.js @@ -0,0 +1,110 @@ +const BASE = import.meta.env.VITE_API_URL || "http://localhost:3001"; + +async function request(path, options) { + const res = await fetch(`${BASE}/api${path}`, { + headers: { "Content-Type": "application/json" }, + ...options, + }); + if (!res.ok) throw new Error(`Request failed: ${res.status}`); + return res.status === 204 ? null : res.json(); +} + +export const getArtists = ({ q = "", genre = "" } = {}) => { + const params = new URLSearchParams(); + if (q) params.set("q", q); + if (genre) params.set("genre", genre); + const qs = params.toString(); + return request(`/artists${qs ? `?${qs}` : ""}`); +}; + +export const getArtist = (id) => request(`/artists/${id}`); + +export const updateArtist = (id, data) => + request(`/artists/${id}`, { method: "PATCH", body: JSON.stringify(data) }); + +export const deleteArtist = (id, userId) => + request(`/artists/${id}`, { + method: "DELETE", + body: JSON.stringify({ user_id: userId }), + }); + +export const getAdminOf = (userId) => request(`/users/${userId}/admin-of`); + +export const getFollowing = (userId) => request(`/users/${userId}/following`); + +export const followArtist = (userId, artistId, notify) => + request(`/follows`, { + method: "POST", + body: JSON.stringify({ + user_id: userId, + artist_id: artistId, + notify_on_release: notify, + }), + }); + +export const unfollowArtist = (userId, artistId) => + request(`/follows/${artistId}?user_id=${userId}`, { method: "DELETE" }); + +export const getPostsByArtist = (artistId) => + request(`/artists/${artistId}/posts`); + +export const getPost = (postId) => request(`/posts/${postId}`); + +export const createPost = (userId, artistId, content) => { + return request(`/artists/${artistId}/posts`, { + method: "POST", + body: JSON.stringify({ + user_id: userId, + artist_id: artistId, + content: content, + }), + }); +}; + +export const updatePost = (postId, userId, artistId, content) => { + return request(`/posts/${postId}`, { + method: "PATCH", + body: JSON.stringify({ + user_id: userId, + artist_id: artistId, + content: content, + }), + }); +}; + +export const deletePost = (postId, userId, artistId) => { + return request(`/posts/${postId}`, { + method: "DELETE", + body: JSON.stringify({ + user_id: userId, + artist_id: artistId, + }), + }); +}; + +export const getMerch = ({ artist_id = "", type = "", sort = "" } = {}) => { + const params = new URLSearchParams(); + if (artist_id) params.set("artist_id", artist_id); + if (type) params.set("type", type); + if (sort) params.set("sort", sort); + const qs = params.toString(); + return request(`/merch${qs ? `?${qs}` : ""}`); +}; + +export const getArtistMerch = (artistId) => + request(`/artists/${artistId}/merch`); + +export const createMerch = (artistId, data) => + request(`/artists/${artistId}/merch`, { + method: "POST", + body: JSON.stringify(data), + }); + +export const updateMerch = (id, data) => + request(`/merch/${id}`, { method: "PATCH", body: JSON.stringify(data) }); + +export const deleteMerch = (id, userId) => + request(`/merch/${id}`, { + method: "DELETE", + body: JSON.stringify({ user_id: userId }), + }); diff --git a/sona-app/client/src/assets/sona-logo-tagline.png b/sona-app/client/src/assets/sona-logo-tagline.png new file mode 100644 index 000000000..8e0ff943b Binary files /dev/null and b/sona-app/client/src/assets/sona-logo-tagline.png differ diff --git a/sona-app/client/src/assets/sona-logo-tagline.svg b/sona-app/client/src/assets/sona-logo-tagline.svg new file mode 100644 index 000000000..2d41dcfa7 --- /dev/null +++ b/sona-app/client/src/assets/sona-logo-tagline.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + S + + Sona + ARTIST + FAN HUB + \ No newline at end of file diff --git a/sona-app/client/src/components/ArtistCard.jsx b/sona-app/client/src/components/ArtistCard.jsx new file mode 100644 index 000000000..46ee562fb --- /dev/null +++ b/sona-app/client/src/components/ArtistCard.jsx @@ -0,0 +1,31 @@ +import { Link } from "react-router-dom"; +import FollowButton from "./FollowButton.jsx"; + +export default function ArtistCard({ + artist, + onQuickView, + initialFollowing = false, + initialNotify = false, +}) { + return ( +
+ + {artist.name} + + +
{artist.name}
+ {artist.genre && {artist.genre}} + +
+ + +
+
+ ); +} diff --git a/sona-app/client/src/components/ArtistPost.jsx b/sona-app/client/src/components/ArtistPost.jsx new file mode 100644 index 000000000..6c9edbce5 --- /dev/null +++ b/sona-app/client/src/components/ArtistPost.jsx @@ -0,0 +1,42 @@ +import { Link, useNavigate } from "react-router-dom"; +import currentUser from "../currentUser.js"; +import { deletePost } from "../api.js"; + +export default function ArtistPost({ postDetails, isAdmin }) { + const pubDate = new Date(postDetails.posted_on); + const options = { year: "numeric", month: "long", day: "numeric" }; + const formattedDate = new Intl.DateTimeFormat("en-US", options).format( + pubDate, + ); + const navigate = useNavigate(); + + async function handleDelete() { + if (!confirm(`Delete post?`)) return; + await deletePost(postDetails.post_id, currentUser.id, postDetails.artist_id); + navigate(0); + } + + return ( +
+

{formattedDate}

+

{postDetails.content}

+ {isAdmin && ( +
+ + Edit + + +
+ )} +
+ ); +} diff --git a/sona-app/client/src/components/CreateMerchForm.jsx b/sona-app/client/src/components/CreateMerchForm.jsx new file mode 100644 index 000000000..033992d07 --- /dev/null +++ b/sona-app/client/src/components/CreateMerchForm.jsx @@ -0,0 +1,68 @@ +import { useState } from "react"; +import { createMerch } from "../api.js"; +import currentUser from "../currentUser.js"; + +export default function CreateMerchForm({ artistId, onCreated }) { + const [form, setForm] = useState({ name: "", type: "", price: "", stock: "", photo: "" }); + const [submitted, setSubmitted] = useState(false); + const [error, setError] = useState(""); + + async function handleSubmit(event) { + event.preventDefault(); + setError(""); + try { + const created = await createMerch(artistId, { + ...form, + price: Number(form.price), + stock: Number(form.stock) || 0, + user_id: currentUser.id, + }); + setSubmitted(true); + onCreated(created); + } catch (err) { + setError("Failed to create merch."); + } + } + + return ( +
+ setForm({ ...form, name: e.target.value })} + required + /> + setForm({ ...form, type: e.target.value })} + required + /> + setForm({ ...form, price: e.target.value })} + required + /> + setForm({ ...form, stock: e.target.value })} + /> + setForm({ ...form, photo: e.target.value })} + /> + + {error &&

{error}

} + + +
+ ); +} \ No newline at end of file diff --git a/sona-app/client/src/components/EditMerchForm.jsx b/sona-app/client/src/components/EditMerchForm.jsx new file mode 100644 index 000000000..86b170220 --- /dev/null +++ b/sona-app/client/src/components/EditMerchForm.jsx @@ -0,0 +1,55 @@ +import { useState } from "react"; +import { updateMerch } from "../api.js"; +import currentUser from "../currentUser.js"; + +export default function EditMerchForm({ merch, onSaved, onCancel }) { + const [form, setForm] = useState({ + name: merch.name, + price: merch.price, + stock: merch.stock, + photo: merch.photo || "", + }); + + async function handleSubmit(event) { + event.preventDefault(); + const updated = await updateMerch(merch.id, { + ...form, + price: Number(form.price), + stock: Number(form.stock), + user_id: currentUser.id, + }); + onSaved(updated); + } + + return ( +
+ setForm({ ...form, name: e.target.value })} + placeholder="Name" + /> + setForm({ ...form, price: e.target.value })} + placeholder="Price" + /> + setForm({ ...form, stock: e.target.value })} + placeholder="Stock" + /> + setForm({ ...form, photo: e.target.value })} + placeholder="Photo URL" + /> +
+ + +
+
+ ); +} \ No newline at end of file diff --git a/sona-app/client/src/components/FollowButton.jsx b/sona-app/client/src/components/FollowButton.jsx new file mode 100644 index 000000000..e58ca165f --- /dev/null +++ b/sona-app/client/src/components/FollowButton.jsx @@ -0,0 +1,62 @@ +import { useState } from "react"; +import currentUser from "../currentUser.js"; +import { followArtist, unfollowArtist } from "../api.js"; + +export default function FollowButton({ + artistId, + initialFollowing, + initialNotify, +}) { + const [following, setFollowing] = useState(initialFollowing); + const [notify, setNotify] = useState(initialNotify); + const [toast, setToast] = useState(""); + + function showToast(message) { + setToast(message); + setTimeout(() => setToast(""), 2000); + } + + async function toggleFollow() { + try { + if (following) { + await unfollowArtist(currentUser.id, artistId); + setFollowing(false); + setNotify(false); + showToast("Unfollowed"); + } else { + await followArtist(currentUser.id, artistId, notify); + setFollowing(true); + showToast("Following"); + } + } catch { + showToast("Something went wrong"); + } + } + + async function toggleNotify(event) { + const next = event.target.checked; + setNotify(next); + if (following) await followArtist(currentUser.id, artistId, next); + } + + return ( + <> + + + {following && ( + + )} + + {toast && {toast}} + + ); +} diff --git a/sona-app/client/src/components/MerchCard.jsx b/sona-app/client/src/components/MerchCard.jsx new file mode 100644 index 000000000..8c6d8ff7c --- /dev/null +++ b/sona-app/client/src/components/MerchCard.jsx @@ -0,0 +1,53 @@ +import { useState } from "react"; +import { deleteMerch } from "../api.js"; +import currentUser from "../currentUser.js"; +import EditMerchForm from "./EditMerchForm.jsx"; + +export default function MerchCard({ merch, isAdmin, onUpdated, onDeleted }) { + const [editing, setEditing] = useState(false); + const [deleted, setDeleted] = useState(false); + + async function handleDelete() { + if (!confirm(`Delete ${merch.name}?`)) return; + await deleteMerch(merch.id, currentUser.id); + setDeleted(true); + onDeleted(merch.id); + } + + if (editing) { + return ( +
+ { + setEditing(false); + onUpdated(updated); + }} + onCancel={() => setEditing(false)} + /> +
+ ); + } + + return ( +
+ {merch.name} + +
{merch.name}
+ {merch.type} + +
+ ${Number(merch.price).toFixed(2)} +
+ + {isAdmin && ( +
+ + +
+ )} +
+ ); +} \ No newline at end of file diff --git a/sona-app/client/src/components/QuickViewPanel.css b/sona-app/client/src/components/QuickViewPanel.css new file mode 100644 index 000000000..9654ec231 --- /dev/null +++ b/sona-app/client/src/components/QuickViewPanel.css @@ -0,0 +1,104 @@ +.qv-overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: rgba(0, 0, 0, 0.5); + z-index: 100; +} + +.qv-panel { + position: fixed; + top: 0; + right: 0; + width: 480px; + height: 100vh; + background: #ffffff; + z-index: 101; + overflow-y: auto; + padding: 24px; + box-shadow: -4px 0 24px rgba(0, 0, 0, 0.15); + animation: slideIn 0.25s ease-out; +} + +@keyframes slideIn { + from { transform: translateX(100%); } + to { transform: translateX(0); } +} + +.qv-close { + position: absolute; + top: 16px; + right: 16px; + background: none; + border: none; + font-size: 20px; + cursor: pointer; + color: #555; +} + +.qv-image { + width: 100%; + height: 200px; + object-fit: cover; + border-radius: 8px; + margin-bottom: 16px; +} + +.qv-name { + font-size: 22px; + font-weight: 700; + margin: 0 0 8px 0; + color: #222; +} + +.qv-genre { + display: inline-block; + background: #eeeeee; + border-radius: 999px; + padding: 4px 12px; + font-size: 12px; + color: #555; + margin-bottom: 16px; +} + +.qv-bio { + font-size: 14px; + color: #444; + line-height: 1.6; + margin-bottom: 20px; +} + +.qv-socials { + display: flex; + gap: 12px; + margin-bottom: 20px; +} +.qv-socials a { + font-size: 13px; + color: #333; + text-decoration: underline; +} + +.qv-spotify { + border: none; + border-radius: 8px; + margin-bottom: 20px; + display: block; +} + +.qv-full-profile { + display: block; + text-align: center; + background: #222; + color: #fff; + padding: 12px; + border-radius: 8px; + text-decoration: none; + font-size: 14px; + font-weight: 600; +} +.qv-full-profile:hover { + background: #444; +} \ No newline at end of file diff --git a/sona-app/client/src/components/QuickViewPanel.jsx b/sona-app/client/src/components/QuickViewPanel.jsx new file mode 100644 index 000000000..dafd74fbe --- /dev/null +++ b/sona-app/client/src/components/QuickViewPanel.jsx @@ -0,0 +1,59 @@ +import "./QuickViewPanel.css"; + +export default function QuickViewPanel({ artist, onClose }) { + if (!artist) return null; + + return ( + <> +
+
+ + {artist.name} + +

{artist.name}

+ {artist.genre} +

{artist.description}

+ +
+ {artist.instagram && ( + + Instagram + + )} + {artist.twitter && ( + + X + + )} + {artist.facebook && ( + + Facebook + + )} + {artist.tiktok && ( + + TikTok + + )} +
+ + {artist.spotify && ( +