Compare commits

..

1 Commits

Author SHA1 Message Date
ngupta
517fd4cc78 test commit 2025-10-21 09:19:07 -05:00
57 changed files with 1024 additions and 1870 deletions

View File

@@ -8,5 +8,5 @@ trim_trailing_whitespace = true
indent_size = 4
indent_style = space
[*.{nix,yaml,yml}]
[*.nix]
indent_size = 2

View File

@@ -1,9 +1,10 @@
---
name: Bug report
about: Create a report to help us improve
title: '[BUG] '
title: "[BUG] "
labels: ''
assignees: ''
---
**Pre-submission Checklist**
@@ -29,11 +30,11 @@ assignees: ''
**Expected Behavior**
<!-- What you expected to happen -->
<!-- A clear and concise description of what you expected to happen -->
**Current Behavior**
<!-- What actually happened -->
<!-- A clear and concise description of what actually happened -->
**Screenshots**

View File

@@ -1,9 +1,10 @@
---
name: Feature Request
about: Suggest an idea for this project
title: '[FEATURE] '
title: "[FEATURE] "
labels: feature
assignees: ''
---
**Pre-submission Checklist**
@@ -16,14 +17,18 @@ assignees: ''
- [ ] I have reviewed the documentation to confirm this feature doesn't exist
- [ ] I have completed all sections below with detailed information
**Your Idea**
**Feature Description**
<!-- A clear and concise description of the feature you'd like to see, and how it would work -->
<!-- A clear and concise description of the feature you'd like to see -->
**Proposed Solution**
<!-- A clear and concise description of what you want to happen -->
**UI/UX Considerations**
<!-- If this feature involves UI changes (aka how it looks), please describe the visual aspects -->
<!-- If this feature involves UI changes, please describe the visual aspects -->
**Other**
**Technical Implementation Details**
<!-- Any other comments you have can go here! -->
<!-- If you have specific technical suggestions, list them here -->

View File

@@ -4,6 +4,7 @@ about: Updating Build Dependencies
title: ''
labels: build, dependencies
assignees: doprz, Razboy20
---
- [ ] Updated Nix Flake

View File

@@ -15,6 +15,7 @@ updates:
major-updates:
update-types:
- 'major'
ignore:
- dependency-name: '@crxjs/vite-plugin'
- dependency-name: '@unocss/vite'

View File

@@ -1,33 +1,43 @@
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

View File

@@ -1,18 +1,24 @@
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

View File

@@ -1,5 +1,7 @@
name: 'Chromatic'
on: [push, pull_request]
jobs:
chromatic:
runs-on: ubuntu-latest
@@ -12,8 +14,10 @@ jobs:
uses: pnpm/action-setup@v4
with:
version: 10
- name: Install dependencies
run: pnpm install
- name: Publish to Chromatic
uses: chromaui/action@latest
with:

View File

@@ -15,6 +15,7 @@ jobs:
- uses: actions/checkout@master
- name: Get file permission
run: chmod -R 777 .
- name: Install dependencies
run: npm ci
- name: Release with semantic-release

View File

@@ -1,18 +1,24 @@
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

View File

@@ -1,6 +1,8 @@
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:
@@ -8,6 +10,7 @@ on:
- reopened
- edited
- synchronize
jobs:
main:
runs-on: ubuntu-latest

7
.gitignore vendored
View File

@@ -212,9 +212,4 @@ package-lock.json
storybook-static/
package/
# Nix
result
result-*
# direnv
.direnv
.direnv/

8
.vscode/launch.json vendored
View File

@@ -6,9 +6,13 @@
"request": "launch",
"name": "Run current script",
"runtimeExecutable": "npx",
"runtimeArgs": ["tsx"],
"runtimeArgs": [
"tsx"
],
"program": "${file}",
"skipFiles": ["<node_internals>/**"]
"skipFiles": [
"<node_internals>/**"
],
}
]
}

View File

@@ -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",
}

View File

@@ -1,18 +1,3 @@
## [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
@@ -21,7 +6,6 @@
* 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))

View File

@@ -1,32 +0,0 @@
# 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 isnt an exhaustive list of things that you cant do. Rather, take it in the spirit in which its 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. Its 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 were 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 doesnt mean that theyre wrong. Dont forget that it is human to err and blaming each other doesnt 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).

View File

@@ -205,7 +205,7 @@ Special thanks to the developers and contributors behind these amazing tools and
## 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
@@ -216,3 +216,5 @@ Special thanks to the developers and contributors behind these amazing tools and
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=Longhorn-Developers/UT-Registration-Plus&type=Date" />
</picture>
</a>

View File

@@ -1,10 +0,0 @@
(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

View File

@@ -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

79
flake.lock generated
View File

@@ -1,30 +1,30 @@
{
"nodes": {
"flake-parts": {
"flake-utils": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
"systems": "systems"
},
"locked": {
"lastModified": 1767609335,
"narHash": "sha256-feveD98mQpptwrAEggBQKJTYbvwwglSbOv53uCfH9PY=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "250481aafeb741edfe23d29195671c19b36b6dca",
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1767640445,
"narHash": "sha256-UWYqmD7JFBEDBHWYcqE6s6c77pWdcU/i+bwD6XxMb8A=",
"lastModified": 1759831965,
"narHash": "sha256-vgPm2xjOmKdZ0xKA6yLXPJpjOtQPHfaZDRtH+47XEBo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9f0c42f8bc7151b8e7e5840fb3bd454ad850d8c5",
"rev": "c9b6fb798541223bbb396d287d16f43520250518",
"type": "github"
},
"original": {
@@ -34,59 +34,24 @@
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1765674936,
"narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85",
"type": "github"
},
"original": {
"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"
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": "nixpkgs_2"
},
"systems": {
"locked": {
"lastModified": 1767468822,
"narHash": "sha256-MpffQxHxmjVKMiQd0Tg2IM/bSjjdQAM+NDcX6yxj7rE=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "d56486eb9493ad9c4777c65932618e9c2d0468fc",
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}

View File

@@ -1,33 +1,43 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
treefmt-nix.url = "github:numtide/treefmt-nix";
flake-utils.url = "github:numtide/flake-utils";
};
outputs =
inputs@{ flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = inputs.nixpkgs.lib.systems.flakeExposed;
imports = [
./nix/packages.nix
./nix/devShells.nix
./nix/treefmt.nix
];
perSystem =
{ system, ... }:
{
_module.args.pkgs = import inputs.nixpkgs {
inherit system;
overlays = [
(final: prev: {
nodejs = prev.nodejs_20; # v20.19.5
})
self,
nixpkgs,
flake-utils,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = (import nixpkgs { inherit system; });
commonPackages = with pkgs; [
nodejs_20 # v20.19.5
pnpm_10 # v10.18.0
];
config = { };
};
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;
};
devShells.full = pkgs.mkShell {
name = "utrp-dev-full";
buildInputs = commonPackages ++ additionalPackages;
};
}
);
}

View File

@@ -1,30 +0,0 @@
{
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;
};
};
}

View File

@@ -1,51 +0,0 @@
{
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;
};
})

View File

@@ -1,40 +0,0 @@
{ 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;
};
};
}

View File

@@ -1,63 +0,0 @@
{ 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
'';
};
};
}

View File

@@ -1,7 +1,7 @@
{
"name": "ut-registration-plus",
"displayName": "UT Registration Plus",
"version": "2.3.0",
"version": "2.2.2",
"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,9 +39,6 @@
"@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",
@@ -139,7 +136,7 @@
"gulp-zip": "^6.1.0",
"path": "^0.12.7",
"postcss": "^8.5.3",
"prettier": "3.6.2",
"prettier": "^3.5.2",
"react-dev-utils": "^12.0.1",
"semantic-release": "^24.2.3",
"storybook": "^8.6.0",
@@ -162,10 +159,7 @@
},
"overrides": {
"es-module-lexer": "^1.5.4"
},
"onlyBuiltDependencies": [
"@tsparticles/engine"
]
}
},
"volta": {
"node": "20.19.4",

496
pnpm-lock.yaml generated
View File

@@ -49,15 +49,6 @@ 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))
@@ -163,25 +154,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.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)
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)
'@storybook/addon-essentials':
specifier: ^8.6.0
version: 8.6.0(@types/react@18.3.18)(storybook@8.6.0(prettier@3.6.2))
version: 8.6.0(@types/react@18.3.18)(storybook@8.6.0(prettier@3.5.2))
'@storybook/addon-links':
specifier: ^8.6.0
version: 8.6.0(react@18.3.1)(storybook@8.6.0(prettier@3.6.2))
version: 8.6.0(react@18.3.1)(storybook@8.6.0(prettier@3.5.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.6.2))
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))
'@storybook/react':
specifier: ^8.6.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)(storybook@8.6.0(prettier@3.6.2))(typescript@5.7.3)
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)
'@storybook/react-vite':
specifier: ^8.6.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))
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))
'@storybook/test':
specifier: ^8.6.0
version: 8.6.0(storybook@8.6.0(prettier@3.6.2))
version: 8.6.0(storybook@8.6.0(prettier@3.5.2))
'@svgr/core':
specifier: ^8.1.0
version: 8.1.0(typescript@5.7.3)
@@ -307,7 +298,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.6.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.5.2)
eslint-plugin-react:
specifier: ^7.37.4
version: 7.37.4(eslint@8.57.1)
@@ -345,8 +336,8 @@ importers:
specifier: ^8.5.3
version: 8.5.3
prettier:
specifier: 3.6.2
version: 3.6.2
specifier: ^3.5.2
version: 3.5.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))
@@ -355,7 +346,7 @@ importers:
version: 24.2.3(typescript@5.7.3)
storybook:
specifier: ^8.6.0
version: 8.6.0(prettier@3.6.2)
version: 8.6.0(prettier@3.5.2)
typescript:
specifier: ^5.7.3
version: 5.7.3
@@ -2084,121 +2075,6 @@ 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==}
@@ -6031,8 +5907,8 @@ packages:
resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
engines: {node: '>=6.0.0'}
prettier@3.6.2:
resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==}
prettier@3.5.2:
resolution: {integrity: sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==}
engines: {node: '>=14'}
hasBin: true
@@ -8549,126 +8425,126 @@ snapshots:
'@sindresorhus/merge-streams@4.0.0': {}
'@storybook/addon-actions@8.6.0(storybook@8.6.0(prettier@3.6.2))':
'@storybook/addon-actions@8.6.0(storybook@8.6.0(prettier@3.5.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.6.2)
storybook: 8.6.0(prettier@3.5.2)
uuid: 9.0.1
'@storybook/addon-backgrounds@8.6.0(storybook@8.6.0(prettier@3.6.2))':
'@storybook/addon-backgrounds@8.6.0(storybook@8.6.0(prettier@3.5.2))':
dependencies:
'@storybook/global': 5.0.0
memoizerific: 1.11.3
storybook: 8.6.0(prettier@3.6.2)
storybook: 8.6.0(prettier@3.5.2)
ts-dedent: 2.2.0
'@storybook/addon-controls@8.6.0(storybook@8.6.0(prettier@3.6.2))':
'@storybook/addon-controls@8.6.0(storybook@8.6.0(prettier@3.5.2))':
dependencies:
'@storybook/global': 5.0.0
dequal: 2.0.3
storybook: 8.6.0(prettier@3.6.2)
storybook: 8.6.0(prettier@3.5.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.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-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)':
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.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))
'@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: 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.6.2))':
'@storybook/addon-docs@8.6.0(@types/react@18.3.18)(storybook@8.6.0(prettier@3.5.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.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))
'@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))
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
storybook: 8.6.0(prettier@3.6.2)
storybook: 8.6.0(prettier@3.5.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.6.2))':
'@storybook/addon-essentials@8.6.0(@types/react@18.3.18)(storybook@8.6.0(prettier@3.5.2))':
dependencies:
'@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)
'@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)
ts-dedent: 2.2.0
transitivePeerDependencies:
- '@types/react'
'@storybook/addon-highlight@8.6.0(storybook@8.6.0(prettier@3.6.2))':
'@storybook/addon-highlight@8.6.0(storybook@8.6.0(prettier@3.5.2))':
dependencies:
'@storybook/global': 5.0.0
storybook: 8.6.0(prettier@3.6.2)
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))':
'@storybook/addon-links@8.6.0(react@18.3.1)(storybook@8.6.0(prettier@3.5.2))':
dependencies:
'@storybook/global': 5.0.0
storybook: 8.6.0(prettier@3.6.2)
storybook: 8.6.0(prettier@3.5.2)
ts-dedent: 2.2.0
optionalDependencies:
react: 18.3.1
'@storybook/addon-measure@8.6.0(storybook@8.6.0(prettier@3.6.2))':
'@storybook/addon-measure@8.6.0(storybook@8.6.0(prettier@3.5.2))':
dependencies:
'@storybook/global': 5.0.0
storybook: 8.6.0(prettier@3.6.2)
storybook: 8.6.0(prettier@3.5.2)
tiny-invariant: 1.3.3
'@storybook/addon-outline@8.6.0(storybook@8.6.0(prettier@3.6.2))':
'@storybook/addon-outline@8.6.0(storybook@8.6.0(prettier@3.5.2))':
dependencies:
'@storybook/global': 5.0.0
storybook: 8.6.0(prettier@3.6.2)
storybook: 8.6.0(prettier@3.5.2)
ts-dedent: 2.2.0
'@storybook/addon-toolbars@8.6.0(storybook@8.6.0(prettier@3.6.2))':
'@storybook/addon-toolbars@8.6.0(storybook@8.6.0(prettier@3.5.2))':
dependencies:
storybook: 8.6.0(prettier@3.6.2)
storybook: 8.6.0(prettier@3.5.2)
'@storybook/addon-viewport@8.6.0(storybook@8.6.0(prettier@3.6.2))':
'@storybook/addon-viewport@8.6.0(storybook@8.6.0(prettier@3.5.2))':
dependencies:
memoizerific: 1.11.3
storybook: 8.6.0(prettier@3.6.2)
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/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))':
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.6.2)
storybook: 8.6.0(prettier@3.5.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.6.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.5.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.6.2))
'@storybook/csf-plugin': 8.6.0(storybook@8.6.0(prettier@3.5.2))
browser-assert: 1.2.1
storybook: 8.6.0(prettier@3.6.2)
storybook: 8.6.0(prettier@3.5.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.6.2))':
'@storybook/components@8.6.0(storybook@8.6.0(prettier@3.5.2))':
dependencies:
storybook: 8.6.0(prettier@3.6.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))':
'@storybook/core@8.6.0(prettier@3.5.2)(storybook@8.6.0(prettier@3.5.2))':
dependencies:
'@storybook/theming': 8.6.0(storybook@8.6.0(prettier@3.6.2))
'@storybook/theming': 8.6.0(storybook@8.6.0(prettier@3.5.2))
better-opn: 3.0.2
browser-assert: 1.2.1
esbuild: 0.25.10
@@ -8680,16 +8556,16 @@ snapshots:
util: 0.12.5
ws: 8.18.1
optionalDependencies:
prettier: 3.6.2
prettier: 3.5.2
transitivePeerDependencies:
- bufferutil
- storybook
- supports-color
- utf-8-validate
'@storybook/csf-plugin@8.6.0(storybook@8.6.0(prettier@3.6.2))':
'@storybook/csf-plugin@8.6.0(storybook@8.6.0(prettier@3.5.2))':
dependencies:
storybook: 8.6.0(prettier@3.6.2)
storybook: 8.6.0(prettier@3.5.2)
unplugin: 1.16.1
'@storybook/csf@0.0.1':
@@ -8703,77 +8579,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.6.2))':
'@storybook/instrumenter@8.6.0(storybook@8.6.0(prettier@3.5.2))':
dependencies:
'@storybook/global': 5.0.0
'@vitest/utils': 2.1.9
storybook: 8.6.0(prettier@3.6.2)
storybook: 8.6.0(prettier@3.5.2)
'@storybook/manager-api@8.6.0(storybook@8.6.0(prettier@3.6.2))':
'@storybook/manager-api@8.6.0(storybook@8.6.0(prettier@3.5.2))':
dependencies:
storybook: 8.6.0(prettier@3.6.2)
storybook: 8.6.0(prettier@3.5.2)
'@storybook/preview-api@8.6.0(storybook@8.6.0(prettier@3.6.2))':
'@storybook/preview-api@8.6.0(storybook@8.6.0(prettier@3.5.2))':
dependencies:
storybook: 8.6.0(prettier@3.6.2)
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))':
'@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))':
dependencies:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
storybook: 8.6.0(prettier@3.6.2)
storybook: 8.6.0(prettier@3.5.2)
'@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))':
'@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))':
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.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)
'@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)
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.6.2)
storybook: 8.6.0(prettier@3.5.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.6.2))
'@storybook/test': 8.6.0(storybook@8.6.0(prettier@3.5.2))
transitivePeerDependencies:
- rollup
- supports-color
- typescript
'@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)':
'@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)':
dependencies:
'@storybook/components': 8.6.0(storybook@8.6.0(prettier@3.6.2))
'@storybook/components': 8.6.0(storybook@8.6.0(prettier@3.5.2))
'@storybook/global': 5.0.0
'@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))
'@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))
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
storybook: 8.6.0(prettier@3.6.2)
storybook: 8.6.0(prettier@3.5.2)
optionalDependencies:
'@storybook/test': 8.6.0(storybook@8.6.0(prettier@3.6.2))
'@storybook/test': 8.6.0(storybook@8.6.0(prettier@3.5.2))
typescript: 5.7.3
'@storybook/test@8.6.0(storybook@8.6.0(prettier@3.6.2))':
'@storybook/test@8.6.0(storybook@8.6.0(prettier@3.5.2))':
dependencies:
'@storybook/global': 5.0.0
'@storybook/instrumenter': 8.6.0(storybook@8.6.0(prettier@3.6.2))
'@storybook/instrumenter': 8.6.0(storybook@8.6.0(prettier@3.5.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.6.2)
storybook: 8.6.0(prettier@3.5.2)
'@storybook/theming@8.6.0(storybook@8.6.0(prettier@3.6.2))':
'@storybook/theming@8.6.0(storybook@8.6.0(prettier@3.5.2))':
dependencies:
storybook: 8.6.0(prettier@3.6.2)
storybook: 8.6.0(prettier@3.5.2)
'@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.26.9)':
dependencies:
@@ -8944,188 +8820,6 @@ 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':
@@ -11316,10 +11010,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.6.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.5.2):
dependencies:
eslint: 8.57.1
prettier: 3.6.2
prettier: 3.5.2
prettier-linter-helpers: 1.0.0
synckit: 0.9.2
optionalDependencies:
@@ -13687,7 +13381,7 @@ snapshots:
dependencies:
fast-diff: 1.3.0
prettier@3.6.2: {}
prettier@3.5.2: {}
pretty-format@27.5.1:
dependencies:
@@ -14344,11 +14038,11 @@ snapshots:
es-errors: 1.3.0
internal-slot: 1.1.0
storybook@8.6.0(prettier@3.6.2):
storybook@8.6.0(prettier@3.5.2):
dependencies:
'@storybook/core': 8.6.0(prettier@3.6.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))
optionalDependencies:
prettier: 3.6.2
prettier: 3.5.2
transitivePeerDependencies:
- bufferutil
- supports-color

View File

@@ -1,10 +0,0 @@
(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

View File

@@ -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);
}
}
}

View File

@@ -9,7 +9,6 @@ import onUpdate from './events/onUpdate';
import browserActionHandler from './handler/browserActionHandler';
import calendarBackgroundHandler from './handler/calendarBackgroundHandler';
import CESHandler from './handler/CESHandler';
import gitHubStatsHandler from './handler/gitHubStatsHandler';
import tabManagementHandler from './handler/tabManagementHandler';
import userScheduleHandler from './handler/userScheduleHandler';
@@ -53,7 +52,6 @@ const messageListener = new MessageListener<BACKGROUND_MESSAGES>({
...userScheduleHandler,
...CESHandler,
...calendarBackgroundHandler,
...gitHubStatsHandler,
});
messageListener.listen();

View File

@@ -1,31 +0,0 @@
import type GitHubStatsMessages from '@shared/messages/GitHubStatsMessages';
import { GitHubStatsService } from '@views/lib/getGitHubStats';
import type { MessageHandler } from 'chrome-extension-toolkit';
const gitHubStatsService = new GitHubStatsService();
/**
* Handler for GitHub stats related messages
*/
const gitHubStatsHandler: MessageHandler<GitHubStatsMessages> = {
async fetchGitHubStats({ data, sendResponse }) {
try {
const includeMergedPRs = data ?? false;
const stats = await gitHubStatsService.fetchGitHubStats({ includeMergedPRs });
sendResponse(stats);
} catch (error) {
console.error('Error fetching GitHub stats in background:', error);
sendResponse({
adminGitHubStats: {},
userGitHubStats: {},
contributors: [],
names: {},
dataFetched: new Date(),
lastUpdated: new Date(),
isCached: false,
});
}
},
};
export default gitHubStatsHandler;

View File

@@ -1,14 +0,0 @@
import type { GitHubStatsService } from '@views/lib/getGitHubStats';
/* eslint-disable jsdoc/require-jsdoc */
export type GitHubStatsResult = Awaited<ReturnType<GitHubStatsService['fetchGitHubStats']>>;
export default interface GitHubStatsMessages {
/**
* Fetch GitHub statistics for all contributors
* @param includeMergedPRs - Whether to include merged PR counts (optional, default: false)
* @returns GitHub stats including commits, lines added/deleted, and optionally merged PRs
*/
fetchGitHubStats: (includeMergedPRs?: boolean) => GitHubStatsResult;
}

View File

@@ -3,7 +3,6 @@ import { createMessenger } from 'chrome-extension-toolkit';
import type BrowserActionMessages from './BrowserActionMessages';
import type { CalendarBackgroundMessages, CalendarTabMessages } from './CalendarMessages';
import type CESMessage from './CESMessage';
import type GitHubStatsMessages from './GitHubStatsMessages';
import type TabInfoMessages from './TabInfoMessages';
import type TabManagementMessages from './TabManagementMessages';
import type { UserScheduleMessages } from './UserScheduleMessages';
@@ -15,8 +14,7 @@ export type BACKGROUND_MESSAGES = BrowserActionMessages &
TabManagementMessages &
UserScheduleMessages &
CESMessage &
CalendarBackgroundMessages &
GitHubStatsMessages;
CalendarBackgroundMessages;
/**
* This is a type with all the message definitions that can be sent TO specific tabs

View File

@@ -24,8 +24,6 @@ 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<IOptionsStore>({
@@ -36,7 +34,6 @@ export const OptionsStore = createSyncStore<IOptionsStore>({
alwaysOpenCalendarInNewTab: false,
showCalendarSidebar: true,
showUTDiningPromo: true,
allowMoreSchedules: false,
});
/**
@@ -53,7 +50,6 @@ 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

View File

@@ -1,19 +0,0 @@
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<typeof CalendarFooter>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {},
};

View File

@@ -114,12 +114,11 @@ export default function Calendar(): ReactNode {
<LargeLogo />
<Button
variant='minimal'
size='small'
color='theme-black'
onClick={() => {
setShowSidebar(!showSidebar);
}}
className='screenshot:hidden'
className='h-fit screenshot:hidden !p-0'
icon={Sidebar}
/>
</div>

View File

@@ -124,7 +124,9 @@ export default function CourseCellColorPicker({ defaultColor }: CourseCellColorP
<>
<Divider orientation='horizontal' size='100%' className='my-1' />
<div className='grid grid-cols-6 gap-1'>
{colorPatchColors.get(selectedBaseColor)?.map(shadeColor => (
{colorPatchColors
.get(selectedBaseColor)
?.map(shadeColor => (
<ColorPatch
key={shadeColor}
color={shadeColor}

View File

@@ -58,7 +58,13 @@ export default function CalendarFooter(): JSX.Element {
))}
</div>
<div>
<Button variant='minimal' size='small' icon={GearSix} color='ut-black' onClick={handleOpenOptions} />
<Button
className='h-fit w-fit !p-0'
variant='minimal'
icon={GearSix}
color='ut-black'
onClick={handleOpenOptions}
/>
</div>
</footer>
);

View File

@@ -28,15 +28,14 @@ export default function CalendarHeader({ sidebarOpen, onSidebarToggle }: Calenda
return (
<div
style={{ scrollbarGutter: 'stable' }}
className='sticky left-0 right-0 top-0 z-10 min-h-[85px] flex items-center gap-5 overflow-x-auto overflow-y-hidden bg-white pl-spacing-7 pt-spacing-5'
className='sticky left-0 right-0 top-0 z-10 min-h-[85px] flex items-center gap-5 overflow-x-scroll overflow-y-hidden bg-white pl-spacing-7 pt-spacing-5'
>
{!sidebarOpen && (
<Button
variant='minimal'
size='small'
color='theme-black'
onClick={onSidebarToggle}
className='screenshot:hidden'
className='h-fit w-fit screenshot:hidden !p-0'
icon={Sidebar}
/>
)}

View File

@@ -26,16 +26,15 @@ export function CalendarSchedules() {
};
return (
<div className='min-w-full w-0 flex flex-col items-center gap-y-spacing-2'>
<div className='m0 w-full flex items-center justify-between'>
<div className='min-w-full w-0 flex flex-col items-center gap-y-spacing-3'>
<div className='m0 w-full flex justify-between'>
<Text variant='h3' className='text-nowrap text-theme-black'>
MY SCHEDULES
</Text>
<Button
variant='minimal'
size='small'
color='theme-black'
className='!p-0 btn'
className='h-fit w-fit !p-0 btn'
onClick={handleAddSchedule}
icon={Plus}
/>

View File

@@ -14,21 +14,17 @@ interface LinkItem {
}
const links: LinkItem[] = [
// {
// text: "Fall '25 Course Schedule",
// url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20259/',
// },
// {
// text: 'Course Schedule Archives',
// url: 'https://registrar.utexas.edu/schedules/archive',
// },
{
text: "Spring '26 Course Schedule",
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20262/',
},
{
text: 'Other Course Schedules',
url: 'https://registrar.utexas.edu/schedules',
text: "Fall '25 Course Schedule",
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20259/',
},
{
text: 'Course Schedule Archives',
url: 'https://registrar.utexas.edu/schedules/archive',
},
{
text: 'My Degree Audit (IDA)',

View File

@@ -444,8 +444,7 @@ export const calculateCourseCellColumns = (dayCells: CalendarGridCourse[]) => {
typeof cell.calendarGridPoint.startIndex === 'number' &&
cell.calendarGridPoint.startIndex >= 0
)
.slice()
.sort((a, b) => a.calendarGridPoint.startIndex - b.calendarGridPoint.startIndex);
.toSorted((a, b) => a.calendarGridPoint.startIndex - b.calendarGridPoint.startIndex);
// Initialize metadata
for (const cell of cells) {

View File

@@ -4,7 +4,7 @@ import type { ThemeColor } from '@shared/types/ThemeColors';
import { getThemeColorHexByName, getThemeColorRgbByName } from '@shared/util/themeColors';
import Text from '@views/components/common/Text/Text';
import clsx from 'clsx';
import React, { useEffect, useRef, useState } from 'react';
import React from 'react';
interface Props {
className?: string;
@@ -21,8 +21,9 @@ interface Props {
}
/**
* A reusable input button component that follows Button.tsx consistency.
* Now supports drag-and-drop file uploads (issue #446).
* A reusable input button component that follows the Button.tsx consistency
*
* @returns
*/
export default function FileUpload({
className,
@@ -42,74 +43,22 @@ export default function FileUpload({
const isIconOnly = !children && !!icon;
const colorHex = getThemeColorHexByName(color);
const colorRgb = getThemeColorRgbByName(color)?.join(' ');
const inputRef = useRef<HTMLInputElement>(null);
const [isDragging, setIsDragging] = useState(false);
// Convert accept array to comma-separated list
// Convert accept to a comma-separated string if it's an array
const acceptValue = Array.isArray(accept) ? accept.join(',') : accept;
// --- Prevent Chrome from opening the file on drop anywhere else ---
useEffect(() => {
const preventDefault = (e: DragEvent) => {
e.preventDefault();
e.stopPropagation();
};
window.addEventListener('dragover', preventDefault);
window.addEventListener('drop', preventDefault);
return () => {
window.removeEventListener('dragover', preventDefault);
window.removeEventListener('drop', preventDefault);
};
}, []);
// ------------------------------------------------------------------
// --- Local drag and drop handlers for this button only -------------
const handleDrop = (event: React.DragEvent<HTMLLabelElement>) => {
event.preventDefault();
event.stopPropagation();
setIsDragging(false);
if (disabled) return;
const file = event.dataTransfer.files?.[0];
if (file && inputRef.current && onChange) {
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
inputRef.current.files = dataTransfer.files;
// Trigger change event manually
onChange({ target: inputRef.current } as React.ChangeEvent<HTMLInputElement>);
}
};
const handleDragOver = (event: React.DragEvent<HTMLLabelElement>) => {
event.preventDefault();
event.stopPropagation();
if (!disabled) setIsDragging(true);
};
const handleDragLeave = (event: React.DragEvent<HTMLLabelElement>) => {
event.preventDefault();
event.stopPropagation();
setIsDragging(false);
};
// ------------------------------------------------------------------
return (
<label
onDrop={handleDrop}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
style={{
style={
{
...style,
color: disabled ? 'ut-gray' : colorHex,
backgroundColor: `rgb(${colorRgb} / var(--un-bg-opacity))`,
}}
backgroundColor: `rgb(${colorRgb} / var(--un-bg-opacity)`,
} satisfies React.CSSProperties
}
className={clsx(
'btn transition-colors select-none',
'btn',
{
'ring-2 ring-offset-2 ring-blue-400': isDragging && !disabled,
'text-white! bg-opacity-100 hover:enabled:shadow-md active:enabled:shadow-sm shadow-black/20':
variant === 'filled',
'bg-opacity-0 border-current hover:enabled:bg-opacity-8 border stroke-width-[1px]':
@@ -121,7 +70,6 @@ export default function FileUpload({
'h-[35px] w-[35px] p-spacing-2': size === 'small' && isIconOnly,
'h-6 p-spacing-2': size === 'mini' && !isIconOnly,
'h-6 w-6 p-0': size === 'mini' && isIconOnly,
'opacity-60 cursor-not-allowed': disabled,
},
className
)}
@@ -137,7 +85,6 @@ export default function FileUpload({
</Text>
)}
<input
ref={inputRef}
type='file'
{...(accept ? { accept: acceptValue } : {})}
className='hidden'

View File

@@ -34,7 +34,7 @@ export default function Link(props: PropsWithChildren<Props>): JSX.Element {
tabIndex={isDisabled ? -1 : 0}
className={clsx(
{
'underline cursor-pointer p-2': !isDisabled,
'underline cursor-pointer': !isDisabled,
'cursor-not-allowed color-ut-gray': isDisabled,
},
className

View File

@@ -175,11 +175,11 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
description: (
<>
The section you&apos;re adding is for{' '}
<span className='whitespace-nowrap text-ut-burntorange'>
<span className='text-ut-burntorange whitespace-nowrap'>
{course.semester.season} {course.semester.year}
</span>
, but your current schedule contains sections in{' '}
<span className='whitespace-nowrap text-ut-burntorange'>{activeSemesters}</span>. Mixing
<span className='text-ut-burntorange whitespace-nowrap'>{activeSemesters}</span>. Mixing
semesters in one schedule may cause confusion.
</>
),

View File

@@ -1,197 +0,0 @@
import { Trash } from '@phosphor-icons/react';
import { OptionsStore } from '@shared/storage/OptionsStore';
import MIMEType from '@shared/types/MIMEType';
import type { UserSchedule } from '@shared/types/UserSchedule';
import { handleExportJson } from '@views/components/calendar/utils';
import { Button } from '@views/components/common/Button';
import Divider from '@views/components/common/Divider';
import SwitchButton from '@views/components/common/SwitchButton';
import Text from '@views/components/common/Text/Text';
import clsx from 'clsx';
import React from 'react';
import FileUpload from '../common/FileUpload';
import { DISPLAY_PREVIEWS, PREVIEW_SECTION_DIV_CLASSNAME } from './constants';
import Preview from './Preview';
interface AdvancedSettingsProps {
highlightConflicts: boolean;
setHighlightConflicts: (value: boolean) => void;
loadAllCourses: boolean;
setLoadAllCourses: (value: boolean) => void;
increaseScheduleLimit: boolean;
setIncreaseScheduleLimit: (value: boolean) => void;
calendarNewTab: boolean;
setCalendarNewTab: (value: boolean) => void;
activeSchedule: UserSchedule;
handleEraseAll: () => void;
handleImportClick: (event: React.ChangeEvent<HTMLInputElement>) => Promise<void>;
}
/**
* Settings section component for advanced settings
*/
export const AdvancedSettings: React.FC<AdvancedSettingsProps> = ({
highlightConflicts,
setHighlightConflicts,
loadAllCourses,
setLoadAllCourses,
increaseScheduleLimit,
setIncreaseScheduleLimit,
calendarNewTab,
setCalendarNewTab,
activeSchedule,
handleEraseAll,
handleImportClick,
}) => (
<section className='mb-8'>
<h2 className='mb-4 text-xl text-ut-black font-semibold'>ADVANCED SETTINGS</h2>
<div className='flex space-x-4'>
<div className={PREVIEW_SECTION_DIV_CLASSNAME}>
<div className='flex items-center justify-between'>
<div className='max-w-xs'>
<Text variant='h4' className='text-ut-burntorange font-semibold'>
Export Current Schedule
</Text>
<p className='text-sm text-gray-600'>Backup your active schedule to a portable file</p>
</div>
<Button
variant='outline'
color='ut-burntorange'
onClick={() => handleExportJson(activeSchedule.id)}
>
Export
</Button>
</div>
<Divider size='auto' orientation='horizontal' />
<div className='flex items-center justify-between'>
<div className='max-w-xs'>
<Text variant='h4' className='text-ut-burntorange font-semibold'>
Import Schedule
</Text>
<p className='text-sm text-gray-600'>Import from a schedule file</p>
</div>
<FileUpload
variant='filled'
color='ut-burntorange'
onChange={handleImportClick}
accept={MIMEType.JSON}
>
Import Schedule
</FileUpload>
</div>
<Divider size='auto' orientation='horizontal' />
<div className='flex items-center justify-between'>
<div className='max-w-xs'>
<Text variant='h4' className='text-ut-burntorange font-semibold'>
Course Conflict Highlight
</Text>
<p className='text-sm text-gray-600'>
Adds a red strikethrough to courses that have conflicting times.
</p>
</div>
<SwitchButton
isChecked={highlightConflicts}
onChange={() => {
setHighlightConflicts(!highlightConflicts);
OptionsStore.set('enableHighlightConflicts', !highlightConflicts);
}}
/>
</div>
<Divider size='auto' orientation='horizontal' />
<div className='flex items-center justify-between'>
<div className='max-w-xs'>
<Text variant='h4' className='text-ut-burntorange font-semibold'>
Load All Courses in Course Schedule
</Text>
<p className='text-sm text-gray-600'>
Loads all courses in the Course Schedule site by scrolling, instead of using next/prev page
buttons.
</p>
</div>
<SwitchButton
isChecked={loadAllCourses}
onChange={() => {
setLoadAllCourses(!loadAllCourses);
OptionsStore.set('enableScrollToLoad', !loadAllCourses);
}}
/>
</div>
<Divider size='auto' orientation='horizontal' />
<div className='flex items-center justify-between'>
<div className='max-w-xs'>
<Text variant='h4' className='text-ut-burntorange font-semibold'>
Allow more than 10 schedules
</Text>
<p className='text-sm text-gray-600'>
Allow bypassing the 10-schedule limit. Intended for advisors or staff who need to create
many schedules on behalf of students.
</p>
</div>
<SwitchButton
isChecked={increaseScheduleLimit}
onChange={() => {
setIncreaseScheduleLimit(!increaseScheduleLimit);
OptionsStore.set('allowMoreSchedules', !increaseScheduleLimit);
}}
/>
</div>
<Divider size='auto' orientation='horizontal' />
<div className='flex items-center justify-between'>
<div className='max-w-xs'>
<Text variant='h4' className='text-ut-burntorange font-semibold'>
Always Open Calendar in New Tab
</Text>
<p className='text-sm text-gray-600'>
Always opens the calendar view in a new tab when navigating to the calendar page. May
prevent issues where the calendar refuses to open.
</p>
</div>
<SwitchButton
isChecked={calendarNewTab}
onChange={() => {
setCalendarNewTab(!calendarNewTab);
OptionsStore.set('alwaysOpenCalendarInNewTab', !calendarNewTab);
}}
/>
</div>
<Divider size='auto' orientation='horizontal' />
<div className='flex items-center justify-between'>
<div className='max-w-xs'>
<Text variant='h4' className='text-ut-burntorange font-semibold'>
Reset All Data
</Text>
<p className='text-sm text-gray-600'>Erases all schedules and courses you have.</p>
</div>
<Button variant='outline' color='theme-red' icon={Trash} onClick={handleEraseAll}>
Erase All
</Button>
</div>
</div>
{DISPLAY_PREVIEWS && (
<Preview>
<Text
variant='h2-course'
className={clsx('text-center text-theme-red font-normal', {
'line-through': highlightConflicts,
})}
>
01234 MWF 10:00 AM - 11:00 AM UTC 1.234
</Text>
</Preview>
)}
</div>
</section>
);

View File

@@ -1,54 +0,0 @@
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<ContributorCardProps> = ({
name,
githubUsername,
roles,
stats,
showStats,
includeMergedPRs,
}) => (
<div className='border border-gray-300 rounded bg-ut-gray/10 p-4'>
<Text
variant='p'
className='text-ut-burntorange font-semibold hover:cursor-pointer'
onClick={() => window.open(`https://github.com/${githubUsername}`, '_blank')}
>
{name}
</Text>
{roles.map(role => (
<p key={`${githubUsername}-${role}`} className='text-sm text-gray-600'>
{role}
</p>
))}
{showStats && stats && (
<div className='mt-2'>
<p className='text-xs text-gray-500'>GitHub Stats (UTRP repo):</p>
{includeMergedPRs && stats.mergedPRs !== undefined && (
<p className='text-xs'>Merged PRs: {stats.mergedPRs}</p>
)}
<p className='text-xs'>Commits: {stats.commits}</p>
<p className='text-xs text-ut-green'>{stats.linesAdded}++</p>
<p className='text-xs text-theme-red'>{stats.linesDeleted}--</p>
</div>
)}
</div>
);

View File

@@ -1,68 +1,114 @@
// Pages
// import addCourse from '@pages/background/lib/addCourse';
import { addCourseByURL } from '@pages/background/lib/addCourseByURL';
import { deleteAllSchedules } from '@pages/background/lib/deleteSchedule';
import importSchedule from '@pages/background/lib/importSchedule';
import { CalendarDots } from '@phosphor-icons/react';
// Shared
import { CalendarDots, Trash } from '@phosphor-icons/react';
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 Particles from '@tsparticles/react';
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 { 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';
// Misc
import React, { useCallback, useEffect, useMemo, useState } from 'react';
// import { SiteSupport } from '@views/lib/getSiteSupport';
import clsx from 'clsx';
import React, { useCallback, useEffect, 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 { AdvancedSettings } from './AdvancedSettings';
import { DEV_MODE_CLICK_TARGET, INCLUDE_MERGED_PRS, STATS_TOGGLE_KEY } from './constants';
import { ContributorCard } from './ContributorCard';
// import RefreshIcon from '~icons/material-symbols/refresh';
import DevMode from './DevMode';
import { useBirthdayCelebration } from './useBirthdayCelebration';
import { useDevMode } from './useDevMode';
import Preview from './Preview';
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';
/**
* Main Settings Component for managing user settings and preferences.
* 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.
*
* @returns The Settings component.
*/
export default function Settings(): JSX.Element {
const gitHubStatsService = useMemo(() => new GitHubStatsService(), []);
const [_enableCourseStatusChips, setEnableCourseStatusChips] = useState<boolean>(false);
// const [_showTimeLocation, setShowTimeLocation] = useState<boolean>(false);
const [highlightConflicts, setHighlightConflicts] = useState<boolean>(false);
const [loadAllCourses, setLoadAllCourses] = useState<boolean>(false);
const [_enableDataRefreshing, setEnableDataRefreshing] = useState<boolean>(false);
const [calendarNewTab, setCalendarNewTab] = useState<boolean>(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 showMigrationDialog = useMigrationDialog();
// Toggle GitHub stats when the user presses the 'S' key
const [showGitHubStats, setShowGitHubStats] = useState<boolean>(false);
const [githubStats, setGitHubStats] = useState<Awaited<
ReturnType<typeof gitHubStatsService.fetchGitHubStats>
> | null>(null);
const [isDeveloper, setIsDeveloper] = useState(false);
const [activeSchedule] = useSchedules();
// const [isRefreshing, setIsRefreshing] = useState<boolean>(false);
const [isDeveloper, setIsDeveloper] = useState<boolean>(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 {
@@ -74,12 +120,19 @@ export default function Settings(): JSX.Element {
};
const initAndSetSettings = async () => {
const { enableHighlightConflicts, enableScrollToLoad, alwaysOpenCalendarInNewTab, allowMoreSchedules } =
await initSettings();
const {
enableCourseStatusChips,
enableHighlightConflicts,
enableScrollToLoad,
enableDataRefreshing,
alwaysOpenCalendarInNewTab,
} = await initSettings();
setEnableCourseStatusChips(enableCourseStatusChips);
// setShowTimeLocation(enableTimeAndLocationInPopup);
setHighlightConflicts(enableHighlightConflicts);
setLoadAllCourses(enableScrollToLoad);
setEnableDataRefreshing(enableDataRefreshing);
setCalendarNewTab(alwaysOpenCalendarInNewTab);
setIncreaseScheduleLimit(allowMoreSchedules);
};
const initDS = async () => {
@@ -87,50 +140,68 @@ 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 = useCallback(() => {
const handleEraseAll = () => {
showDialog({
title: 'Erase All Course/Schedule Data',
description: (
@@ -157,45 +228,34 @@ export default function Settings(): JSX.Element {
</Button>
),
});
}, [showDialog]);
};
const handleImportClick = useCallback(async (event: React.ChangeEvent<HTMLInputElement>) => {
const handleImportClick = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
if (file) {
const reader = new FileReader();
reader.onload = async e => {
try {
const text = await file.text();
const data = JSON.parse(text);
await importSchedule(data);
alert('Schedule imported successfully.');
const result = e.target?.result as string;
const jsonObject = JSON.parse(result);
await importSchedule(jsonObject);
} catch (error) {
console.error('Error importing schedule:', error);
alert('Failed to import schedule. Make sure the file is a valid .json format.');
console.error('Invalid import file!');
}
}, []);
};
reader.readAsText(file);
}
};
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 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 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]);
const [devMode, toggleDevMode] = useDevMode(10);
if (devMode) {
DevStore.set('isDeveloper', true);
@@ -203,32 +263,13 @@ export default function Settings(): JSX.Element {
}
return (
<div className='relative'>
{particlesInit && showParticles && (
<Particles
id='birthday-particles'
options={particlesOptions}
className='pointer-events-none absolute inset-0 z-50'
/>
)}
<div>
<header className='flex items-center gap-5 overflow-x-auto overflow-y-hidden border-b border-ut-offwhite px-7 py-4 md:overflow-x-hidden'>
<LargeLogo />
<Divider className='mx-2 self-center md:mx-4' size='2.5rem' orientation='vertical' />
<div className='flex flex-1 items-center gap-2'>
<Text variant='h1' className='text-ut-burntorange normal-case'>
<Text variant='h1' className='flex-1 text-ut-burntorange normal-case!'>
Settings and Credits
</Text>
{isBirthday && (
<span
onClick={triggerCelebration}
className='cursor-pointer px-4 text-sm text-ut-burntorange transition-transform hover:scale-110'
title='Click to celebrate!'
>
🎉 Happy Birthday LHD! 🎉
</span>
)}
</div>
<div className='hidden flex-row items-center justify-end gap-6 screenshot:hidden lg:flex'>
<Button variant='minimal' color='theme-black' onClick={handleChangelogOnClick}>
<IconoirGitFork className='h-6 w-6 text-ut-gray' />
@@ -249,19 +290,219 @@ export default function Settings(): JSX.Element {
<div className='p-6 lg:flex'>
<div className='mr-4 lg:w-1/2 xl:w-xl'>
<AdvancedSettings
highlightConflicts={highlightConflicts}
setHighlightConflicts={setHighlightConflicts}
loadAllCourses={loadAllCourses}
setLoadAllCourses={setLoadAllCourses}
increaseScheduleLimit={increaseScheduleLimit}
setIncreaseScheduleLimit={setIncreaseScheduleLimit}
calendarNewTab={calendarNewTab}
setCalendarNewTab={setCalendarNewTab}
activeSchedule={activeSchedule}
handleEraseAll={handleEraseAll}
handleImportClick={handleImportClick}
{/* <section className='mb-8'>
<h2 className='mb-4 text-xl text-ut-black font-semibold'>CUSTOMIZATION OPTIONS</h2>
<div className='flex space-x-4'>
<div className='w-1/2 space-y-4'>
<div className='flex items-center justify-between'>
<div className='max-w-xs'>
<h3 className='text-ut-burntorange font-semibold'>Show Course Status</h3>
<p className='text-sm text-gray-600'>
Shows an indicator for waitlisted, cancelled, and closed courses.
</p>
</div>
<SwitchButton
isChecked={enableCourseStatusChips}
onChange={() => {
setEnableCourseStatusChips(!enableCourseStatusChips);
OptionsStore.set('enableCourseStatusChips', !enableCourseStatusChips);
}}
/>
</div>
<Divider size='auto' orientation='horizontal' />
<div className='flex items-center justify-between'>
<div className='max-w-xs'>
<h3 className='text-ut-burntorange font-semibold'>
Show Time & Location in Popup
</h3>
<p className='text-sm text-gray-600'>
Shows the course&apos;s time and location in the extension&apos;s popup.
</p>
</div>
<SwitchButton
isChecked={showTimeLocation}
onChange={() => {
setShowTimeLocation(!showTimeLocation);
OptionsStore.set('enableTimeAndLocationInPopup', !showTimeLocation);
}}
/>
</div>
</div>
{DISPLAY_PREVIEWS && (
<Preview>
<CalendarCourseCell
colors={getCourseColors('orange')}
courseDeptAndInstr={ExampleCourse.department}
className={ExampleCourse.number}
status={ExampleCourse.status}
timeAndLocation={ExampleCourse.schedule.meetings[0]!.getTimeString({
separator: '-',
})}
/>
<PopupCourseBlock colors={getCourseColors('orange')} course={ExampleCourse} />
</Preview>
)}
</div>
</section>
<Divider size='auto' orientation='horizontal' /> */}
<section className='mb-8'>
<h2 className='mb-4 text-xl text-ut-black font-semibold'>ADVANCED SETTINGS</h2>
<div className='flex space-x-4'>
<div className={PREVIEW_SECTION_DIV_CLASSNAME}>
{/* <div className='flex items-center justify-between'>
<div className='max-w-xs'>
<h3 className='text-ut-burntorange font-semibold'>Refresh Data</h3>
<p className='text-sm text-gray-600'>
Refreshes waitlist, course status, and other info with the latest data from
UT&apos;s site.
</p>
</div>
<Button
variant='outline'
color='ut-black'
icon={RefreshIcon}
onClick={() => console.log('Refresh clicked')}
disabled={!enableDataRefreshing}
>
Refresh
</Button>
</div>
<Divider size='auto' orientation='horizontal' /> */}
<div className='flex items-center justify-between'>
<div className='max-w-xs'>
<Text variant='h4' className='text-ut-burntorange font-semibold'>
Export Current Schedule
</Text>
<p className='text-sm text-gray-600'>
Backup your active schedule to a portable file
</p>
</div>
<Button
variant='outline'
color='ut-burntorange'
onClick={() => handleExportJson(activeSchedule.id)}
>
Export
</Button>
</div>
<Divider size='auto' orientation='horizontal' />
<div className='flex items-center justify-between'>
<div className='max-w-xs'>
<Text variant='h4' className='text-ut-burntorange font-semibold'>
Import Schedule
</Text>
<p className='text-sm text-gray-600'>Import from a schedule file</p>
</div>
<FileUpload
variant='filled'
color='ut-burntorange'
onChange={handleImportClick}
accept={MIMEType.JSON}
>
Import Schedule
</FileUpload>
</div>
<Divider size='auto' orientation='horizontal' />
<div className='flex items-center justify-between'>
<div className='max-w-xs'>
<Text variant='h4' className='text-ut-burntorange font-semibold'>
Course Conflict Highlight
</Text>
<p className='text-sm text-gray-600'>
Adds a red strikethrough to courses that have conflicting times.
</p>
</div>
<SwitchButton
isChecked={highlightConflicts}
onChange={() => {
setHighlightConflicts(!highlightConflicts);
OptionsStore.set('enableHighlightConflicts', !highlightConflicts);
}}
/>
</div>
<Divider size='auto' orientation='horizontal' />
<div className='flex items-center justify-between'>
<div className='max-w-xs'>
<Text variant='h4' className='text-ut-burntorange font-semibold'>
Load All Courses in Course Schedule
</Text>
<p className='text-sm text-gray-600'>
Loads all courses in the Course Schedule site by scrolling, instead of using
next/prev page buttons.
</p>
</div>
<SwitchButton
isChecked={loadAllCourses}
onChange={() => {
setLoadAllCourses(!loadAllCourses);
OptionsStore.set('enableScrollToLoad', !loadAllCourses);
}}
/>
</div>
<Divider size='auto' orientation='horizontal' />
<div className='flex items-center justify-between'>
<div className='max-w-xs'>
<Text variant='h4' className='text-ut-burntorange font-semibold'>
Always Open Calendar in New Tab
</Text>
<p className='text-sm text-gray-600'>
Always opens the calendar view in a new tab when navigating to the calendar
page. May prevent issues where the calendar refuses to open.
</p>
</div>
<SwitchButton
isChecked={calendarNewTab}
onChange={() => {
setCalendarNewTab(!calendarNewTab);
OptionsStore.set('alwaysOpenCalendarInNewTab', !calendarNewTab);
}}
/>
</div>
<Divider size='auto' orientation='horizontal' />
<div className='flex items-center justify-between'>
<div className='max-w-xs'>
<Text variant='h4' className='text-ut-burntorange font-semibold'>
Reset All Data
</Text>
<p className='text-sm text-gray-600'>
Erases all schedules and courses you have.
</p>
</div>
<Button variant='outline' color='theme-red' icon={Trash} onClick={handleEraseAll}>
Erase All
</Button>
</div>
</div>
{DISPLAY_PREVIEWS && (
<Preview>
<Text
variant='h2-course'
className={clsx('text-center text-theme-red !font-normal', {
'line-through': highlightConflicts,
})}
>
01234 MWF 10:00 AM - 11:00 AM UTC 1.234
</Text>
</Preview>
)}
</div>
</section>
<Divider size='auto' orientation='horizontal' />
@@ -322,21 +563,17 @@ export default function Settings(): JSX.Element {
Open Debug Page
</Button>
</div>
</>
)}
<Divider size='auto' orientation='horizontal' />
<Button
variant='filled'
color='ut-black'
onClick={() => addCourseByURL(activeSchedule)}
>
<Button variant='filled' color='ut-black' onClick={() => addCourseByURL(activeSchedule)}>
Add course by link
</Button>
<Button variant='filled' color='ut-burntorange' onClick={showMigrationDialog}>
Show Migration Dialog
</Button>
</>
)}
</section>
</div>
@@ -349,43 +586,143 @@ export default function Settings(): JSX.Element {
</h2>
<div className='grid grid-cols-2 gap-4 2xl:grid-cols-4 md:grid-cols-3'>
{LONGHORN_DEVELOPERS_ADMINS.map(admin => (
<ContributorCard
<div
key={admin.githubUsername}
name={admin.name}
githubUsername={admin.githubUsername}
roles={admin.role}
stats={githubStats?.adminGitHubStats[admin.githubUsername]}
showStats={showGitHubStats}
includeMergedPRs={INCLUDE_MERGED_PRS}
/>
className='border border-gray-300 rounded bg-ut-gray/10 p-4'
>
<Text
variant='p'
className='text-ut-burntorange font-semibold hover:cursor-pointer'
onClick={() =>
window.open(`https://github.com/${admin.githubUsername}`, '_blank')
}
>
{admin.name}
</Text>
{admin.role.map(role => (
<p key={admin.githubUsername} className='text-sm text-gray-600'>
{role}
</p>
))}
{showGitHubStats && githubStats && (
<div className='mt-2'>
<p className='text-xs text-gray-500'>GitHub Stats (UTRP repo):</p>
{includeMergedPRs && (
<p className='text-xs'>
Merged PRS:{' '}
{githubStats.adminGitHubStats[admin.githubUsername]?.mergedPRs}
</p>
)}
<p className='text-xs'>
Commits: {githubStats.adminGitHubStats[admin.githubUsername]?.commits}
</p>
<p className='text-xs text-ut-green'>
{githubStats.adminGitHubStats[admin.githubUsername]?.linesAdded} ++
</p>
<p className='text-xs text-theme-red'>
{githubStats.adminGitHubStats[admin.githubUsername]?.linesDeleted} --
</p>
</div>
)}
</div>
))}
</div>
</section>
<section className='my-8'>
<h2 className='mb-4 text-xl text-ut-black font-semibold'>UTRP CONTRIBUTORS</h2>
<div className='grid grid-cols-2 gap-4 2xl:grid-cols-4 md:grid-cols-3 xl:grid-cols-3'>
{sortedContributors.map(swe => (
<ContributorCard
{LONGHORN_DEVELOPERS_SWE.sort(
(a, b) =>
(githubStats?.userGitHubStats[b.githubUsername]?.commits ?? 0) -
(githubStats?.userGitHubStats[a.githubUsername]?.commits ?? 0)
).map(swe => (
<div
key={swe.githubUsername}
name={swe.name}
githubUsername={swe.githubUsername}
roles={swe.role}
stats={githubStats?.userGitHubStats[swe.githubUsername]}
showStats={showGitHubStats}
includeMergedPRs={INCLUDE_MERGED_PRS}
/>
className='border border-gray-300 rounded bg-ut-gray/10 p-4'
>
<Text
variant='p'
className='text-ut-burntorange font-semibold hover:cursor-pointer'
onClick={() =>
window.open(`https://github.com/${swe.githubUsername}`, '_blank')
}
>
{swe.name}
</Text>
{swe.role.map(role => (
<p key={swe.githubUsername} className='text-sm text-gray-600'>
{role}
</p>
))}
{additionalContributors.map(username => (
<ContributorCard
{showGitHubStats && githubStats && (
<div className='mt-2'>
<p className='text-xs text-gray-500'>GitHub Stats (UTRP repo):</p>
{includeMergedPRs && (
<p className='text-xs'>
Merged PRS:{' '}
{githubStats.userGitHubStats[swe.githubUsername]?.mergedPRs}
</p>
)}
<p className='text-xs'>
Commits: {githubStats.userGitHubStats[swe.githubUsername]?.commits}
</p>
<p className='text-xs text-ut-green'>
{githubStats.userGitHubStats[swe.githubUsername]?.linesAdded} ++
</p>
<p className='text-xs text-theme-red'>
{githubStats.userGitHubStats[swe.githubUsername]?.linesDeleted} --
</p>
</div>
)}
</div>
))}
{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 => (
<div
key={username}
name={githubStats!.names[username] || username}
githubUsername={username}
roles={['Contributor']}
stats={githubStats!.userGitHubStats[username]}
showStats={showGitHubStats}
includeMergedPRs={INCLUDE_MERGED_PRS}
/>
className='overflow-clip border border-gray-300 rounded bg-ut-gray/10 p-4'
>
<Text
variant='p'
className='text-ut-burntorange font-semibold hover:cursor-pointer'
onClick={() => window.open(`https://github.com/${username}`, '_blank')}
>
{githubStats.names[username]}
</Text>
<p className='text-sm text-gray-600'>Contributor</p>
{showGitHubStats && (
<div className='mt-2'>
<p className='text-xs text-gray-500'>GitHub Stats (UTRP repo):</p>
{includeMergedPRs && (
<p className='text-xs'>
Merged PRs:{' '}
{githubStats.userGitHubStats[username]?.mergedPRs}
</p>
)}
<p className='text-xs'>
Commits: {githubStats.userGitHubStats[username]?.commits}
</p>
<p className='text-xs text-ut-green'>
{githubStats.userGitHubStats[username]?.linesAdded} ++
</p>
<p className='text-xs text-theme-red'>
{githubStats.userGitHubStats[username]?.linesDeleted} --
</p>
</div>
)}
</div>
))}
</div>
</section>

View File

@@ -1,13 +0,0 @@
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;

View File

@@ -1,140 +0,0 @@
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 };
};

View File

@@ -1,35 +0,0 @@
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];
};

View File

@@ -1,8 +1,5 @@
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, useEffect, useState } from 'react';
import React, { useCallback } from 'react';
import { Button } from '../components/common/Button';
import { usePrompt } from '../components/common/DialogProvider/DialogProvider';
@@ -20,33 +17,8 @@ const SCHEDULE_LIMIT = 10;
export function useEnforceScheduleLimit(): () => boolean {
const [, schedules] = useSchedules();
const showDialog = usePrompt();
const [allowMoreSchedules, setAllowMoreSchedules] = useState<boolean>(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!`,
@@ -55,33 +27,19 @@ export function useEnforceScheduleLimit(): () => boolean {
<>
To encourage organization,{' '}
<span className='text-ut-burntorange'>please consider deleting any unused schedules</span> you
may have. You can increase the limit in the settings if its really necessary.
may have.
</>
),
buttons: close => (
<>
<Button
variant='outline'
color='ut-black'
onClick={() => {
close();
// open options/settings page so user can enable bypass if they are advising staff
const url = chrome.runtime.getURL(CRX_PAGES.OPTIONS);
background.openNewTab({ url });
}}
>
Open Settings
</Button>
<Button variant='filled' color='ut-burntorange' onClick={close}>
I understand
</Button>
</>
),
});
return false;
}
return true;
}, [schedules, showDialog, allowMoreSchedules]);
}, [schedules, showDialog]);
}

View File

@@ -88,31 +88,6 @@ 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;
@@ -205,14 +180,12 @@ export default defineConfig({
'PROD',
'VITE_SENTRY_ENVIRONMENT',
'VITE_BETA_BUILD',
'VITE_GIT_BRANCH',
'VITE_GIT_COMMIT',
],
includeTimestamp: true,
includeBuildTime: true,
customMetadata: {
gitBranch: () => gitInfo.gitBranch,
gitCommit: () => gitInfo.gitCommit,
gitBranch: () => execSync('git rev-parse --abbrev-ref HEAD').toString().trim(),
gitCommit: () => execSync('git rev-parse --short HEAD').toString().trim(),
nodeVersion: () => process.version,
},
}),