diff --git a/.editorconfig b/.editorconfig index f40d3d29..f4da4406 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,5 +8,5 @@ trim_trailing_whitespace = true indent_size = 4 indent_style = space -[*.nix] +[*.{nix,yaml,yml}] indent_size = 2 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 6a9ed57a..f11d0a2c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,21 +1,20 @@ --- name: Bug report about: Create a report to help us improve -title: "[BUG] " +title: '[BUG] ' labels: '' assignees: '' - --- **Pre-submission Checklist** -- [ ] I have searched existing issues to make sure this is not a duplicate -- [ ] I have cleared my browser cache and confirmed the issue persists -- [ ] I have checked this issue affects the latest version of the extension -- [ ] I have disabled other extensions to ensure this isn't a conflict issue -- [ ] I have included all the information requested below +- [ ] I have searched existing issues to make sure this is not a duplicate +- [ ] I have cleared my browser cache and confirmed the issue persists +- [ ] I have checked this issue affects the latest version of the extension +- [ ] I have disabled other extensions to ensure this isn't a conflict issue +- [ ] I have included all the information requested below **Bug Description** @@ -30,11 +29,11 @@ assignees: '' **Expected Behavior** - + **Current Behavior** - + **Screenshots** @@ -42,9 +41,9 @@ assignees: '' **Debug Information** -- UTRP Extension Version: [e.g. 1.2.0] -- Browser Info: [e.g. Chrome 120.0.0] -- OS: [e.g. Windows 11, macOS Sonoma] +- UTRP Extension Version: [e.g. 1.2.0] +- Browser Info: [e.g. Chrome 120.0.0] +- OS: [e.g. Windows 11, macOS Sonoma] **Console Logs** diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 66e298f7..d87cca8e 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,34 +1,29 @@ --- name: Feature Request about: Suggest an idea for this project -title: "[FEATURE] " +title: '[FEATURE] ' labels: feature assignees: '' - --- **Pre-submission Checklist** -- [ ] I have searched existing issues to ensure this feature hasn't been requested -- [ ] I have searched closed issues to check if this was previously rejected/implemented -- [ ] I have checked the project roadmap (if available) for planned similar features -- [ ] I have reviewed the documentation to confirm this feature doesn't exist -- [ ] I have completed all sections below with detailed information +- [ ] I have searched existing issues to ensure this feature hasn't been requested +- [ ] I have searched closed issues to check if this was previously rejected/implemented +- [ ] I have checked the project roadmap (if available) for planned similar features +- [ ] I have reviewed the documentation to confirm this feature doesn't exist +- [ ] I have completed all sections below with detailed information -**Feature Description** +**Your Idea** - - -**Proposed Solution** - - + **UI/UX Considerations** - + -**Technical Implementation Details** +**Other** - + diff --git a/.github/ISSUE_TEMPLATE/updating-build-dependencies.md b/.github/ISSUE_TEMPLATE/updating-build-dependencies.md index 9aedf541..7603a7f9 100644 --- a/.github/ISSUE_TEMPLATE/updating-build-dependencies.md +++ b/.github/ISSUE_TEMPLATE/updating-build-dependencies.md @@ -4,7 +4,6 @@ about: Updating Build Dependencies title: '' labels: build, dependencies assignees: doprz, Razboy20 - --- - [ ] Updated Nix Flake diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d93fb431..ad62cbda 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,21 +1,20 @@ version: 2 updates: - - package-ecosystem: 'npm' - directory: '/' - schedule: - interval: 'weekly' - day: 'monday' - time: '09:00' - timezone: 'America/Chicago' - groups: - minor-and-patch-updates: - update-types: - - 'minor' - - 'patch' - major-updates: - update-types: - - 'major' - - ignore: - - dependency-name: '@crxjs/vite-plugin' - - dependency-name: '@unocss/vite' + - package-ecosystem: 'npm' + directory: '/' + schedule: + interval: 'weekly' + day: 'monday' + time: '09:00' + timezone: 'America/Chicago' + groups: + minor-and-patch-updates: + update-types: + - 'minor' + - 'patch' + major-updates: + update-types: + - 'major' + ignore: + - dependency-name: '@crxjs/vite-plugin' + - dependency-name: '@unocss/vite' diff --git a/.github/workflows/best-practices.yml b/.github/workflows/best-practices.yml index 1f8fd579..9094f284 100644 --- a/.github/workflows/best-practices.yml +++ b/.github/workflows/best-practices.yml @@ -1,43 +1,33 @@ name: Best Practices - on: [push, pull_request] - jobs: - lint: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 - - - name: Install dependencies - run: pnpm install - - - name: Run ESLint - run: pnpm run lint - format: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 - - - name: Install dependencies - run: pnpm install - - - name: Run Prettier - run: pnpm run prettier + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + - name: Install dependencies + run: pnpm install + - name: Run ESLint + run: pnpm run lint + format: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + - name: Install dependencies + run: pnpm install + - name: Run Prettier + run: pnpm run prettier diff --git a/.github/workflows/check-types.yml b/.github/workflows/check-types.yml index 0ffd77da..b5fe537f 100644 --- a/.github/workflows/check-types.yml +++ b/.github/workflows/check-types.yml @@ -1,24 +1,18 @@ name: Type Check - on: [push, pull_request] - jobs: - type-check: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 - - - name: Install dependencies - run: pnpm install - - - name: Run tests - run: pnpm run check-types + type-check: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + - name: Install dependencies + run: pnpm install + - name: Run tests + run: pnpm run check-types diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index a0fb4dfe..3f7dba63 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -1,26 +1,22 @@ name: 'Chromatic' - on: [push, pull_request] - jobs: - chromatic: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 - - - name: Install dependencies - run: pnpm install - - - name: Publish to Chromatic - uses: chromaui/action@latest - with: - projectToken: chpt_e8bd07b0b27d8eb - exitZeroOnChanges: true - autoAcceptChanges: 'main' + chromatic: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + - name: Install dependencies + run: pnpm install + - name: Publish to Chromatic + uses: chromaui/action@latest + with: + projectToken: chpt_e8bd07b0b27d8eb + exitZeroOnChanges: true + autoAcceptChanges: 'main' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 71535534..db8ba73f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,25 +1,24 @@ name: Create Release on: - push: - branches: - - production - - preview + push: + branches: + - production + - preview jobs: - build: - name: build extension & create release - runs-on: ubuntu-latest - concurrency: - group: ${{ github.ref }} - cancel-in-progress: true - steps: - - uses: actions/checkout@master - - name: Get file permission - run: chmod -R 777 . - - - name: Install dependencies - run: npm ci - - name: Release with semantic-release - id: semantic-release - run: npx --no-install semantic-release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + build: + name: build extension & create release + runs-on: ubuntu-latest + concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + steps: + - uses: actions/checkout@master + - name: Get file permission + run: chmod -R 777 . + - name: Install dependencies + run: npm ci + - name: Release with semantic-release + id: semantic-release + run: npx --no-install semantic-release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1e6227dc..4ddde8a5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,24 +1,18 @@ name: Tests - on: [push, pull_request] - jobs: - test: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 - - - name: Install dependencies - run: pnpm install - - - name: Run tests - run: pnpm test + test: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + - name: Install dependencies + run: pnpm install + - name: Run tests + run: pnpm test diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml index 8e612c71..95db3d3b 100644 --- a/.github/workflows/validate-pr.yml +++ b/.github/workflows/validate-pr.yml @@ -1,43 +1,40 @@ name: Validate PR Title - # thank you ben limmer for this workflow: # https://github.com/blimmer/semantic-release-demo-2/blob/main/.github/workflows/lint-pr.yml - on: - pull_request_target: - types: - - opened - - reopened - - edited - - synchronize - + pull_request_target: + types: + - opened + - reopened + - edited + - synchronize jobs: - main: - runs-on: ubuntu-latest - steps: - - uses: amannn/action-semantic-pull-request@v3.2.6 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Post Conventional Commit Comment (on failure) - uses: jungwinter/comment@v1 - id: conventional-commit-help - with: - type: create - issue_number: ${{ github.event.pull_request.number }} - token: ${{ secrets.GITHUB_TOKEN }} - body: | - Your pull request title did not conform to [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) standards. Our upcoming automated release pipeline will automatically determine - the proper release version based on your pull request title. - **Cheat Sheet** - - feat: A new feature - - fix: A bug fix - - docs: Documentation only changes - - style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) - - refactor: A code change that neither fixes a bug nor adds a feature - - perf: A code change that improves performance - - test: Adding missing tests or correcting existing tests - - build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm) - - ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs) - - chore: Other changes that don't modify src or test files - - revert: Reverts a previous commit - if: ${{ failure() }} + main: + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v3.2.6 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Post Conventional Commit Comment (on failure) + uses: jungwinter/comment@v1 + id: conventional-commit-help + with: + type: create + issue_number: ${{ github.event.pull_request.number }} + token: ${{ secrets.GITHUB_TOKEN }} + body: | + Your pull request title did not conform to [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) standards. Our upcoming automated release pipeline will automatically determine + the proper release version based on your pull request title. + **Cheat Sheet** + - feat: A new feature + - fix: A bug fix + - docs: Documentation only changes + - style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) + - refactor: A code change that neither fixes a bug nor adds a feature + - perf: A code change that improves performance + - test: Adding missing tests or correcting existing tests + - build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm) + - ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs) + - chore: Other changes that don't modify src or test files + - revert: Reverts a previous commit + if: ${{ failure() }} diff --git a/.gitignore b/.gitignore index 770da4e8..8a86a447 100644 --- a/.gitignore +++ b/.gitignore @@ -212,4 +212,9 @@ package-lock.json storybook-static/ package/ -.direnv/ +# Nix +result +result-* + +# direnv +.direnv diff --git a/.vscode/launch.json b/.vscode/launch.json index 3a274ea0..fe650bc6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,13 +6,9 @@ "request": "launch", "name": "Run current script", "runtimeExecutable": "npx", - "runtimeArgs": [ - "tsx" - ], + "runtimeArgs": ["tsx"], "program": "${file}", - "skipFiles": [ - "/**" - ], + "skipFiles": ["/**"] } ] -} \ No newline at end of file +} diff --git a/.vscode/settings.json b/.vscode/settings.json index a89cbd32..9685063e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -26,7 +26,7 @@ "navigation": "Routes", "logging": "log", "popup": "Layout", - "storage": "Database", + "storage": "Database" }, "material-icon-theme.files.associations": { "tsconfig.extension.json": "tsconfig", @@ -36,5 +36,5 @@ "[html]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, - "typescript.tsdk": "node_modules/typescript/lib", + "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/CHANGELOG.md b/CHANGELOG.md index a12cb885..a7d7b5ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +## [2.3.0](https://github.com/Longhorn-Developers/UT-Registration-Plus/compare/v2.2.2...v2.3.0) (2026-01-07) + +### Features + +* add drag-and-drop import for schedules ([#661](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/661)) ([549c52a](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/549c52a39fee718f2bb07cfce33a294835a2246b)), closes [#446](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/446) +* allow bypassing the 10-schedule limit ([#675](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/675)) ([6a67a32](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/6a67a32e4f50a5bdd20aa43789f199b822483e2d)) +* condense resourceLinks course schedule ([#676](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/676)) ([cee5f02](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/cee5f0284f09f39ca5ae64559d0b697646c77e74)) +* LHD birthday ([#717](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/717)) ([2d18553](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2d18553f98c5146fa18699ae20462e7dcbc9d35c)) +* **nix:** add prettier-version-match check ([#713](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/713)) ([8ccf7fb](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8ccf7fb37e769ba445f39c140ca9c1c4245cc1c1)) +* **nix:** build UTRP ([#714](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/714)) ([38bb29b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/38bb29b20b97ed3cf8fd6511df16553fed1d58bb)) + +### Bug Fixes + +* .editorconfig syntax for nix files ([b406d4d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b406d4dd244a25688c2b9621cf5d441228bd8913)) +* toSorted outdated chrome bug ([#694](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/694)) ([4f5d8c6](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/4f5d8c6d20e3cfeb7b62520ba1819e297d2cc60f)) ## [2.2.2](https://github.com/Longhorn-Developers/UT-Registration-Plus/compare/v2.2.1...v2.2.2) (2025-10-13) ### Features @@ -6,6 +21,7 @@ * automatically select new or duplicated schedules ([#583](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/583)) ([#589](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/589)) ([2a50f55](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2a50f5580d3dbeb0d66546c23cf29bbb37d80da2)) * **env:** add SENTRY env vars ([8f7e1bc](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8f7e1bc0af6336549068e02b80df21d4e8f4ef9c)) * export schedule button add to calendar ([#594](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/594)) ([5994ded](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/5994ded8be876cb55174d27d3fdb0832b21a0ff9)) +* **release:** v2.2.2 ([c21cbd7](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/c21cbd77f0764c03a711589ff4f957cb8c936eec)) * search result shading ([#617](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/617)) ([be861b8](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/be861b823cb2cb7f6f4a1f266351eec3fc1c2f99)) * show warning for courses of different semesters ([#570](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/570)) ([2e7dac1](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2e7dac1e3eba757231ac07ac966231c08c703a16)) * support summer grades, fix summer course parser ([#596](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/596)) ([2d92dd4](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2d92dd47f00a44b7d48e92a8ffba94480e4e73f9)) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..5fd56688 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,32 @@ +# UT Registration Plus Code of Conduct + +Like the technical community as a whole, the UT Registration Plus team and community is made up of a mixture of professionals and volunteers from all over the world, working on every aspect of the mission - including mentorship, teaching, and connecting people. + +Diversity is one of our huge strengths, but it can also lead to communication issues and unhappiness. To that end, we have a few ground rules that we ask people to adhere to. This code applies equally to founders, mentors and those seeking help and guidance. + +This isn’t an exhaustive list of things that you can’t do. Rather, take it in the spirit in which it’s intended - a guide to make it easier to enrich all of us and the technical communities in which we participate. + +This code of conduct applies to all spaces managed by the UT Registration Plus project or Longhorn Developers. This includes IRC, the mailing lists, the issue tracker, DSF events, and any other forums created by the project team which the community uses for communication. In addition, violations of this code outside these spaces may affect a person's ability to participate within them. + +If you believe someone is violating the code of conduct, we ask that you report it by emailing [contact@longhorns.dev](mailto:contact@longhorns.dev). For more details please see our + +- **Be friendly and patient.** +- **Be welcoming.** We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability. +- **Be considerate.** Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we're a world-wide community, so you might not be communicating in someone else's primary language. +- **Be respectful.** Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It’s important to remember that a community where people feel uncomfortable or threatened is not a productive one. Members of the UT Registration Plus community should be respectful when dealing with other members as well as with people outside the UT Registration Plus community. +- **Be careful in the words that you choose.** We are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren't acceptable. This includes, but is not limited to: +- Violent threats or language directed against another person. +- Discriminatory jokes and language. +- Posting sexually explicit or violent material. +- Posting (or threatening to post) other people's personally identifying information ("doxing"). +- Personal insults, especially those using racist or sexist terms. +- Unwelcome sexual attention. +- Advocating for, or encouraging, any of the above behavior. +- Repeated harassment of others. In general, if someone asks you to stop, then stop. +- **When we disagree, try to understand why.** Disagreements, both social and technical, happen all the time and UT Registration Plus is no exception. It is important that we resolve disagreements and differing views constructively. Remember that we’re different. The strength of UT Registration Plus comes from its varied community, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to err and blaming each other doesn’t get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes. + +Original text courtesy of the [Speak Up! project](http://web.archive.org/web/20141109123859/http://speakup.io/coc.html). + +## Questions? + +If you have questions, please see . If that doesn't answer your questions, feel free to [contact us](mailto:contact@longhorns.dev). diff --git a/DOCKER_DEV_SETUP.md b/DOCKER_DEV_SETUP.md index c879b938..c80858b2 100644 --- a/DOCKER_DEV_SETUP.md +++ b/DOCKER_DEV_SETUP.md @@ -2,8 +2,8 @@ ## Prerequisites -- Docker installed on your machine -- Git (to clone the repository) +- Docker installed on your machine +- Git (to clone the repository) ## Getting Started @@ -70,9 +70,9 @@ docker run -it --rm -v $(pwd)/dist:/extension/dist -p 5173:5173 -e BUILD_MODE=de ## Accessing the Built Extension -- For build mode, the extension files will be in the `dist` directory on your host machine. -- For zip mode, the zipped extension will be in the `package` directory on your host machine. -- For dev mode, the extension will be continuously built in the `dist` directory, and you can load it as an unpacked extension in Chrome. +- For build mode, the extension files will be in the `dist` directory on your host machine. +- For zip mode, the zipped extension will be in the `package` directory on your host machine. +- For dev mode, the extension will be continuously built in the `dist` directory, and you can load it as an unpacked extension in Chrome. ## Docker Development Workflow @@ -85,5 +85,5 @@ When working on the extension: ## Troubleshooting -- If you encounter permission issues with the output directories, ensure that the directories exist on your host machine and have the correct permissions. -- For any other issues, please check the Docker logs or open an issue in the GitHub repository. +- If you encounter permission issues with the output directories, ensure that the directories exist on your host machine and have the correct permissions. +- For any other issues, please check the Docker logs or open an issue in the GitHub repository. diff --git a/LICENSE.md b/LICENSE.md index e6a57fae..e1cff0a2 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/README.md b/README.md index 792e1cca..8949de8d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ![Downloads](https://img.shields.io/chrome-web-store/d/hboadpjkoaieogjimneceaahlppnipaa) ![License](https://img.shields.io/github/license/Longhorn-Developers/UT-Registration-Plus) -**UT Registration Plus (UTRP)** streamlines the process of registering for classes at UT Austin by reducing the chaos of juggling multiple tabs like Rate My Professor, Google Sheets, and the UT Course Schedule. With UTRP, you can simplify class selection and schedule management. We've all been there. 20 tabs of Rate My Professor, Google Spreadsheet, and the UT Course Schedule open and you still don't know what classes to take. +**UT Registration Plus (UTRP)** streamlines the process of registering for classes at UT Austin by reducing the chaos of juggling multiple tabs like Rate My Professor, Google Sheets, and the UT Course Schedule. With UTRP, you can simplify class selection and schedule management. We've all been there. 20 tabs of Rate My Professor, Google Spreadsheet, and the UT Course Schedule open and you still don't know what classes to take. ## Demo @@ -14,37 +14,37 @@ ## Features -- **Quick Access to Class Info**: For each class in the UT Course Schedule, UTRP provides a "breakdown" popup with direct links to RateMyProfessor, Course Evaluation Surveys (CES), and past syllabi. -- **Prerequisite & Restriction Highlights**: Displays course descriptions with highlighted details on prerequisites, restrictions, and other important info. -- **Grade Distribution Graphs**: View an aggregate and semester-specific graph of grade distributions for each course. -- **Saved Courses List**: Easily add courses to a list and view them in the extension popup. Copy unique numbers with a single click. -- **Conflict Detection**: Automatically highlights and strikes out courses that conflict with your saved courses in the UT Course Catalog. -- **Weekly Schedule View**: Displays your saved courses in a weekly schedule format for easier planning. -- **Multiple Schedule Support**: Create multiple schedules to plan for different registration scenarios. -- **And much more!** +- **Quick Access to Class Info**: For each class in the UT Course Schedule, UTRP provides a "breakdown" popup with direct links to RateMyProfessor, Course Evaluation Surveys (CES), and past syllabi. +- **Prerequisite & Restriction Highlights**: Displays course descriptions with highlighted details on prerequisites, restrictions, and other important info. +- **Grade Distribution Graphs**: View an aggregate and semester-specific graph of grade distributions for each course. +- **Saved Courses List**: Easily add courses to a list and view them in the extension popup. Copy unique numbers with a single click. +- **Conflict Detection**: Automatically highlights and strikes out courses that conflict with your saved courses in the UT Course Catalog. +- **Weekly Schedule View**: Displays your saved courses in a weekly schedule format for easier planning. +- **Multiple Schedule Support**: Create multiple schedules to plan for different registration scenarios. +- **And much more!** ## Toolchain -- React v20.9.0 (LTS) -- TypeScript v5.x -- Vite v5.x -- pnpm v10.x -- UnoCSS -- ESLint -- Prettier -- Storybook -- Figma -- Semantic-Release -- Custom Messaging & Storage Wrappers +- React v20.9.0 (LTS) +- TypeScript v5.x +- Vite v5.x +- pnpm v10.x +- UnoCSS +- ESLint +- Prettier +- Storybook +- Figma +- Semantic-Release +- Custom Messaging & Storage Wrappers ## VSCode Extensions We recommend using the following VSCode extensions to improve your development experience: -- **[ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)**: For identifying and fixing linting issues. -- **[Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)**: For automatic code formatting. -- **[Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)**: For Tailwind CSS class autocomplete and IntelliSense. -- **[UnoCSS](https://marketplace.visualstudio.com/items?itemName=antfu.unocss)**: For better support with UnoCSS utilities. +- **[ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)**: For identifying and fixing linting issues. +- **[Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)**: For automatic code formatting. +- **[Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)**: For Tailwind CSS class autocomplete and IntelliSense. +- **[UnoCSS](https://marketplace.visualstudio.com/items?itemName=antfu.unocss)**: For better support with UnoCSS utilities. ## Contributing @@ -79,7 +79,7 @@ Once set up, the extension can be built to the `dist/` directory using the follo ### Development Builds -- Run the development server: +- Run the development server: ```bash pnpm dev ``` @@ -92,7 +92,7 @@ Once set up, the extension can be built to the `dist/` directory using the follo ### Production Builds -- To generate production builds: +- To generate production builds: ```bash pnpm build ``` @@ -121,10 +121,10 @@ To load the extension manually in Chrome: If you encounter bugs or issues, please report them in the [Issues](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues) section, including: -- A clear, descriptive title -- Steps to reproduce the issue -- Expected behavior -- Screenshots or logs (if applicable) +- A clear, descriptive title +- Steps to reproduce the issue +- Expected behavior +- Screenshots or logs (if applicable) We aim to address issues promptly. @@ -142,14 +142,14 @@ Follow this structure for commit messages: Where: -- **type**: One of the following: - - `feat`: A new feature - - `fix`: A bug fix - - `docs`: Documentation updates - - `style`: Code formatting changes (whitespace, semicolons, etc.) - - `refactor`: Code restructuring (without adding features or fixing bugs) - - `test`: Adding or modifying tests - - `chore`: Maintenance tasks or build process changes +- **type**: One of the following: + - `feat`: A new feature + - `fix`: A bug fix + - `docs`: Documentation updates + - `style`: Code formatting changes (whitespace, semicolons, etc.) + - `refactor`: Code restructuring (without adding features or fixing bugs) + - `test`: Adding or modifying tests + - `chore`: Maintenance tasks or build process changes Example: @@ -168,9 +168,9 @@ Branch names should follow the format: Examples: -- `feat/user-login` -- `fix/navbar-layout` -- `docs/update-readme` +- `feat/user-login` +- `fix/navbar-layout` +- `docs/update-readme` ## License @@ -184,28 +184,28 @@ We maintain a strict code of conduct. By contributing, you agree to adhere to th Special thanks to the developers and contributors behind these amazing tools and libraries: -- React v20.9.0 (LTS) -- TypeScript v5.x -- Vite v5.x -- pnpm v10.x -- UnoCSS -- ESLint -- Prettier -- Storybook -- Figma -- Semantic-Release -- [chrome-extension-toolkit](https://github.com/sghsri/chrome-extension-toolkit) -- [UT_Grade_Parser](https://github.com/doprz/UT_Grade_Parser) -- [eslint-plugin-import-essentials](https://github.com/doprz/eslint-plugin-import-essentials) -- [UT Registration Plus v1.2.2.7](https://github.com/Longhorn-Developers/UT-Registration-Plus/tree/legacy) by @sghsri -- [UT Registration Planner](https://github.com/doprz/UT-Registration-Planner) by @doprz -- [Figma Designs](https://www.figma.com/design/8tsCay2FRqctrdcZ3r9Ahw/UTRP) by @IsaDavRod -- [Longhorn Developers](https://github.com/Longhorn-Developers) - established in 2024 -- The UTRP devs, beta testers, and all supporters of the project! +- React v20.9.0 (LTS) +- TypeScript v5.x +- Vite v5.x +- pnpm v10.x +- UnoCSS +- ESLint +- Prettier +- Storybook +- Figma +- Semantic-Release +- [chrome-extension-toolkit](https://github.com/sghsri/chrome-extension-toolkit) +- [UT_Grade_Parser](https://github.com/doprz/UT_Grade_Parser) +- [eslint-plugin-import-essentials](https://github.com/doprz/eslint-plugin-import-essentials) +- [UT Registration Plus v1.2.2.7](https://github.com/Longhorn-Developers/UT-Registration-Plus/tree/legacy) by @sghsri +- [UT Registration Planner](https://github.com/doprz/UT-Registration-Planner) by @doprz +- [Figma Designs](https://www.figma.com/design/8tsCay2FRqctrdcZ3r9Ahw/UTRP) by @IsaDavRod +- [Longhorn Developers](https://github.com/Longhorn-Developers) - established in 2024 +- The UTRP devs, beta testers, and all supporters of the project! ## Activity -![UT-Registration-Plus Activity](https://repobeats.axiom.co/api/embed/47930fa3916ac1b475cd63a05948c449eb5ad502.svg "UT-Registration-Plus Repobeats analytics image") +![UT-Registration-Plus Activity](https://repobeats.axiom.co/api/embed/47930fa3916ac1b475cd63a05948c449eb5ad502.svg 'UT-Registration-Plus Repobeats analytics image') ## Star History diff --git a/chromatic.config.json b/chromatic.config.json index aeb5a24c..4b32d86e 100644 --- a/chromatic.config.json +++ b/chromatic.config.json @@ -1,5 +1,5 @@ { - "onlyChanged": true, - "projectId": "Project:65c5172964f36dcf207985bf", - "zip": true + "onlyChanged": true, + "projectId": "Project:65c5172964f36dcf207985bf", + "zip": true } diff --git a/default.nix b/default.nix new file mode 100644 index 00000000..b1565619 --- /dev/null +++ b/default.nix @@ -0,0 +1,10 @@ +(import ( + let + rev = "v1.1.0"; + sha256 = "sha256:19d2z6xsvpxm184m41qrpi1bplilwipgnzv9jy17fgw421785q1m"; + in + fetchTarball { + inherit sha256; + url = "https://github.com/NixOS/flake-compat/archive/${rev}.tar.gz"; + } +) { src = ./.; }).defaultNix diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 5d937781..42ffb086 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -24,7 +24,7 @@ else fi # Validate the mode -if [[ ! " ${SUPPORTED_MODES[*]} " =~ " ${mode} " ]]; then +if [[ ! " ${SUPPORTED_MODES[*]} " =~ ${mode} ]]; then echo "Error: Invalid mode '${mode}'" >&2 usage fi diff --git a/flake.lock b/flake.lock index 31fe6379..af19e613 100644 --- a/flake.lock +++ b/flake.lock @@ -1,30 +1,30 @@ { "nodes": { - "flake-utils": { + "flake-parts": { "inputs": { - "systems": "systems" + "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "lastModified": 1767609335, + "narHash": "sha256-feveD98mQpptwrAEggBQKJTYbvwwglSbOv53uCfH9PY=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "250481aafeb741edfe23d29195671c19b36b6dca", "type": "github" }, "original": { - "owner": "numtide", - "repo": "flake-utils", + "owner": "hercules-ci", + "repo": "flake-parts", "type": "github" } }, "nixpkgs": { "locked": { - "lastModified": 1759831965, - "narHash": "sha256-vgPm2xjOmKdZ0xKA6yLXPJpjOtQPHfaZDRtH+47XEBo=", + "lastModified": 1767640445, + "narHash": "sha256-UWYqmD7JFBEDBHWYcqE6s6c77pWdcU/i+bwD6XxMb8A=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "c9b6fb798541223bbb396d287d16f43520250518", + "rev": "9f0c42f8bc7151b8e7e5840fb3bd454ad850d8c5", "type": "github" }, "original": { @@ -34,24 +34,59 @@ "type": "github" } }, - "root": { - "inputs": { - "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs" - } - }, - "systems": { + "nixpkgs-lib": { "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "lastModified": 1765674936, + "narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85", "type": "github" }, "original": { - "owner": "nix-systems", - "repo": "default", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1761236834, + "narHash": "sha256-+pthv6hrL5VLW2UqPdISGuLiUZ6SnAXdd2DdUE+fV2Q=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "d5faa84122bc0a1fd5d378492efce4e289f8eac1", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs", + "treefmt-nix": "treefmt-nix" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1767468822, + "narHash": "sha256-MpffQxHxmjVKMiQd0Tg2IM/bSjjdQAM+NDcX6yxj7rE=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "d56486eb9493ad9c4777c65932618e9c2d0468fc", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", "type": "github" } } diff --git a/flake.nix b/flake.nix index 6dbdba38..3b8a6360 100644 --- a/flake.nix +++ b/flake.nix @@ -1,43 +1,33 @@ { inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - flake-utils.url = "github:numtide/flake-utils"; + flake-parts.url = "github:hercules-ci/flake-parts"; + treefmt-nix.url = "github:numtide/treefmt-nix"; }; outputs = - { - self, - nixpkgs, - flake-utils, - }: - flake-utils.lib.eachDefaultSystem ( - system: - let - pkgs = (import nixpkgs { inherit system; }); + inputs@{ flake-parts, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + systems = inputs.nixpkgs.lib.systems.flakeExposed; - commonPackages = with pkgs; [ - nodejs_20 # v20.19.5 - pnpm_10 # v10.18.0 - ]; + imports = [ + ./nix/packages.nix + ./nix/devShells.nix + ./nix/treefmt.nix + ]; - additionalPackages = with pkgs; [ - bun - nodePackages.conventional-changelog-cli - sentry-cli - ]; - in - { - formatter = pkgs.nixfmt-rfc-style; - - devShells.default = pkgs.mkShell { - name = "utrp-dev"; - buildInputs = commonPackages; + perSystem = + { system, ... }: + { + _module.args.pkgs = import inputs.nixpkgs { + inherit system; + overlays = [ + (final: prev: { + nodejs = prev.nodejs_20; # v20.19.5 + }) + ]; + config = { }; + }; }; - - devShells.full = pkgs.mkShell { - name = "utrp-dev-full"; - buildInputs = commonPackages ++ additionalPackages; - }; - } - ); + }; } diff --git a/nix/devShells.nix b/nix/devShells.nix new file mode 100644 index 00000000..e24b0475 --- /dev/null +++ b/nix/devShells.nix @@ -0,0 +1,30 @@ +{ + perSystem = + { + pkgs, + ... + }: + let + commonPackages = with pkgs; [ + nodejs # Defined in overlay + pnpm_10 # v10.18.2 + ]; + + additionalPackages = with pkgs; [ + bun + nodePackages.conventional-changelog-cli + sentry-cli + ]; + in + { + devShells.default = pkgs.mkShell { + name = "utrp-dev"; + packages = commonPackages; + }; + + devShells.full = pkgs.mkShell { + name = "utrp-dev-full"; + packages = commonPackages ++ additionalPackages; + }; + }; +} diff --git a/nix/package.nix b/nix/package.nix new file mode 100644 index 00000000..9e896d46 --- /dev/null +++ b/nix/package.nix @@ -0,0 +1,51 @@ +{ + stdenv, + lib, + nodejs, + pnpm_10, + git, + version ? "dev", + gitRev ? "unknown", + gitBranch ? "unknown", + buildScript ? "build", +}: + +stdenv.mkDerivation (finalAttrs: { + inherit version; + pname = "ut-registration-plus"; + + src = ../.; + + nativeBuildInputs = [ + nodejs + pnpm_10.configHook + git + ]; + + pnpmDeps = pnpm_10.fetchDeps { + inherit (finalAttrs) pname version src; + fetcherVersion = 2; + hash = "sha256-UqHymJWvlTV4glra/6DkxuCxbG5dpPkFcnvq3vuxsJ8="; + }; + + # Pass git info to the build + VITE_GIT_COMMIT = gitRev; + VITE_GIT_BRANCH = gitBranch; + + buildPhase = '' + pnpm run ${buildScript} + ''; + + installPhase = '' + mkdir -p $out + cp -r dist/* $out/ + ''; + + meta = { + description = "UT Registration Plus"; + homepage = "https://github.com/Longhorn-Developers/UT-Registration-Plus"; + license = lib.licenses.mit; + maintainers = lib.maintainers.doprz; + platforms = lib.platforms.unix; + }; +}) diff --git a/nix/packages.nix b/nix/packages.nix new file mode 100644 index 00000000..bc68bb65 --- /dev/null +++ b/nix/packages.nix @@ -0,0 +1,40 @@ +{ inputs, ... }: +{ + perSystem = + { pkgs, ... }: + let + packageJson = builtins.fromJSON (builtins.readFile ../package.json); + gitRev = inputs.self.shortRev or inputs.self.dirtyShortRev or "dev"; + gitBranch = if inputs.self ? ref then inputs.self.ref else "unknown"; + baseVersion = packageJson.version; + + commonArgs = { + inherit gitRev gitBranch; + }; + + # Prod variant + ut-registration-plus = pkgs.callPackage ./package.nix ( + commonArgs + // { + version = "${baseVersion}+git.${gitRev}"; + buildScript = "build"; + } + ); + + # Dev variant + ut-registration-plus-dev = pkgs.callPackage ./package.nix ( + commonArgs + // { + version = "${baseVersion}-dev+git.${gitRev}"; + buildScript = "build:dev"; + } + ); + in + { + packages = { + inherit ut-registration-plus ut-registration-plus-dev; + default = ut-registration-plus; + dev = ut-registration-plus-dev; + }; + }; +} diff --git a/nix/treefmt.nix b/nix/treefmt.nix new file mode 100644 index 00000000..7c5cbb89 --- /dev/null +++ b/nix/treefmt.nix @@ -0,0 +1,63 @@ +{ inputs, ... }: +{ + imports = [ + inputs.treefmt-nix.flakeModule + ]; + + perSystem = + { pkgs, ... }: + { + treefmt = { + projectRootFile = "flake.nix"; + programs.nixfmt.enable = pkgs.lib.meta.availableOn pkgs.stdenv.buildPlatform pkgs.nixfmt-rfc-style.compiler; + programs.nixfmt.package = pkgs.nixfmt-rfc-style; + + # NOTE: Make sure the prettier version in package.json and the one used by treefmt are the same for consistent results + programs.prettier.enable = true; + programs.shellcheck.enable = true; + programs.yamlfmt.enable = true; + programs.dockerfmt.enable = true; + + settings.formatter.prettier.excludes = [ "pnpm-lock.yaml" ]; + settings.formatter.shellcheck.excludes = [ ".envrc" ]; + settings.formatter.yamlfmt.excludes = [ "pnpm-lock.yaml" ]; + }; + + checks = { + prettier-version-match = + pkgs.runCommand "check-prettier-version" + { + buildInputs = [ pkgs.jq ]; + } + '' + # Extract prettier version from package.json + packageJsonVersion=$(jq -r '.devDependencies.prettier // empty' ${../package.json}) + + if [ -z "$packageJsonVersion" ]; then + echo "Error: prettier not found in package.json devDependencies" + exit 1 + fi + + # Remove any semver prefix characters (^, ~, etc...) + packageJsonVersion=$(echo "$packageJsonVersion" | sed 's/^[\^~>=<]*//') + + # Get prettier version from nixpkgs + nixVersion="${pkgs.nodePackages.prettier.version}" + + if [ "$packageJsonVersion" != "$nixVersion" ]; then + echo "" + echo "ERROR: Prettier version mismatch!" + echo " package.json: $packageJsonVersion" + echo " nixpkgs: $nixVersion" + echo "" + echo "Please update one of the following:" + echo " - Update prettier in package.json to match nixpkgs: $nixVersion" + echo " - Override prettier in your flake to match package.json" + exit 1 + fi + + touch $out + ''; + }; + }; +} diff --git a/package.json b/package.json index 7976d5bb..2a3548d9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ut-registration-plus", "displayName": "UT Registration Plus", - "version": "2.2.2", + "version": "2.3.0", "description": "UT Registration Plus is a Chrome extension that allows students to easily register for classes.", "private": true, "homepage": "https://github.com/Longhorn-Developers/UT-Registration-Plus", @@ -39,6 +39,9 @@ "@phosphor-icons/react": "^2.1.7", "@sentry/react": "^8.55.0", "@tanstack/react-query": "^5.69.0", + "@tsparticles/engine": "^3.9.1", + "@tsparticles/react": "^3.0.0", + "@tsparticles/slim": "^3.9.1", "@unocss/vite": "^0.63.6", "@vitejs/plugin-react": "^4.3.4", "chrome-extension-toolkit": "^0.0.54", @@ -136,7 +139,7 @@ "gulp-zip": "^6.1.0", "path": "^0.12.7", "postcss": "^8.5.3", - "prettier": "^3.5.2", + "prettier": "3.6.2", "react-dev-utils": "^12.0.1", "semantic-release": "^24.2.3", "storybook": "^8.6.0", @@ -159,7 +162,10 @@ }, "overrides": { "es-module-lexer": "^1.5.4" - } + }, + "onlyBuiltDependencies": [ + "@tsparticles/engine" + ] }, "volta": { "node": "20.19.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 012c01d9..15fd316a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -49,6 +49,15 @@ importers: '@tanstack/react-query': specifier: ^5.69.0 version: 5.69.0(react@18.3.1) + '@tsparticles/engine': + specifier: ^3.9.1 + version: 3.9.1 + '@tsparticles/react': + specifier: ^3.0.0 + version: 3.0.0(@tsparticles/engine@3.9.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tsparticles/slim': + specifier: ^3.9.1 + version: 3.9.1 '@unocss/vite': specifier: ^0.63.6 version: 0.63.6(patch_hash=9e2d2732a6e057a2ca90fba199730f252d8b4db8631b2c6ee0854fce7771bc95)(rollup@4.52.4)(typescript@5.7.3)(vite@5.4.20(@types/node@22.13.5)(sass@1.85.1)(terser@5.44.0)) @@ -154,25 +163,25 @@ importers: version: 8.55.0 '@storybook/addon-designs': specifier: ^8.2.0 - version: 8.2.0(@storybook/blocks@8.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.0(prettier@3.5.2)))(@storybook/components@8.6.0(storybook@8.6.0(prettier@3.5.2)))(@storybook/theming@8.6.0(storybook@8.6.0(prettier@3.5.2)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 8.2.0(@storybook/blocks@8.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.0(prettier@3.6.2)))(@storybook/components@8.6.0(storybook@8.6.0(prettier@3.6.2)))(@storybook/theming@8.6.0(storybook@8.6.0(prettier@3.6.2)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@storybook/addon-essentials': specifier: ^8.6.0 - version: 8.6.0(@types/react@18.3.18)(storybook@8.6.0(prettier@3.5.2)) + version: 8.6.0(@types/react@18.3.18)(storybook@8.6.0(prettier@3.6.2)) '@storybook/addon-links': specifier: ^8.6.0 - version: 8.6.0(react@18.3.1)(storybook@8.6.0(prettier@3.5.2)) + version: 8.6.0(react@18.3.1)(storybook@8.6.0(prettier@3.6.2)) '@storybook/blocks': specifier: ^8.6.0 - version: 8.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.0(prettier@3.5.2)) + version: 8.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.0(prettier@3.6.2)) '@storybook/react': specifier: ^8.6.0 - version: 8.6.0(@storybook/test@8.6.0(storybook@8.6.0(prettier@3.5.2)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.0(prettier@3.5.2))(typescript@5.7.3) + version: 8.6.0(@storybook/test@8.6.0(storybook@8.6.0(prettier@3.6.2)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.0(prettier@3.6.2))(typescript@5.7.3) '@storybook/react-vite': specifier: ^8.6.0 - version: 8.6.0(@storybook/test@8.6.0(storybook@8.6.0(prettier@3.5.2)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.52.4)(storybook@8.6.0(prettier@3.5.2))(typescript@5.7.3)(vite@5.4.20(@types/node@22.13.5)(sass@1.85.1)(terser@5.44.0)) + version: 8.6.0(@storybook/test@8.6.0(storybook@8.6.0(prettier@3.6.2)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.52.4)(storybook@8.6.0(prettier@3.6.2))(typescript@5.7.3)(vite@5.4.20(@types/node@22.13.5)(sass@1.85.1)(terser@5.44.0)) '@storybook/test': specifier: ^8.6.0 - version: 8.6.0(storybook@8.6.0(prettier@3.5.2)) + version: 8.6.0(storybook@8.6.0(prettier@3.6.2)) '@svgr/core': specifier: ^8.1.0 version: 8.1.0(typescript@5.7.3) @@ -298,7 +307,7 @@ importers: version: 50.6.3(eslint@8.57.1) eslint-plugin-prettier: specifier: ^5.2.3 - version: 5.2.3(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.5.2) + version: 5.2.3(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.6.2) eslint-plugin-react: specifier: ^7.37.4 version: 7.37.4(eslint@8.57.1) @@ -336,8 +345,8 @@ importers: specifier: ^8.5.3 version: 8.5.3 prettier: - specifier: ^3.5.2 - version: 3.5.2 + specifier: 3.6.2 + version: 3.6.2 react-dev-utils: specifier: ^12.0.1 version: 12.0.1(eslint@8.57.1)(typescript@5.7.3)(webpack@5.97.1(esbuild@0.25.10)) @@ -346,7 +355,7 @@ importers: version: 24.2.3(typescript@5.7.3) storybook: specifier: ^8.6.0 - version: 8.6.0(prettier@3.5.2) + version: 8.6.0(prettier@3.6.2) typescript: specifier: ^5.7.3 version: 5.7.3 @@ -2075,6 +2084,121 @@ packages: resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} + '@tsparticles/basic@3.9.1': + resolution: {integrity: sha512-ijr2dHMx0IQHqhKW3qA8tfwrR2XYbbWYdaJMQuBo2CkwBVIhZ76U+H20Y492j/NXpd1FUnt2aC0l4CEVGVGdeQ==} + + '@tsparticles/engine@3.9.1': + resolution: {integrity: sha512-DpdgAhWMZ3Eh2gyxik8FXS6BKZ8vyea+Eu5BC4epsahqTGY9V3JGGJcXC6lRJx6cPMAx1A0FaQAojPF3v6rkmQ==} + + '@tsparticles/interaction-external-attract@3.9.1': + resolution: {integrity: sha512-5AJGmhzM9o4AVFV24WH5vSqMBzOXEOzIdGLIr+QJf4fRh9ZK62snsusv/ozKgs2KteRYQx+L7c5V3TqcDy2upg==} + + '@tsparticles/interaction-external-bounce@3.9.1': + resolution: {integrity: sha512-bv05+h70UIHOTWeTsTI1AeAmX6R3s8nnY74Ea6p6AbQjERzPYIa0XY19nq/hA7+Nrg+EissP5zgoYYeSphr85A==} + + '@tsparticles/interaction-external-bubble@3.9.1': + resolution: {integrity: sha512-tbd8ox/1GPl+zr+KyHQVV1bW88GE7OM6i4zql801YIlCDrl9wgTDdDFGIy9X7/cwTvTrCePhrfvdkUamXIribQ==} + + '@tsparticles/interaction-external-connect@3.9.1': + resolution: {integrity: sha512-sq8YfUNsIORjXHzzW7/AJQtfi/qDqLnYG2qOSE1WOsog39MD30RzmiOloejOkfNeUdcGUcfsDgpUuL3UhzFUOA==} + + '@tsparticles/interaction-external-grab@3.9.1': + resolution: {integrity: sha512-QwXza+sMMWDaMiFxd8y2tJwUK6c+nNw554+/9+tEZeTTk2fCbB0IJ7p/TH6ZGWDL0vo2muK54Njv2fEey191ow==} + + '@tsparticles/interaction-external-pause@3.9.1': + resolution: {integrity: sha512-Gzv4/FeNir0U/tVM9zQCqV1k+IAgaFjDU3T30M1AeAsNGh/rCITV2wnT7TOGFkbcla27m4Yxa+Fuab8+8pzm+g==} + + '@tsparticles/interaction-external-push@3.9.1': + resolution: {integrity: sha512-GvnWF9Qy4YkZdx+WJL2iy9IcgLvzOIu3K7aLYJFsQPaxT8d9TF8WlpoMlWKnJID6H5q4JqQuMRKRyWH8aAKyQw==} + + '@tsparticles/interaction-external-remove@3.9.1': + resolution: {integrity: sha512-yPThm4UDWejDOWW5Qc8KnnS2EfSo5VFcJUQDWc1+Wcj17xe7vdSoiwwOORM0PmNBzdDpSKQrte/gUnoqaUMwOA==} + + '@tsparticles/interaction-external-repulse@3.9.1': + resolution: {integrity: sha512-/LBppXkrMdvLHlEKWC7IykFhzrz+9nebT2fwSSFXK4plEBxDlIwnkDxd3FbVOAbnBvx4+L8+fbrEx+RvC8diAw==} + + '@tsparticles/interaction-external-slow@3.9.1': + resolution: {integrity: sha512-1ZYIR/udBwA9MdSCfgADsbDXKSFS0FMWuPWz7bm79g3sUxcYkihn+/hDhc6GXvNNR46V1ocJjrj0u6pAynS1KQ==} + + '@tsparticles/interaction-particles-attract@3.9.1': + resolution: {integrity: sha512-CYYYowJuGwRLUixQcSU/48PTKM8fCUYThe0hXwQ+yRMLAn053VHzL7NNZzKqEIeEyt5oJoy9KcvubjKWbzMBLQ==} + + '@tsparticles/interaction-particles-collisions@3.9.1': + resolution: {integrity: sha512-ggGyjW/3v1yxvYW1IF1EMT15M6w31y5zfNNUPkqd/IXRNPYvm0Z0ayhp+FKmz70M5p0UxxPIQHTvAv9Jqnuj8w==} + + '@tsparticles/interaction-particles-links@3.9.1': + resolution: {integrity: sha512-MsLbMjy1vY5M5/hu/oa5OSRZAUz49H3+9EBMTIOThiX+a+vpl3sxc9AqNd9gMsPbM4WJlub8T6VBZdyvzez1Vg==} + + '@tsparticles/move-base@3.9.1': + resolution: {integrity: sha512-X4huBS27d8srpxwOxliWPUt+NtCwY+8q/cx1DvQxyqmTA8VFCGpcHNwtqiN+9JicgzOvSuaORVqUgwlsc7h4pQ==} + + '@tsparticles/move-parallax@3.9.1': + resolution: {integrity: sha512-whlOR0bVeyh6J/hvxf/QM3DqvNnITMiAQ0kro6saqSDItAVqg4pYxBfEsSOKq7EhjxNvfhhqR+pFMhp06zoCVA==} + + '@tsparticles/plugin-easing-quad@3.9.1': + resolution: {integrity: sha512-C2UJOca5MTDXKUTBXj30Kiqr5UyID+xrY/LxicVWWZPczQW2bBxbIbfq9ULvzGDwBTxE2rdvIB8YFKmDYO45qw==} + + '@tsparticles/plugin-hex-color@3.9.1': + resolution: {integrity: sha512-vZgZ12AjUicJvk7AX4K2eAmKEQX/D1VEjEPFhyjbgI7A65eX72M465vVKIgNA6QArLZ1DLs7Z787LOE6GOBWsg==} + + '@tsparticles/plugin-hsl-color@3.9.1': + resolution: {integrity: sha512-jJd1iGgRwX6eeNjc1zUXiJivaqC5UE+SC2A3/NtHwwoQrkfxGWmRHOsVyLnOBRcCPgBp/FpdDe6DIDjCMO715w==} + + '@tsparticles/plugin-rgb-color@3.9.1': + resolution: {integrity: sha512-SBxk7f1KBfXeTnnklbE2Hx4jBgh6I6HOtxb+Os1gTp0oaghZOkWcCD2dP4QbUu7fVNCMOcApPoMNC8RTFcy9wQ==} + + '@tsparticles/react@3.0.0': + resolution: {integrity: sha512-hjGEtTT1cwv6BcjL+GcVgH++KYs52bIuQGW3PWv7z3tMa8g0bd6RI/vWSLj7p//NZ3uTjEIeilYIUPBh7Jfq/Q==} + peerDependencies: + '@tsparticles/engine': ^3.0.2 + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@tsparticles/shape-circle@3.9.1': + resolution: {integrity: sha512-DqZFLjbuhVn99WJ+A9ajz9YON72RtCcvubzq6qfjFmtwAK7frvQeb6iDTp6Ze9FUipluxVZWVRG4vWTxi2B+/g==} + + '@tsparticles/shape-emoji@3.9.1': + resolution: {integrity: sha512-ifvY63usuT+hipgVHb8gelBHSeF6ryPnMxAAEC1RGHhhXfpSRWMtE6ybr+pSsYU52M3G9+TF84v91pSwNrb9ZQ==} + + '@tsparticles/shape-image@3.9.1': + resolution: {integrity: sha512-fCA5eme8VF3oX8yNVUA0l2SLDKuiZObkijb0z3Ky0qj1HUEVlAuEMhhNDNB9E2iELTrWEix9z7BFMePp2CC7AA==} + + '@tsparticles/shape-line@3.9.1': + resolution: {integrity: sha512-wT8NSp0N9HURyV05f371cHKcNTNqr0/cwUu6WhBzbshkYGy1KZUP9CpRIh5FCrBpTev34mEQfOXDycgfG0KiLQ==} + + '@tsparticles/shape-polygon@3.9.1': + resolution: {integrity: sha512-dA77PgZdoLwxnliH6XQM/zF0r4jhT01pw5y7XTeTqws++hg4rTLV9255k6R6eUqKq0FPSW1/WBsBIl7q/MmrqQ==} + + '@tsparticles/shape-square@3.9.1': + resolution: {integrity: sha512-DKGkDnRyZrAm7T2ipqNezJahSWs6xd9O5LQLe5vjrYm1qGwrFxJiQaAdlb00UNrexz1/SA7bEoIg4XKaFa7qhQ==} + + '@tsparticles/shape-star@3.9.1': + resolution: {integrity: sha512-kdMJpi8cdeb6vGrZVSxTG0JIjCwIenggqk0EYeKAwtOGZFBgL7eHhF2F6uu1oq8cJAbXPujEoabnLsz6mW8XaA==} + + '@tsparticles/slim@3.9.1': + resolution: {integrity: sha512-CL5cDmADU7sDjRli0So+hY61VMbdroqbArmR9Av+c1Fisa5ytr6QD7Jv62iwU2S6rvgicEe9OyRmSy5GIefwZw==} + + '@tsparticles/updater-color@3.9.1': + resolution: {integrity: sha512-XGWdscrgEMA8L5E7exsE0f8/2zHKIqnTrZymcyuFBw2DCB6BIV+5z6qaNStpxrhq3DbIxxhqqcybqeOo7+Alpg==} + + '@tsparticles/updater-life@3.9.1': + resolution: {integrity: sha512-Oi8aF2RIwMMsjssUkCB6t3PRpENHjdZf6cX92WNfAuqXtQphr3OMAkYFJFWkvyPFK22AVy3p/cFt6KE5zXxwAA==} + + '@tsparticles/updater-opacity@3.9.1': + resolution: {integrity: sha512-w778LQuRZJ+IoWzeRdrGykPYSSaTeWfBvLZ2XwYEkh/Ss961InOxZKIpcS6i5Kp/Zfw0fS1ZAuqeHwuj///Osw==} + + '@tsparticles/updater-out-modes@3.9.1': + resolution: {integrity: sha512-cKQEkAwbru+hhKF+GTsfbOvuBbx2DSB25CxOdhtW2wRvDBoCnngNdLw91rs+0Cex4tgEeibkebrIKFDDE6kELg==} + + '@tsparticles/updater-rotate@3.9.1': + resolution: {integrity: sha512-9BfKaGfp28JN82MF2qs6Ae/lJr9EColMfMTHqSKljblwbpVDHte4umuwKl3VjbRt87WD9MGtla66NTUYl+WxuQ==} + + '@tsparticles/updater-size@3.9.1': + resolution: {integrity: sha512-3NSVs0O2ApNKZXfd+y/zNhTXSFeG1Pw4peI8e6z/q5+XLbmue9oiEwoPy/tQLaark3oNj3JU7Q903ZijPyXSzw==} + + '@tsparticles/updater-stroke-color@3.9.1': + resolution: {integrity: sha512-3x14+C2is9pZYTg9T2TiA/aM1YMq4wLdYaZDcHm3qO30DZu5oeQq0rm/6w+QOGKYY1Z3Htg9rlSUZkhTHn7eDA==} + '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -5904,8 +6028,8 @@ packages: resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} engines: {node: '>=6.0.0'} - prettier@3.5.2: - resolution: {integrity: sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==} + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} engines: {node: '>=14'} hasBin: true @@ -8424,126 +8548,126 @@ snapshots: '@sindresorhus/merge-streams@4.0.0': {} - '@storybook/addon-actions@8.6.0(storybook@8.6.0(prettier@3.5.2))': + '@storybook/addon-actions@8.6.0(storybook@8.6.0(prettier@3.6.2))': dependencies: '@storybook/global': 5.0.0 '@types/uuid': 9.0.8 dequal: 2.0.3 polished: 4.3.1 - storybook: 8.6.0(prettier@3.5.2) + storybook: 8.6.0(prettier@3.6.2) uuid: 9.0.1 - '@storybook/addon-backgrounds@8.6.0(storybook@8.6.0(prettier@3.5.2))': + '@storybook/addon-backgrounds@8.6.0(storybook@8.6.0(prettier@3.6.2))': dependencies: '@storybook/global': 5.0.0 memoizerific: 1.11.3 - storybook: 8.6.0(prettier@3.5.2) + storybook: 8.6.0(prettier@3.6.2) ts-dedent: 2.2.0 - '@storybook/addon-controls@8.6.0(storybook@8.6.0(prettier@3.5.2))': + '@storybook/addon-controls@8.6.0(storybook@8.6.0(prettier@3.6.2))': dependencies: '@storybook/global': 5.0.0 dequal: 2.0.3 - storybook: 8.6.0(prettier@3.5.2) + storybook: 8.6.0(prettier@3.6.2) ts-dedent: 2.2.0 - '@storybook/addon-designs@8.2.0(@storybook/blocks@8.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.0(prettier@3.5.2)))(@storybook/components@8.6.0(storybook@8.6.0(prettier@3.5.2)))(@storybook/theming@8.6.0(storybook@8.6.0(prettier@3.5.2)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@storybook/addon-designs@8.2.0(@storybook/blocks@8.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.0(prettier@3.6.2)))(@storybook/components@8.6.0(storybook@8.6.0(prettier@3.6.2)))(@storybook/theming@8.6.0(storybook@8.6.0(prettier@3.6.2)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@figspec/react': 1.0.3(react@18.3.1) optionalDependencies: - '@storybook/blocks': 8.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.0(prettier@3.5.2)) - '@storybook/components': 8.6.0(storybook@8.6.0(prettier@3.5.2)) - '@storybook/theming': 8.6.0(storybook@8.6.0(prettier@3.5.2)) + '@storybook/blocks': 8.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.0(prettier@3.6.2)) + '@storybook/components': 8.6.0(storybook@8.6.0(prettier@3.6.2)) + '@storybook/theming': 8.6.0(storybook@8.6.0(prettier@3.6.2)) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@storybook/addon-docs@8.6.0(@types/react@18.3.18)(storybook@8.6.0(prettier@3.5.2))': + '@storybook/addon-docs@8.6.0(@types/react@18.3.18)(storybook@8.6.0(prettier@3.6.2))': dependencies: '@mdx-js/react': 3.1.0(@types/react@18.3.18)(react@18.3.1) - '@storybook/blocks': 8.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.0(prettier@3.5.2)) - '@storybook/csf-plugin': 8.6.0(storybook@8.6.0(prettier@3.5.2)) - '@storybook/react-dom-shim': 8.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.0(prettier@3.5.2)) + '@storybook/blocks': 8.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.0(prettier@3.6.2)) + '@storybook/csf-plugin': 8.6.0(storybook@8.6.0(prettier@3.6.2)) + '@storybook/react-dom-shim': 8.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.0(prettier@3.6.2)) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - storybook: 8.6.0(prettier@3.5.2) + storybook: 8.6.0(prettier@3.6.2) ts-dedent: 2.2.0 transitivePeerDependencies: - '@types/react' - '@storybook/addon-essentials@8.6.0(@types/react@18.3.18)(storybook@8.6.0(prettier@3.5.2))': + '@storybook/addon-essentials@8.6.0(@types/react@18.3.18)(storybook@8.6.0(prettier@3.6.2))': dependencies: - '@storybook/addon-actions': 8.6.0(storybook@8.6.0(prettier@3.5.2)) - '@storybook/addon-backgrounds': 8.6.0(storybook@8.6.0(prettier@3.5.2)) - '@storybook/addon-controls': 8.6.0(storybook@8.6.0(prettier@3.5.2)) - '@storybook/addon-docs': 8.6.0(@types/react@18.3.18)(storybook@8.6.0(prettier@3.5.2)) - '@storybook/addon-highlight': 8.6.0(storybook@8.6.0(prettier@3.5.2)) - '@storybook/addon-measure': 8.6.0(storybook@8.6.0(prettier@3.5.2)) - '@storybook/addon-outline': 8.6.0(storybook@8.6.0(prettier@3.5.2)) - '@storybook/addon-toolbars': 8.6.0(storybook@8.6.0(prettier@3.5.2)) - '@storybook/addon-viewport': 8.6.0(storybook@8.6.0(prettier@3.5.2)) - storybook: 8.6.0(prettier@3.5.2) + '@storybook/addon-actions': 8.6.0(storybook@8.6.0(prettier@3.6.2)) + '@storybook/addon-backgrounds': 8.6.0(storybook@8.6.0(prettier@3.6.2)) + '@storybook/addon-controls': 8.6.0(storybook@8.6.0(prettier@3.6.2)) + '@storybook/addon-docs': 8.6.0(@types/react@18.3.18)(storybook@8.6.0(prettier@3.6.2)) + '@storybook/addon-highlight': 8.6.0(storybook@8.6.0(prettier@3.6.2)) + '@storybook/addon-measure': 8.6.0(storybook@8.6.0(prettier@3.6.2)) + '@storybook/addon-outline': 8.6.0(storybook@8.6.0(prettier@3.6.2)) + '@storybook/addon-toolbars': 8.6.0(storybook@8.6.0(prettier@3.6.2)) + '@storybook/addon-viewport': 8.6.0(storybook@8.6.0(prettier@3.6.2)) + storybook: 8.6.0(prettier@3.6.2) ts-dedent: 2.2.0 transitivePeerDependencies: - '@types/react' - '@storybook/addon-highlight@8.6.0(storybook@8.6.0(prettier@3.5.2))': + '@storybook/addon-highlight@8.6.0(storybook@8.6.0(prettier@3.6.2))': dependencies: '@storybook/global': 5.0.0 - storybook: 8.6.0(prettier@3.5.2) + storybook: 8.6.0(prettier@3.6.2) - '@storybook/addon-links@8.6.0(react@18.3.1)(storybook@8.6.0(prettier@3.5.2))': + '@storybook/addon-links@8.6.0(react@18.3.1)(storybook@8.6.0(prettier@3.6.2))': dependencies: '@storybook/global': 5.0.0 - storybook: 8.6.0(prettier@3.5.2) + storybook: 8.6.0(prettier@3.6.2) ts-dedent: 2.2.0 optionalDependencies: react: 18.3.1 - '@storybook/addon-measure@8.6.0(storybook@8.6.0(prettier@3.5.2))': + '@storybook/addon-measure@8.6.0(storybook@8.6.0(prettier@3.6.2))': dependencies: '@storybook/global': 5.0.0 - storybook: 8.6.0(prettier@3.5.2) + storybook: 8.6.0(prettier@3.6.2) tiny-invariant: 1.3.3 - '@storybook/addon-outline@8.6.0(storybook@8.6.0(prettier@3.5.2))': + '@storybook/addon-outline@8.6.0(storybook@8.6.0(prettier@3.6.2))': dependencies: '@storybook/global': 5.0.0 - storybook: 8.6.0(prettier@3.5.2) + storybook: 8.6.0(prettier@3.6.2) ts-dedent: 2.2.0 - '@storybook/addon-toolbars@8.6.0(storybook@8.6.0(prettier@3.5.2))': + '@storybook/addon-toolbars@8.6.0(storybook@8.6.0(prettier@3.6.2))': dependencies: - storybook: 8.6.0(prettier@3.5.2) + storybook: 8.6.0(prettier@3.6.2) - '@storybook/addon-viewport@8.6.0(storybook@8.6.0(prettier@3.5.2))': + '@storybook/addon-viewport@8.6.0(storybook@8.6.0(prettier@3.6.2))': dependencies: memoizerific: 1.11.3 - storybook: 8.6.0(prettier@3.5.2) + storybook: 8.6.0(prettier@3.6.2) - '@storybook/blocks@8.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.0(prettier@3.5.2))': + '@storybook/blocks@8.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.0(prettier@3.6.2))': dependencies: '@storybook/icons': 1.3.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - storybook: 8.6.0(prettier@3.5.2) + storybook: 8.6.0(prettier@3.6.2) ts-dedent: 2.2.0 optionalDependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@storybook/builder-vite@8.6.0(storybook@8.6.0(prettier@3.5.2))(vite@5.4.20(@types/node@22.13.5)(sass@1.85.1)(terser@5.44.0))': + '@storybook/builder-vite@8.6.0(storybook@8.6.0(prettier@3.6.2))(vite@5.4.20(@types/node@22.13.5)(sass@1.85.1)(terser@5.44.0))': dependencies: - '@storybook/csf-plugin': 8.6.0(storybook@8.6.0(prettier@3.5.2)) + '@storybook/csf-plugin': 8.6.0(storybook@8.6.0(prettier@3.6.2)) browser-assert: 1.2.1 - storybook: 8.6.0(prettier@3.5.2) + storybook: 8.6.0(prettier@3.6.2) ts-dedent: 2.2.0 vite: 5.4.20(@types/node@22.13.5)(sass@1.85.1)(terser@5.44.0) - '@storybook/components@8.6.0(storybook@8.6.0(prettier@3.5.2))': + '@storybook/components@8.6.0(storybook@8.6.0(prettier@3.6.2))': dependencies: - storybook: 8.6.0(prettier@3.5.2) + storybook: 8.6.0(prettier@3.6.2) - '@storybook/core@8.6.0(prettier@3.5.2)(storybook@8.6.0(prettier@3.5.2))': + '@storybook/core@8.6.0(prettier@3.6.2)(storybook@8.6.0(prettier@3.6.2))': dependencies: - '@storybook/theming': 8.6.0(storybook@8.6.0(prettier@3.5.2)) + '@storybook/theming': 8.6.0(storybook@8.6.0(prettier@3.6.2)) better-opn: 3.0.2 browser-assert: 1.2.1 esbuild: 0.25.10 @@ -8555,16 +8679,16 @@ snapshots: util: 0.12.5 ws: 8.18.1 optionalDependencies: - prettier: 3.5.2 + prettier: 3.6.2 transitivePeerDependencies: - bufferutil - storybook - supports-color - utf-8-validate - '@storybook/csf-plugin@8.6.0(storybook@8.6.0(prettier@3.5.2))': + '@storybook/csf-plugin@8.6.0(storybook@8.6.0(prettier@3.6.2))': dependencies: - storybook: 8.6.0(prettier@3.5.2) + storybook: 8.6.0(prettier@3.6.2) unplugin: 1.16.1 '@storybook/csf@0.0.1': @@ -8578,77 +8702,77 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@storybook/instrumenter@8.6.0(storybook@8.6.0(prettier@3.5.2))': + '@storybook/instrumenter@8.6.0(storybook@8.6.0(prettier@3.6.2))': dependencies: '@storybook/global': 5.0.0 '@vitest/utils': 2.1.9 - storybook: 8.6.0(prettier@3.5.2) + storybook: 8.6.0(prettier@3.6.2) - '@storybook/manager-api@8.6.0(storybook@8.6.0(prettier@3.5.2))': + '@storybook/manager-api@8.6.0(storybook@8.6.0(prettier@3.6.2))': dependencies: - storybook: 8.6.0(prettier@3.5.2) + storybook: 8.6.0(prettier@3.6.2) - '@storybook/preview-api@8.6.0(storybook@8.6.0(prettier@3.5.2))': + '@storybook/preview-api@8.6.0(storybook@8.6.0(prettier@3.6.2))': dependencies: - storybook: 8.6.0(prettier@3.5.2) + storybook: 8.6.0(prettier@3.6.2) - '@storybook/react-dom-shim@8.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.0(prettier@3.5.2))': + '@storybook/react-dom-shim@8.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.0(prettier@3.6.2))': dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - storybook: 8.6.0(prettier@3.5.2) + storybook: 8.6.0(prettier@3.6.2) - '@storybook/react-vite@8.6.0(@storybook/test@8.6.0(storybook@8.6.0(prettier@3.5.2)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.52.4)(storybook@8.6.0(prettier@3.5.2))(typescript@5.7.3)(vite@5.4.20(@types/node@22.13.5)(sass@1.85.1)(terser@5.44.0))': + '@storybook/react-vite@8.6.0(@storybook/test@8.6.0(storybook@8.6.0(prettier@3.6.2)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.52.4)(storybook@8.6.0(prettier@3.6.2))(typescript@5.7.3)(vite@5.4.20(@types/node@22.13.5)(sass@1.85.1)(terser@5.44.0))': dependencies: '@joshwooding/vite-plugin-react-docgen-typescript': 0.5.0(typescript@5.7.3)(vite@5.4.20(@types/node@22.13.5)(sass@1.85.1)(terser@5.44.0)) '@rollup/pluginutils': 5.1.4(rollup@4.52.4) - '@storybook/builder-vite': 8.6.0(storybook@8.6.0(prettier@3.5.2))(vite@5.4.20(@types/node@22.13.5)(sass@1.85.1)(terser@5.44.0)) - '@storybook/react': 8.6.0(@storybook/test@8.6.0(storybook@8.6.0(prettier@3.5.2)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.0(prettier@3.5.2))(typescript@5.7.3) + '@storybook/builder-vite': 8.6.0(storybook@8.6.0(prettier@3.6.2))(vite@5.4.20(@types/node@22.13.5)(sass@1.85.1)(terser@5.44.0)) + '@storybook/react': 8.6.0(@storybook/test@8.6.0(storybook@8.6.0(prettier@3.6.2)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.0(prettier@3.6.2))(typescript@5.7.3) find-up: 5.0.0 magic-string: 0.30.17 react: 18.3.1 react-docgen: 7.1.1 react-dom: 18.3.1(react@18.3.1) resolve: 1.22.10 - storybook: 8.6.0(prettier@3.5.2) + storybook: 8.6.0(prettier@3.6.2) tsconfig-paths: 4.2.0 vite: 5.4.20(@types/node@22.13.5)(sass@1.85.1)(terser@5.44.0) optionalDependencies: - '@storybook/test': 8.6.0(storybook@8.6.0(prettier@3.5.2)) + '@storybook/test': 8.6.0(storybook@8.6.0(prettier@3.6.2)) transitivePeerDependencies: - rollup - supports-color - typescript - '@storybook/react@8.6.0(@storybook/test@8.6.0(storybook@8.6.0(prettier@3.5.2)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.0(prettier@3.5.2))(typescript@5.7.3)': + '@storybook/react@8.6.0(@storybook/test@8.6.0(storybook@8.6.0(prettier@3.6.2)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.0(prettier@3.6.2))(typescript@5.7.3)': dependencies: - '@storybook/components': 8.6.0(storybook@8.6.0(prettier@3.5.2)) + '@storybook/components': 8.6.0(storybook@8.6.0(prettier@3.6.2)) '@storybook/global': 5.0.0 - '@storybook/manager-api': 8.6.0(storybook@8.6.0(prettier@3.5.2)) - '@storybook/preview-api': 8.6.0(storybook@8.6.0(prettier@3.5.2)) - '@storybook/react-dom-shim': 8.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.0(prettier@3.5.2)) - '@storybook/theming': 8.6.0(storybook@8.6.0(prettier@3.5.2)) + '@storybook/manager-api': 8.6.0(storybook@8.6.0(prettier@3.6.2)) + '@storybook/preview-api': 8.6.0(storybook@8.6.0(prettier@3.6.2)) + '@storybook/react-dom-shim': 8.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.0(prettier@3.6.2)) + '@storybook/theming': 8.6.0(storybook@8.6.0(prettier@3.6.2)) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - storybook: 8.6.0(prettier@3.5.2) + storybook: 8.6.0(prettier@3.6.2) optionalDependencies: - '@storybook/test': 8.6.0(storybook@8.6.0(prettier@3.5.2)) + '@storybook/test': 8.6.0(storybook@8.6.0(prettier@3.6.2)) typescript: 5.7.3 - '@storybook/test@8.6.0(storybook@8.6.0(prettier@3.5.2))': + '@storybook/test@8.6.0(storybook@8.6.0(prettier@3.6.2))': dependencies: '@storybook/global': 5.0.0 - '@storybook/instrumenter': 8.6.0(storybook@8.6.0(prettier@3.5.2)) + '@storybook/instrumenter': 8.6.0(storybook@8.6.0(prettier@3.6.2)) '@testing-library/dom': 10.4.0 '@testing-library/jest-dom': 6.5.0 '@testing-library/user-event': 14.5.2(@testing-library/dom@10.4.0) '@vitest/expect': 2.0.5 '@vitest/spy': 2.0.5 - storybook: 8.6.0(prettier@3.5.2) + storybook: 8.6.0(prettier@3.6.2) - '@storybook/theming@8.6.0(storybook@8.6.0(prettier@3.5.2))': + '@storybook/theming@8.6.0(storybook@8.6.0(prettier@3.6.2))': dependencies: - storybook: 8.6.0(prettier@3.5.2) + storybook: 8.6.0(prettier@3.6.2) '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.26.9)': dependencies: @@ -8819,6 +8943,188 @@ snapshots: '@trysound/sax@0.2.0': {} + '@tsparticles/basic@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + '@tsparticles/move-base': 3.9.1 + '@tsparticles/plugin-hex-color': 3.9.1 + '@tsparticles/plugin-hsl-color': 3.9.1 + '@tsparticles/plugin-rgb-color': 3.9.1 + '@tsparticles/shape-circle': 3.9.1 + '@tsparticles/updater-color': 3.9.1 + '@tsparticles/updater-opacity': 3.9.1 + '@tsparticles/updater-out-modes': 3.9.1 + '@tsparticles/updater-size': 3.9.1 + + '@tsparticles/engine@3.9.1': {} + + '@tsparticles/interaction-external-attract@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/interaction-external-bounce@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/interaction-external-bubble@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/interaction-external-connect@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/interaction-external-grab@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/interaction-external-pause@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/interaction-external-push@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/interaction-external-remove@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/interaction-external-repulse@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/interaction-external-slow@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/interaction-particles-attract@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/interaction-particles-collisions@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/interaction-particles-links@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/move-base@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/move-parallax@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/plugin-easing-quad@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/plugin-hex-color@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/plugin-hsl-color@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/plugin-rgb-color@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/react@3.0.0(@tsparticles/engine@3.9.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tsparticles/engine': 3.9.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@tsparticles/shape-circle@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/shape-emoji@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/shape-image@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/shape-line@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/shape-polygon@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/shape-square@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/shape-star@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/slim@3.9.1': + dependencies: + '@tsparticles/basic': 3.9.1 + '@tsparticles/engine': 3.9.1 + '@tsparticles/interaction-external-attract': 3.9.1 + '@tsparticles/interaction-external-bounce': 3.9.1 + '@tsparticles/interaction-external-bubble': 3.9.1 + '@tsparticles/interaction-external-connect': 3.9.1 + '@tsparticles/interaction-external-grab': 3.9.1 + '@tsparticles/interaction-external-pause': 3.9.1 + '@tsparticles/interaction-external-push': 3.9.1 + '@tsparticles/interaction-external-remove': 3.9.1 + '@tsparticles/interaction-external-repulse': 3.9.1 + '@tsparticles/interaction-external-slow': 3.9.1 + '@tsparticles/interaction-particles-attract': 3.9.1 + '@tsparticles/interaction-particles-collisions': 3.9.1 + '@tsparticles/interaction-particles-links': 3.9.1 + '@tsparticles/move-parallax': 3.9.1 + '@tsparticles/plugin-easing-quad': 3.9.1 + '@tsparticles/shape-emoji': 3.9.1 + '@tsparticles/shape-image': 3.9.1 + '@tsparticles/shape-line': 3.9.1 + '@tsparticles/shape-polygon': 3.9.1 + '@tsparticles/shape-square': 3.9.1 + '@tsparticles/shape-star': 3.9.1 + '@tsparticles/updater-life': 3.9.1 + '@tsparticles/updater-rotate': 3.9.1 + '@tsparticles/updater-stroke-color': 3.9.1 + + '@tsparticles/updater-color@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/updater-life@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/updater-opacity@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/updater-out-modes@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/updater-rotate@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/updater-size@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + + '@tsparticles/updater-stroke-color@3.9.1': + dependencies: + '@tsparticles/engine': 3.9.1 + '@types/aria-query@5.0.4': {} '@types/babel__core@7.20.5': @@ -11007,10 +11313,10 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-prettier@5.2.3(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.5.2): + eslint-plugin-prettier@5.2.3(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.6.2): dependencies: eslint: 8.57.1 - prettier: 3.5.2 + prettier: 3.6.2 prettier-linter-helpers: 1.0.0 synckit: 0.9.2 optionalDependencies: @@ -13378,7 +13684,7 @@ snapshots: dependencies: fast-diff: 1.3.0 - prettier@3.5.2: {} + prettier@3.6.2: {} pretty-format@27.5.1: dependencies: @@ -14035,11 +14341,11 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 - storybook@8.6.0(prettier@3.5.2): + storybook@8.6.0(prettier@3.6.2): dependencies: - '@storybook/core': 8.6.0(prettier@3.5.2)(storybook@8.6.0(prettier@3.5.2)) + '@storybook/core': 8.6.0(prettier@3.6.2)(storybook@8.6.0(prettier@3.6.2)) optionalDependencies: - prettier: 3.5.2 + prettier: 3.6.2 transitivePeerDependencies: - bufferutil - supports-color diff --git a/public/json/departments.json b/public/json/departments.json index 49188ee2..f51249de 100644 --- a/public/json/departments.json +++ b/public/json/departments.json @@ -218,4 +218,4 @@ "WRT", "YID", "YOR" -] \ No newline at end of file +] diff --git a/shell.nix b/shell.nix new file mode 100644 index 00000000..5dab565e --- /dev/null +++ b/shell.nix @@ -0,0 +1,10 @@ +(import ( + let + rev = "v1.1.0"; + sha256 = "sha256:19d2z6xsvpxm184m41qrpi1bplilwipgnzv9jy17fgw421785q1m"; + in + fetchTarball { + inherit sha256; + url = "https://github.com/NixOS/flake-compat/archive/${rev}.tar.gz"; + } +) { src = ./.; }).shellNix diff --git a/src/pages/404/Page404.tsx b/src/pages/404/Page404.tsx index f5e94389..87031b46 100644 --- a/src/pages/404/Page404.tsx +++ b/src/pages/404/Page404.tsx @@ -271,12 +271,12 @@ export default function Page404(): JSX.Element { } function _0x5629d1() { let _0x13c635 = _0xdd3699; - _0x5b7f43(), + (_0x5b7f43(), _0x16f39e[_0x13c635(0x81)]( _0x228047, 0x9c + -0x1 * 0x23ab + 0x230f * 0x1, -0x1c26 + 0x7bf + -0x6cd * -0x3 - ); + )); } let _0x93f360 = 0x64 * 0x49 + 0x1e1e + -0x3aa2; function _0x5b7f43() { @@ -373,12 +373,12 @@ export default function Page404(): JSX.Element { (-0x2469 + 0x156a * -0x1 + 0x39e2)) + (-0x1 * 0x13f8 + 0x6df + -0x1 * -0xd29); if (_0x3e178a == 0x1 * -0x1a87 + 0x1fdd + -0x555 * 0x1) { - (_0x546fb5 = + ((_0x546fb5 = (_0x227002 * (-0x10d7 + 0x1 * 0x15ad + -0x2 * 0x263)) & (-0x25ca * 0x1 + 0x8 * 0x278 + -0x1 * -0x1219)), (_0x1667c5 = (_0x32116b * (0x4d3 + 0x1c09 * -0x1 + 0x3 * 0x7c2)) & - (-0xf06 * 0x2 + -0x144f * -0x1 + -0x344 * -0x3)); + (-0xf06 * 0x2 + -0x144f * -0x1 + -0x344 * -0x3))); if (_0x5b3085 < -0xa * 0xed + -0xd19 + 0x1 * 0x165b) _0x1667c5 += -0xd48 + 0xf6c + 0xc * -0x2b; } @@ -410,10 +410,10 @@ export default function Page404(): JSX.Element { (-0x2709 + -0x6 * -0x312 + -0x39a * -0x6)), (_0x267dd3 = _0x38c463)); } - (_0x227002 += _0x4b089b), + ((_0x227002 += _0x4b089b), (_0x2aec99 += _0x5b3085), (_0x32116b += _0x1eaaad), - (_0x38c463 += _0x57383c); + (_0x38c463 += _0x57383c)); } } let _0x5cba48 = @@ -430,7 +430,7 @@ export default function Page404(): JSX.Element { let _0xdf8389 = ((_0x13f1b0 & (-0x247a + -0x4 * -0x9c2 + -0x39 * 0x7)) * _0x2062a9 * _0x5c387a) / ((0x1d5 * 0xa + -0x250a + -0x31 * -0x67) * (-0x7 * 0x25f + -0xae7 + 0x1c7f * 0x1)); - (_0x228047[_0x4626de(0x8e)][ + ((_0x228047[_0x4626de(0x8e)][ (_0x132623 + _0x1a573d * _0x124180) * (0x29 * -0xa9 + -0x94 * -0x2b + -0x239 * -0x1) + (0x55d * 0x2 + 0xeed * 0x1 + -0xc7 * 0x21) ] = _0x5cba48), @@ -441,7 +441,7 @@ export default function Page404(): JSX.Element { (_0x228047[_0x4626de(0x8e)][ (_0x132623 + _0x1a573d * _0x124180) * (0x1e2a + -0x21df + -0x1 * -0x3b9) + (0x1e79 + 0x860 * -0x2 + 0x1 * -0xdb7) - ] = _0xdf8389); + ] = _0xdf8389)); } } } diff --git a/src/shared/storage/OptionsStore.ts b/src/shared/storage/OptionsStore.ts index 33291ead..d0a66634 100644 --- a/src/shared/storage/OptionsStore.ts +++ b/src/shared/storage/OptionsStore.ts @@ -24,6 +24,8 @@ export interface IOptionsStore { /** whether the promo should be shown */ showUTDiningPromo: boolean; + /** whether users are allowed to bypass the 10 schedule limit */ + allowMoreSchedules: boolean; } export const OptionsStore = createSyncStore({ @@ -34,6 +36,7 @@ export const OptionsStore = createSyncStore({ alwaysOpenCalendarInNewTab: false, showCalendarSidebar: true, showUTDiningPromo: true, + allowMoreSchedules: false, }); /** @@ -50,6 +53,7 @@ export const initSettings = async () => alwaysOpenCalendarInNewTab: await OptionsStore.get('alwaysOpenCalendarInNewTab'), showCalendarSidebar: await OptionsStore.get('showCalendarSidebar'), showUTDiningPromo: await OptionsStore.get('showUTDiningPromo'), + allowMoreSchedules: await OptionsStore.get('allowMoreSchedules'), }) satisfies IOptionsStore; // Clothing retailer right diff --git a/src/stories/components/CalendarFooter.stories.tsx b/src/stories/components/CalendarFooter.stories.tsx new file mode 100644 index 00000000..1a126a4f --- /dev/null +++ b/src/stories/components/CalendarFooter.stories.tsx @@ -0,0 +1,19 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import CalendarFooter from '@views/components/calendar/CalendarFooter'; +import React from 'react'; + +const meta = { + title: 'Components/Calendar/CalendarFooter', + component: CalendarFooter, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: {}, +}; diff --git a/src/views/components/calendar/Calendar.tsx b/src/views/components/calendar/Calendar.tsx index 0c26f21c..1998eccb 100644 --- a/src/views/components/calendar/Calendar.tsx +++ b/src/views/components/calendar/Calendar.tsx @@ -114,11 +114,12 @@ export default function Calendar(): ReactNode { + + + + +
+
+ + Import Schedule + +

Import from a schedule file

+
+ + Import Schedule + +
+ + + +
+
+ + Course Conflict Highlight + +

+ Adds a red strikethrough to courses that have conflicting times. +

+
+ { + setHighlightConflicts(!highlightConflicts); + OptionsStore.set('enableHighlightConflicts', !highlightConflicts); + }} + /> +
+ + + +
+
+ + Load All Courses in Course Schedule + +

+ Loads all courses in the Course Schedule site by scrolling, instead of using next/prev page + buttons. +

+
+ { + setLoadAllCourses(!loadAllCourses); + OptionsStore.set('enableScrollToLoad', !loadAllCourses); + }} + /> +
+ + + +
+
+ + Allow more than 10 schedules + +

+ Allow bypassing the 10-schedule limit. Intended for advisors or staff who need to create + many schedules on behalf of students. +

+
+ { + setIncreaseScheduleLimit(!increaseScheduleLimit); + OptionsStore.set('allowMoreSchedules', !increaseScheduleLimit); + }} + /> +
+ + + +
+
+ + Always Open Calendar in New Tab + +

+ Always opens the calendar view in a new tab when navigating to the calendar page. May + prevent issues where the calendar refuses to open. +

+
+ { + setCalendarNewTab(!calendarNewTab); + OptionsStore.set('alwaysOpenCalendarInNewTab', !calendarNewTab); + }} + /> +
+ + + +
+
+ + Reset All Data + +

Erases all schedules and courses you have.

+
+ +
+ + {DISPLAY_PREVIEWS && ( + + + 01234 MWF 10:00 AM - 11:00 AM UTC 1.234 + + + )} + + +); diff --git a/src/views/components/settings/ContributorCard.tsx b/src/views/components/settings/ContributorCard.tsx new file mode 100644 index 00000000..af7efb61 --- /dev/null +++ b/src/views/components/settings/ContributorCard.tsx @@ -0,0 +1,54 @@ +import Text from '@views/components/common/Text/Text'; +import React from 'react'; + +interface ContributorCardProps { + name: string; + githubUsername: string; + roles: string[]; + stats?: { + commits: number; + linesAdded: number; + linesDeleted: number; + mergedPRs?: number; + }; + showStats: boolean; + includeMergedPRs: boolean; +} + +/** + * GitHub contributor card component + */ +export const ContributorCard: React.FC = ({ + name, + githubUsername, + roles, + stats, + showStats, + includeMergedPRs, +}) => ( +
+ window.open(`https://github.com/${githubUsername}`, '_blank')} + > + {name} + + {roles.map(role => ( +

+ {role} +

+ ))} + {showStats && stats && ( +
+

GitHub Stats (UTRP repo):

+ {includeMergedPRs && stats.mergedPRs !== undefined && ( +

Merged PRs: {stats.mergedPRs}

+ )} +

Commits: {stats.commits}

+

{stats.linesAdded}++

+

{stats.linesDeleted}--

+
+ )} +
+); diff --git a/src/views/components/settings/Settings.tsx b/src/views/components/settings/Settings.tsx index 9706a0b5..daa62765 100644 --- a/src/views/components/settings/Settings.tsx +++ b/src/views/components/settings/Settings.tsx @@ -1,114 +1,68 @@ -// import addCourse from '@pages/background/lib/addCourse'; +// Pages import { addCourseByURL } from '@pages/background/lib/addCourseByURL'; import { deleteAllSchedules } from '@pages/background/lib/deleteSchedule'; import importSchedule from '@pages/background/lib/importSchedule'; -import { CalendarDots, Trash } from '@phosphor-icons/react'; +import { CalendarDots } from '@phosphor-icons/react'; +// Shared import { background } from '@shared/messages'; import { DevStore } from '@shared/storage/DevStore'; import { initSettings, OptionsStore } from '@shared/storage/OptionsStore'; import { CRX_PAGES } from '@shared/types/CRXPages'; -import MIMEType from '@shared/types/MIMEType'; -// import { addCourseByUrl } from '@shared/util/courseUtils'; -// import { getCourseColors } from '@shared/util/colors'; -// import CalendarCourseCell from '@views/components/calendar/CalendarCourseCell'; +import Particles from '@tsparticles/react'; import { Button } from '@views/components/common/Button'; import { usePrompt } from '@views/components/common/DialogProvider/DialogProvider'; +// Views import Divider from '@views/components/common/Divider'; import { LargeLogo } from '@views/components/common/LogoIcon'; -// import PopupCourseBlock from '@views/components/common/PopupCourseBlock'; -import SwitchButton from '@views/components/common/SwitchButton'; import Text from '@views/components/common/Text/Text'; +// Hooks import useChangelog from '@views/hooks/useChangelog'; import useSchedules from '@views/hooks/useSchedules'; -// import { CourseCatalogScraper } from '@views/lib/CourseCatalogScraper'; -// import getCourseTableRows from '@views/lib/getCourseTableRows'; import { GitHubStatsService, LONGHORN_DEVELOPERS_ADMINS, LONGHORN_DEVELOPERS_SWE } from '@views/lib/getGitHubStats'; -// import { SiteSupport } from '@views/lib/getSiteSupport'; -import clsx from 'clsx'; -import React, { useCallback, useEffect, useState } from 'react'; +// Misc +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +// Icons import IconoirGitFork from '~icons/iconoir/git-fork'; -import { handleExportJson } from '../calendar/utils'; -// import { ExampleCourse } from 'src/stories/components/ConflictsWithWarning.stories';; -import FileUpload from '../common/FileUpload'; import { useMigrationDialog } from '../common/MigrationDialog'; -// import RefreshIcon from '~icons/material-symbols/refresh'; +import { AdvancedSettings } from './AdvancedSettings'; +import { DEV_MODE_CLICK_TARGET, INCLUDE_MERGED_PRS, STATS_TOGGLE_KEY } from './constants'; +import { ContributorCard } from './ContributorCard'; import DevMode from './DevMode'; -import Preview from './Preview'; +import { useBirthdayCelebration } from './useBirthdayCelebration'; +import { useDevMode } from './useDevMode'; const manifest = chrome.runtime.getManifest(); -const gitHubStatsService = new GitHubStatsService(); -const includeMergedPRs = false; - -const DISPLAY_PREVIEWS = false; -const PREVIEW_SECTION_DIV_CLASSNAME = DISPLAY_PREVIEWS ? 'w-1/2 space-y-4' : 'flex-grow space-y-4'; - /** - * Custom hook for enabling developer mode. - * - * @param targetCount - The target count to activate developer mode. - * @returns A tuple containing a boolean indicating if developer mode is active and a function to increment the count. - */ -const useDevMode = (targetCount: number): [boolean, () => void] => { - const [count, setCount] = useState(0); - const [active, setActive] = useState(false); - const [lastClick, setLastClick] = useState(0); - - const incrementCount = useCallback(() => { - const now = Date.now(); - if (now - lastClick < 500) { - setCount(prevCount => { - const newCount = prevCount + 1; - if (newCount === targetCount) { - setActive(true); - } - return newCount; - }); - } else { - setCount(1); - } - setLastClick(now); - }, [lastClick, targetCount]); - - useEffect(() => { - const timer = setTimeout(() => setCount(0), 3000); - return () => clearTimeout(timer); - }, [count]); - - return [active, incrementCount]; -}; - -/** - * Component for managing user settings and preferences. + * Main Settings Component for managing user settings and preferences. * * @returns The Settings component. */ export default function Settings(): JSX.Element { - const [_enableCourseStatusChips, setEnableCourseStatusChips] = useState(false); - // const [_showTimeLocation, setShowTimeLocation] = useState(false); - const [highlightConflicts, setHighlightConflicts] = useState(false); - const [loadAllCourses, setLoadAllCourses] = useState(false); - const [_enableDataRefreshing, setEnableDataRefreshing] = useState(false); - const [calendarNewTab, setCalendarNewTab] = useState(false); + const gitHubStatsService = useMemo(() => new GitHubStatsService(), []); - const showMigrationDialog = useMigrationDialog(); - - // Toggle GitHub stats when the user presses the 'S' key - const [showGitHubStats, setShowGitHubStats] = useState(false); + // State + const [highlightConflicts, setHighlightConflicts] = useState(false); + const [loadAllCourses, setLoadAllCourses] = useState(false); + const [calendarNewTab, setCalendarNewTab] = useState(false); + const [increaseScheduleLimit, setIncreaseScheduleLimit] = useState(false); + const [showGitHubStats, setShowGitHubStats] = useState(false); const [githubStats, setGitHubStats] = useState > | null>(null); + const [isDeveloper, setIsDeveloper] = useState(false); const [activeSchedule] = useSchedules(); - // const [isRefreshing, setIsRefreshing] = useState(false); - - const [isDeveloper, setIsDeveloper] = useState(false); - const showDialog = usePrompt(); const handleChangelogOnClick = useChangelog(); + const showMigrationDialog = useMigrationDialog(); + const [devMode, toggleDevMode] = useDevMode(DEV_MODE_CLICK_TARGET); + const { showParticles, particlesInit, particlesOptions, triggerCelebration, isBirthday } = useBirthdayCelebration(); + + // Initialize settings and listeners useEffect(() => { const fetchGitHubStats = async () => { try { @@ -120,19 +74,12 @@ export default function Settings(): JSX.Element { }; const initAndSetSettings = async () => { - const { - enableCourseStatusChips, - enableHighlightConflicts, - enableScrollToLoad, - enableDataRefreshing, - alwaysOpenCalendarInNewTab, - } = await initSettings(); - setEnableCourseStatusChips(enableCourseStatusChips); - // setShowTimeLocation(enableTimeAndLocationInPopup); + const { enableHighlightConflicts, enableScrollToLoad, alwaysOpenCalendarInNewTab, allowMoreSchedules } = + await initSettings(); setHighlightConflicts(enableHighlightConflicts); setLoadAllCourses(enableScrollToLoad); - setEnableDataRefreshing(enableDataRefreshing); setCalendarNewTab(alwaysOpenCalendarInNewTab); + setIncreaseScheduleLimit(allowMoreSchedules); }; const initDS = async () => { @@ -140,68 +87,50 @@ export default function Settings(): JSX.Element { setIsDeveloper(isDev); }; + const handleKeyPress = (event: KeyboardEvent) => { + if (event.key === STATS_TOGGLE_KEY || event.key === STATS_TOGGLE_KEY.toUpperCase()) { + setShowGitHubStats(prev => !prev); + } + }; + + // Listeners const ds_l1 = DevStore.listen('isDeveloper', async ({ newValue }) => { setIsDeveloper(newValue); }); + const l1 = OptionsStore.listen('enableHighlightConflicts', async ({ newValue }) => { + setHighlightConflicts(newValue); + }); + + const l2 = OptionsStore.listen('enableScrollToLoad', async ({ newValue }) => { + setLoadAllCourses(newValue); + }); + + const l3 = OptionsStore.listen('alwaysOpenCalendarInNewTab', async ({ newValue }) => { + setCalendarNewTab(newValue); + }); + + const l4 = OptionsStore.listen('allowMoreSchedules', async ({ newValue }) => { + setIncreaseScheduleLimit(newValue); + }); + + window.addEventListener('keydown', handleKeyPress); + initDS(); fetchGitHubStats(); initAndSetSettings(); - const handleKeyPress = (event: KeyboardEvent) => { - if (event.key === 'S' || event.key === 's') { - setShowGitHubStats(prev => !prev); - } - }; - - window.addEventListener('keydown', handleKeyPress); - - // Listen for changes in the settings - const l1 = OptionsStore.listen('enableCourseStatusChips', async ({ newValue }) => { - setEnableCourseStatusChips(newValue); - // console.log('enableCourseStatusChips', newValue); - }); - - // const l2 = OptionsStore.listen('enableTimeAndLocationInPopup', async ({ newValue }) => { - // setShowTimeLocation(newValue); - // // console.log('enableTimeAndLocationInPopup', newValue); - // }); - - const l2 = OptionsStore.listen('enableHighlightConflicts', async ({ newValue }) => { - setHighlightConflicts(newValue); - // console.log('enableHighlightConflicts', newValue); - }); - - const l3 = OptionsStore.listen('enableScrollToLoad', async ({ newValue }) => { - setLoadAllCourses(newValue); - // console.log('enableScrollToLoad', newValue); - }); - - const l4 = OptionsStore.listen('enableDataRefreshing', async ({ newValue }) => { - setEnableDataRefreshing(newValue); - // console.log('enableDataRefreshing', newValue); - }); - - const l5 = OptionsStore.listen('alwaysOpenCalendarInNewTab', async ({ newValue }) => { - setCalendarNewTab(newValue); - // console.log('alwaysOpenCalendarInNewTab', newValue); - }); - - // Remove listeners when the component is unmounted return () => { OptionsStore.removeListener(l1); OptionsStore.removeListener(l2); OptionsStore.removeListener(l3); OptionsStore.removeListener(l4); - OptionsStore.removeListener(l5); - DevStore.removeListener(ds_l1); - window.removeEventListener('keydown', handleKeyPress); }; - }, []); + }, [gitHubStatsService]); - const handleEraseAll = () => { + const handleEraseAll = useCallback(() => { showDialog({ title: 'Erase All Course/Schedule Data', description: ( @@ -228,34 +157,45 @@ export default function Settings(): JSX.Element { ), }); - }; + }, [showDialog]); - const handleImportClick = async (event: React.ChangeEvent) => { + const handleImportClick = useCallback(async (event: React.ChangeEvent) => { const file = event.target.files?.[0]; - if (file) { - const reader = new FileReader(); - reader.onload = async e => { - try { - const result = e.target?.result as string; - const jsonObject = JSON.parse(result); - await importSchedule(jsonObject); - } catch (error) { - console.error('Invalid import file!'); - } - }; - reader.readAsText(file); + if (!file) return; + + try { + const text = await file.text(); + const data = JSON.parse(text); + await importSchedule(data); + alert('Schedule imported successfully.'); + } catch (error) { + console.error('Error importing schedule:', error); + alert('Failed to import schedule. Make sure the file is a valid .json format.'); } - }; + }, []); - // const handleAddCourseByLink = async () => { - // // todo: Use a proper modal instead of a prompt - // const link: string | null = prompt('Enter course link'); - // // Exit if the user cancels the prompt - // if (link === null) return; - // await addCourseByUrl(link, activeSchedule); - // }; + const sortedContributors = useMemo(() => { + if (!githubStats) return LONGHORN_DEVELOPERS_SWE; + return [...LONGHORN_DEVELOPERS_SWE].sort( + (a, b) => + (githubStats.userGitHubStats[b.githubUsername]?.commits ?? 0) - + (githubStats.userGitHubStats[a.githubUsername]?.commits ?? 0) + ); + }, [githubStats]); - const [devMode, toggleDevMode] = useDevMode(10); + const additionalContributors = useMemo(() => { + if (!githubStats) return []; + return Object.keys(githubStats.userGitHubStats) + .filter( + username => + !LONGHORN_DEVELOPERS_ADMINS.some(admin => admin.githubUsername === username) && + !LONGHORN_DEVELOPERS_SWE.some(swe => swe.githubUsername === username) + ) + .sort( + (a, b) => + (githubStats.userGitHubStats[b]?.commits ?? 0) - (githubStats.userGitHubStats[a]?.commits ?? 0) + ); + }, [githubStats]); if (devMode) { DevStore.set('isDeveloper', true); @@ -263,13 +203,32 @@ export default function Settings(): JSX.Element { } return ( -
+
+ {particlesInit && showParticles && ( + + )} +
- - Settings and Credits - +
+ + Settings and Credits + + {isBirthday && ( + + 🎉 Happy Birthday LHD! 🎉 + + )} +
-
- - */} - -
-
- - Export Current Schedule - -

- Backup your active schedule to a portable file -

-
- -
- - - -
-
- - Import Schedule - -

Import from a schedule file

-
- - Import Schedule - -
- - - -
-
- - Course Conflict Highlight - -

- Adds a red strikethrough to courses that have conflicting times. -

-
- { - setHighlightConflicts(!highlightConflicts); - OptionsStore.set('enableHighlightConflicts', !highlightConflicts); - }} - /> -
- - - -
-
- - Load All Courses in Course Schedule - -

- Loads all courses in the Course Schedule site by scrolling, instead of using - next/prev page buttons. -

-
- { - setLoadAllCourses(!loadAllCourses); - OptionsStore.set('enableScrollToLoad', !loadAllCourses); - }} - /> -
- - - -
-
- - Always Open Calendar in New Tab - -

- Always opens the calendar view in a new tab when navigating to the calendar - page. May prevent issues where the calendar refuses to open. -

-
- { - setCalendarNewTab(!calendarNewTab); - OptionsStore.set('alwaysOpenCalendarInNewTab', !calendarNewTab); - }} - /> -
- - - -
-
- - Reset All Data - -

- Erases all schedules and courses you have. -

-
- -
-
- {DISPLAY_PREVIEWS && ( - - - 01234 MWF 10:00 AM - 11:00 AM UTC 1.234 - - - )} -
- + @@ -563,17 +322,21 @@ export default function Settings(): JSX.Element { Open Debug Page + + + + + )} - - - - - @@ -586,144 +349,44 @@ export default function Settings(): JSX.Element {
{LONGHORN_DEVELOPERS_ADMINS.map(admin => ( -
- - window.open(`https://github.com/${admin.githubUsername}`, '_blank') - } - > - {admin.name} - - {admin.role.map(role => ( -

- {role} -

- ))} - {showGitHubStats && githubStats && ( -
-

GitHub Stats (UTRP repo):

- {includeMergedPRs && ( -

- Merged PRS:{' '} - {githubStats.adminGitHubStats[admin.githubUsername]?.mergedPRs} -

- )} -

- Commits: {githubStats.adminGitHubStats[admin.githubUsername]?.commits} -

-

- {githubStats.adminGitHubStats[admin.githubUsername]?.linesAdded} ++ -

-

- {githubStats.adminGitHubStats[admin.githubUsername]?.linesDeleted} -- -

-
- )} -
+ name={admin.name} + githubUsername={admin.githubUsername} + roles={admin.role} + stats={githubStats?.adminGitHubStats[admin.githubUsername]} + showStats={showGitHubStats} + includeMergedPRs={INCLUDE_MERGED_PRS} + /> ))}
+

UTRP CONTRIBUTORS

- {LONGHORN_DEVELOPERS_SWE.sort( - (a, b) => - (githubStats?.userGitHubStats[b.githubUsername]?.commits ?? 0) - - (githubStats?.userGitHubStats[a.githubUsername]?.commits ?? 0) - ).map(swe => ( -
( + - - window.open(`https://github.com/${swe.githubUsername}`, '_blank') - } - > - {swe.name} - - {swe.role.map(role => ( -

- {role} -

- ))} - {showGitHubStats && githubStats && ( -
-

GitHub Stats (UTRP repo):

- {includeMergedPRs && ( -

- Merged PRS:{' '} - {githubStats.userGitHubStats[swe.githubUsername]?.mergedPRs} -

- )} -

- Commits: {githubStats.userGitHubStats[swe.githubUsername]?.commits} -

-

- {githubStats.userGitHubStats[swe.githubUsername]?.linesAdded} ++ -

-

- {githubStats.userGitHubStats[swe.githubUsername]?.linesDeleted} -- -

-
- )} -
+ name={swe.name} + githubUsername={swe.githubUsername} + roles={swe.role} + stats={githubStats?.userGitHubStats[swe.githubUsername]} + showStats={showGitHubStats} + includeMergedPRs={INCLUDE_MERGED_PRS} + /> + ))} + {additionalContributors.map(username => ( + ))} - {githubStats && - Object.keys(githubStats.userGitHubStats) - .filter( - username => - !LONGHORN_DEVELOPERS_ADMINS.some( - admin => admin.githubUsername === username - ) && !LONGHORN_DEVELOPERS_SWE.some(swe => swe.githubUsername === username) - ) - .sort( - (a, b) => - (githubStats.userGitHubStats[b]?.commits ?? 0) - - (githubStats.userGitHubStats[a]?.commits ?? 0) - ) - .map(username => ( -
- window.open(`https://github.com/${username}`, '_blank')} - > - {githubStats.names[username]} - -

Contributor

- {showGitHubStats && ( -
-

GitHub Stats (UTRP repo):

- {includeMergedPRs && ( -

- Merged PRs:{' '} - {githubStats.userGitHubStats[username]?.mergedPRs} -

- )} -

- Commits: {githubStats.userGitHubStats[username]?.commits} -

-

- {githubStats.userGitHubStats[username]?.linesAdded} ++ -

-

- {githubStats.userGitHubStats[username]?.linesDeleted} -- -

-
- )} -
- ))}
diff --git a/src/views/components/settings/constants.ts b/src/views/components/settings/constants.ts new file mode 100644 index 00000000..d7c5768d --- /dev/null +++ b/src/views/components/settings/constants.ts @@ -0,0 +1,13 @@ +export const DISPLAY_PREVIEWS = false; +export const PREVIEW_SECTION_DIV_CLASSNAME = DISPLAY_PREVIEWS ? 'w-1/2 space-y-4' : 'flex-grow space-y-4'; + +export const STATS_TOGGLE_KEY = 's'; +export const INCLUDE_MERGED_PRS = false; +export const DEV_MODE_CLICK_TARGET = 5; +export const DEV_MODE_CLICK_TIMEOUT = 5000; +export const DEV_MODE_CLICK_INTERVAL = 500; + +// LHD Birthday: January 9th, 2025 +export const LHD_BIRTHDAY = { month: 0, day: 9 }; +export const BIRTHDAY_CELEBRATION_DURATION = 5000; +export const BIRTHDAY_CELEBRATION_DEBOUNCE = 2000; diff --git a/src/views/components/settings/useBirthdayCelebration.ts b/src/views/components/settings/useBirthdayCelebration.ts new file mode 100644 index 00000000..01cbf7f5 --- /dev/null +++ b/src/views/components/settings/useBirthdayCelebration.ts @@ -0,0 +1,140 @@ +import type { Engine, ISourceOptions } from '@tsparticles/engine'; +import { initParticlesEngine } from '@tsparticles/react'; +import { loadSlim } from '@tsparticles/slim'; +import { useCallback, useEffect, useMemo, useState } from 'react'; + +import { BIRTHDAY_CELEBRATION_DEBOUNCE, BIRTHDAY_CELEBRATION_DURATION, LHD_BIRTHDAY } from './constants'; + +/** + * Custom hook for birthday celebration particles + */ +export const useBirthdayCelebration = () => { + const [showParticles, setShowParticles] = useState(false); + const [particlesInit, setParticlesInit] = useState(false); + const [lastCelebration, setLastCelebration] = useState(0); + + const isBirthday = useMemo(() => { + const today = new Date(); + return today.getMonth() === LHD_BIRTHDAY.month && today.getDate() === LHD_BIRTHDAY.day; + }, []); + + useEffect(() => { + initParticlesEngine(async (engine: Engine) => { + await loadSlim(engine); + }).then(() => { + setParticlesInit(true); + }); + }, []); + + const triggerCelebration = useCallback(() => { + if (!isBirthday) return; + + const now = Date.now(); + // Debounce: prevent triggering again within BIRTHDAY_CELEBRATION_DEBOUNCE ms + if (now - lastCelebration < BIRTHDAY_CELEBRATION_DEBOUNCE) return; + + setLastCelebration(now); + setShowParticles(true); + setTimeout(() => setShowParticles(false), BIRTHDAY_CELEBRATION_DURATION); + }, [isBirthday, lastCelebration]); + + const particlesOptions: ISourceOptions = useMemo( + () => ({ + fullScreen: { enable: true, zIndex: 1 }, + particles: { + color: { value: ['#BF5700', '#333F48', '#FFFFFF'] }, // UT colors + move: { + direction: 'bottom', + enable: true, + outModes: { + default: 'out', + }, + size: true, + speed: { + min: 1, + max: 3, + }, + }, + number: { + value: 500, + density: { + enable: true, + area: 800, + }, + }, + opacity: { + value: 1, + animation: { + enable: false, + startValue: 'max', + destroy: 'min', + speed: 0.3, + sync: true, + }, + }, + rotate: { + value: { + min: 0, + max: 360, + }, + direction: 'random', + move: true, + animation: { + enable: true, + speed: 60, + }, + }, + tilt: { + direction: 'random', + enable: true, + move: true, + value: { + min: 0, + max: 360, + }, + animation: { + enable: true, + speed: 60, + }, + }, + shape: { + type: ['circle', 'square'], + options: {}, + }, + size: { + value: { + min: 2, + max: 4, + }, + }, + roll: { + darken: { + enable: true, + value: 30, + }, + enlighten: { + enable: true, + value: 30, + }, + enable: true, + speed: { + min: 15, + max: 25, + }, + }, + wobble: { + distance: 30, + enable: true, + move: true, + speed: { + min: -15, + max: 15, + }, + }, + }, + }), + [] + ); + + return { showParticles, particlesInit, particlesOptions, triggerCelebration, isBirthday }; +}; diff --git a/src/views/components/settings/useDevMode.ts b/src/views/components/settings/useDevMode.ts new file mode 100644 index 00000000..a4a0873b --- /dev/null +++ b/src/views/components/settings/useDevMode.ts @@ -0,0 +1,35 @@ +import { useCallback, useEffect, useState } from 'react'; + +import { DEV_MODE_CLICK_INTERVAL, DEV_MODE_CLICK_TIMEOUT } from './constants'; + +/** + * Custom hook for enabling developer mode via rapid clicking + */ +export const useDevMode = (targetCount: number): [boolean, () => void] => { + const [count, setCount] = useState(0); + const [active, setActive] = useState(false); + const [lastClick, setLastClick] = useState(0); + + const incrementCount = useCallback(() => { + const now = Date.now(); + if (now - lastClick < DEV_MODE_CLICK_INTERVAL) { + setCount(prevCount => { + const newCount = prevCount + 1; + if (newCount === targetCount) { + setActive(true); + } + return newCount; + }); + } else { + setCount(1); + } + setLastClick(now); + }, [lastClick, targetCount]); + + useEffect(() => { + const timer = setTimeout(() => setCount(0), DEV_MODE_CLICK_TIMEOUT); + return () => clearTimeout(timer); + }, [count]); + + return [active, incrementCount]; +}; diff --git a/src/views/hooks/useEnforceScheduleLimit.tsx b/src/views/hooks/useEnforceScheduleLimit.tsx index 53902896..4409fec6 100644 --- a/src/views/hooks/useEnforceScheduleLimit.tsx +++ b/src/views/hooks/useEnforceScheduleLimit.tsx @@ -1,5 +1,8 @@ +import { background } from '@shared/messages'; +import { OptionsStore } from '@shared/storage/OptionsStore'; +import { CRX_PAGES } from '@shared/types/CRXPages'; import useSchedules from '@views/hooks/useSchedules'; -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { Button } from '../components/common/Button'; import { usePrompt } from '../components/common/DialogProvider/DialogProvider'; @@ -17,8 +20,33 @@ const SCHEDULE_LIMIT = 10; export function useEnforceScheduleLimit(): () => boolean { const [, schedules] = useSchedules(); const showDialog = usePrompt(); + const [allowMoreSchedules, setAllowMoreSchedules] = useState(false); + + useEffect(() => { + let mounted = true; + (async () => { + try { + const val = await OptionsStore.get('allowMoreSchedules'); + if (mounted) setAllowMoreSchedules(val ?? false); + } catch (err) { + console.error('Failed to read allowMoreSchedules from OptionsStore:', err); + } + })(); + + const listener = OptionsStore.listen('allowMoreSchedules', async ({ newValue }) => { + setAllowMoreSchedules(newValue); + }); + + return () => { + mounted = false; + OptionsStore.removeListener(listener); + }; + }, []); return useCallback(() => { + // If user has enabled bypass, allow creating more schedules + if (allowMoreSchedules) return true; + if (schedules.length >= SCHEDULE_LIMIT) { showDialog({ title: `You have too many schedules!`, @@ -27,19 +55,33 @@ export function useEnforceScheduleLimit(): () => boolean { <> To encourage organization,{' '} please consider deleting any unused schedules you - may have. + may have. You can increase the limit in the settings if it’s really necessary. ), buttons: close => ( - + <> + + + ), }); return false; } return true; - }, [schedules, showDialog]); + }, [schedules, showDialog, allowMoreSchedules]); } diff --git a/vite.config.ts b/vite.config.ts index 77e83991..673fca2c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -88,6 +88,31 @@ const fixManifestOptionsPage = (): Plugin => ({ }, }); +function getGitInfo() { + // Try environment variables first (for Nix builds) + if (process.env.VITE_GIT_BRANCH && process.env.VITE_GIT_COMMIT) { + return { + gitBranch: process.env.VITE_GIT_BRANCH, + gitCommit: process.env.VITE_GIT_COMMIT, + }; + } + + // Fall back to git commands (for local development) + try { + return { + gitBranch: execSync('git rev-parse --abbrev-ref HEAD').toString().trim(), + gitCommit: execSync('git rev-parse --short HEAD').toString().trim(), + }; + } catch { + return { + gitBranch: 'unknown', + gitCommit: 'unknown', + }; + } +} + +const gitInfo = getGitInfo(); + let config: ResolvedConfig; let server: ViteDevServer; @@ -180,12 +205,14 @@ export default defineConfig({ 'PROD', 'VITE_SENTRY_ENVIRONMENT', 'VITE_BETA_BUILD', + 'VITE_GIT_BRANCH', + 'VITE_GIT_COMMIT', ], includeTimestamp: true, includeBuildTime: true, customMetadata: { - gitBranch: () => execSync('git rev-parse --abbrev-ref HEAD').toString().trim(), - gitCommit: () => execSync('git rev-parse --short HEAD').toString().trim(), + gitBranch: () => gitInfo.gitBranch, + gitCommit: () => gitInfo.gitCommit, nodeVersion: () => process.version, }, }),