diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 000000000..91a204aca
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,42 @@
+name: CI
+
+on:
+ push:
+ branches: [main, feature/*]
+ pull_request:
+ branches: [main]
+
+jobs:
+ test:
+ name: Lint, Test & Build
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Run linter
+ run: npm run lint || true
+ # TODO: Remove "|| true" after cleanup branch fixes all lint errors
+
+ - name: Run tests
+ run: npm test
+
+ - name: Build application
+ run: npm run build
+ env:
+ # Skip env validation during CI build
+ SKIP_ENV_VALIDATION: true
+ # Provide dummy values for required env vars during build
+ MONGODB_URI: mongodb://localhost:27017/test
+ NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: pk_test_dummy
+ CLERK_SECRET_KEY: sk_test_dummy
diff --git a/.gitignore b/.gitignore
index f5d6518a2..944028550 100644
--- a/.gitignore
+++ b/.gitignore
@@ -78,4 +78,6 @@ next-env.d.ts
.next/
//CLAUDE
-claude.md
\ No newline at end of file
+claude.md
+# Local Netlify folder
+.netlify
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100644
index 000000000..9d8ec4a16
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1,11 @@
+echo "Running pre-commit checks..."
+
+# Skip lint for now - cleanup needed
+# TODO: Re-enable after cleanup branch fixes all lint errors
+# npm run lint || exit 1
+
+# Run tests - these must pass
+echo "Running tests..."
+npm test || exit 1
+
+echo "All checks passed!"
diff --git a/package-lock.json b/package-lock.json
index f132a1e72..56b8522ea 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -37,6 +37,7 @@
"autoprefixer": "^10.4.21",
"eslint": "^8.0.0",
"eslint-config-next": "^14.0.0",
+ "husky": "^9.1.7",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"postcss": "^8.4.31",
@@ -55,7 +56,6 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
@@ -910,7 +910,6 @@
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
- "dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
@@ -928,7 +927,6 @@
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -941,7 +939,6 @@
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
@@ -1504,7 +1501,6 @@
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
@@ -1526,7 +1522,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@@ -1536,14 +1531,12 @@
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
- "dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@@ -1746,7 +1739,6 @@
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "2.0.5",
@@ -1760,7 +1752,6 @@
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 8"
@@ -1770,7 +1761,6 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.scandir": "2.1.5",
@@ -1852,7 +1842,6 @@
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
- "dev": true,
"license": "MIT",
"optional": true,
"engines": {
@@ -2615,7 +2604,7 @@
"version": "15.7.15",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/@types/qs": {
@@ -2634,7 +2623,7 @@
"version": "18.3.26",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz",
"integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
@@ -3398,7 +3387,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -3408,7 +3396,6 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
@@ -3424,14 +3411,12 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
- "dev": true,
"license": "MIT"
},
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
- "dev": true,
"license": "ISC",
"dependencies": {
"normalize-path": "^3.0.0",
@@ -3445,7 +3430,6 @@
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
- "dev": true,
"license": "MIT"
},
"node_modules/argparse": {
@@ -3872,7 +3856,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
@@ -3889,7 +3872,6 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -3913,7 +3895,6 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
@@ -4065,7 +4046,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
@@ -4149,7 +4129,6 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"anymatch": "~3.1.2",
@@ -4174,7 +4153,6 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
@@ -4308,7 +4286,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
@@ -4321,7 +4298,6 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true,
"license": "MIT"
},
"node_modules/combined-stream": {
@@ -4340,7 +4316,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
@@ -4498,7 +4473,6 @@
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
@@ -4520,7 +4494,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
- "dev": true,
"license": "MIT",
"bin": {
"cssesc": "bin/cssesc"
@@ -4971,7 +4944,6 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
- "dev": true,
"license": "Apache-2.0"
},
"node_modules/diff-sequences": {
@@ -4988,7 +4960,6 @@
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
- "dev": true,
"license": "MIT"
},
"node_modules/doctrine": {
@@ -5060,7 +5031,6 @@
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
- "dev": true,
"license": "MIT"
},
"node_modules/electron-to-chromium": {
@@ -5087,7 +5057,6 @@
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true,
"license": "MIT"
},
"node_modules/enhanced-resolve": {
@@ -5912,7 +5881,6 @@
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
@@ -5929,7 +5897,6 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
@@ -5956,7 +5923,6 @@
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
- "dev": true,
"license": "ISC",
"dependencies": {
"reusify": "^1.0.4"
@@ -5989,7 +5955,6 @@
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
@@ -6057,7 +6022,6 @@
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
- "dev": true,
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.6",
@@ -6138,7 +6102,6 @@
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@@ -6314,7 +6277,6 @@
"version": "10.3.10",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
"integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
- "dev": true,
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
@@ -6337,7 +6299,6 @@
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.3"
@@ -6356,7 +6317,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
@@ -6366,7 +6326,6 @@
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
@@ -6602,6 +6561,22 @@
"node": ">=10.17.0"
}
},
+ "node_modules/husky": {
+ "version": "9.1.7",
+ "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
+ "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "husky": "bin.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/typicode"
+ }
+ },
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
@@ -6817,7 +6792,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"binary-extensions": "^2.0.0"
@@ -6870,7 +6844,6 @@
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
- "dev": true,
"license": "MIT",
"dependencies": {
"hasown": "^2.0.2"
@@ -6921,7 +6894,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -6947,7 +6919,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -6987,7 +6958,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
@@ -7026,7 +6996,6 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.12.0"
@@ -7245,7 +7214,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "dev": true,
"license": "ISC"
},
"node_modules/istanbul-lib-coverage": {
@@ -7341,7 +7309,6 @@
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
"integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
- "dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
@@ -8340,7 +8307,7 @@
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
@@ -8872,7 +8839,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
@@ -8885,7 +8851,6 @@
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
- "dev": true,
"license": "MIT"
},
"node_modules/locate-path": {
@@ -8936,7 +8901,6 @@
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
- "dev": true,
"license": "ISC"
},
"node_modules/lucide-react": {
@@ -9032,7 +8996,6 @@
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 8"
@@ -9042,7 +9005,6 @@
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"braces": "^3.0.3",
@@ -9120,7 +9082,6 @@
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
- "dev": true,
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
@@ -9273,7 +9234,6 @@
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
- "dev": true,
"license": "MIT",
"dependencies": {
"any-promise": "^1.0.0",
@@ -9434,7 +9394,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -9483,7 +9442,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
@@ -9802,7 +9760,6 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -9812,14 +9769,12 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
- "dev": true,
"license": "MIT"
},
"node_modules/path-scurry": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
- "dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^10.2.0",
@@ -9842,7 +9797,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
@@ -9855,7 +9809,6 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -9865,7 +9818,6 @@
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
@@ -9954,7 +9906,6 @@
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
- "dev": true,
"funding": [
{
"type": "opencollective",
@@ -9983,7 +9934,6 @@
"version": "15.1.0",
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
- "dev": true,
"license": "MIT",
"dependencies": {
"postcss-value-parser": "^4.0.0",
@@ -10001,7 +9951,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
"integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
- "dev": true,
"funding": [
{
"type": "opencollective",
@@ -10027,7 +9976,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
"integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
- "dev": true,
"funding": [
{
"type": "opencollective",
@@ -10070,7 +10018,6 @@
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
- "dev": true,
"funding": [
{
"type": "opencollective",
@@ -10096,7 +10043,6 @@
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
@@ -10110,7 +10056,6 @@
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
- "dev": true,
"license": "MIT"
},
"node_modules/prelude-ls": {
@@ -10257,7 +10202,6 @@
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -10350,7 +10294,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"pify": "^2.3.0"
@@ -10360,7 +10303,6 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"picomatch": "^2.2.1"
@@ -10499,7 +10441,6 @@
"version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
- "dev": true,
"license": "MIT",
"dependencies": {
"is-core-module": "^2.16.0",
@@ -10573,7 +10514,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
- "dev": true,
"license": "MIT",
"engines": {
"iojs": ">=1.0.0",
@@ -10623,7 +10563,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -10799,7 +10738,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
@@ -10812,7 +10750,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -10904,7 +10841,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
"license": "ISC",
"engines": {
"node": ">=14"
@@ -11097,7 +11033,6 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
@@ -11116,7 +11051,6 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
@@ -11131,14 +11065,12 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true,
"license": "MIT"
},
"node_modules/string-width/node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -11151,7 +11083,6 @@
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
@@ -11280,7 +11211,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
@@ -11294,7 +11224,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
@@ -11376,7 +11305,6 @@
"version": "3.35.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
"integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.2",
@@ -11412,7 +11340,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -11457,7 +11384,6 @@
"version": "3.4.18",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz",
"integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
@@ -11495,7 +11421,6 @@
"version": "1.21.7",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
- "dev": true,
"license": "MIT",
"bin": {
"jiti": "bin/jiti.js"
@@ -11580,7 +11505,6 @@
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"any-promise": "^1.0.0"
@@ -11590,7 +11514,6 @@
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"thenify": ">= 3.1.0 < 4"
@@ -11670,7 +11593,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
@@ -11752,7 +11674,6 @@
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
- "dev": true,
"license": "Apache-2.0"
},
"node_modules/tsconfig-paths": {
@@ -12037,7 +11958,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
- "dev": true,
"license": "MIT"
},
"node_modules/v8-to-istanbul": {
@@ -12206,7 +12126,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
@@ -12321,7 +12240,6 @@
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
@@ -12340,7 +12258,6 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
@@ -12358,14 +12275,12 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true,
"license": "MIT"
},
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
@@ -12380,7 +12295,6 @@
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -12393,7 +12307,6 @@
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -12406,7 +12319,6 @@
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
diff --git a/package.json b/package.json
index 0dd56fcab..9e4e02c34 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,8 @@
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
- "analyze": "ANALYZE=true npm run build"
+ "analyze": "ANALYZE=true npm run build",
+ "prepare": "husky"
},
"dependencies": {
"@clerk/clerk-react": "^4.30.0",
@@ -42,6 +43,7 @@
"autoprefixer": "^10.4.21",
"eslint": "^8.0.0",
"eslint-config-next": "^14.0.0",
+ "husky": "^9.1.7",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"postcss": "^8.4.31",
diff --git a/src/app/MinimalLayout.jsx b/src/app/MinimalLayout.jsx
index d17fe6064..c4fcde6bd 100644
--- a/src/app/MinimalLayout.jsx
+++ b/src/app/MinimalLayout.jsx
@@ -1,7 +1,6 @@
'use client';
-import { usePathname, useRouter } from 'next/navigation';
-import { useEffect } from 'react';
+import { usePathname } from 'next/navigation';
import { useUser } from '@clerk/nextjs';
import { useUserContext } from './contexts/UserContext';
import { AppShell } from '@/components/experimental';
@@ -21,7 +20,6 @@ import { Alert } from '@components/ui';
*/
export default function MinimalLayout({ children }) {
const pathname = usePathname();
- const router = useRouter();
const { isLoaded: clerkLoaded, isSignedIn } = useUser();
const { user, dailyBonusMessage, settlementMessage } = useUserContext();
@@ -29,12 +27,7 @@ export default function MinimalLayout({ children }) {
const isAuthRoute = pathname.startsWith('/login') || pathname.startsWith('/sign-up') || pathname === '/';
const showShell = !isAuthRoute;
- // Redirect unauthenticated users from protected pages to sign-up
- useEffect(() => {
- if (clerkLoaded && !isSignedIn && showShell) {
- router.replace('/sign-up');
- }
- }, [clerkLoaded, isSignedIn, showShell, router]);
+ // Note: Auth redirects are handled by middleware.ts - no client-side redirect needed
// Extract user data for AppShell
const biscuits = user?.biscuits ?? 0;
@@ -58,17 +51,11 @@ export default function MinimalLayout({ children }) {
);
}
- // User not authenticated but on protected page - show redirect message
+ // User not authenticated but on protected page - middleware will redirect
if (!isSignedIn) {
return (
-
+
-
- Redirecting to sign up page...
-
-
- Create an account or login to access these features
-
);
}
diff --git a/src/app/__tests__/LandingPage.test.jsx b/src/app/__tests__/LandingPage.test.jsx
index b5e675d31..9d72a2b18 100644
--- a/src/app/__tests__/LandingPage.test.jsx
+++ b/src/app/__tests__/LandingPage.test.jsx
@@ -20,9 +20,11 @@ jest.mock('@clerk/nextjs', () => ({
// Mock Link since it's used in the component
jest.mock('next/link', () => {
- return ({ children, href }) => {
+ const MockLink = ({ children, href }) => {
return
{children};
};
+ MockLink.displayName = 'MockLink';
+ return MockLink;
});
describe('Landing Page (Home)', () => {
@@ -39,17 +41,20 @@ describe('Landing Page (Home)', () => {
render(
);
- // Check for landing page content
- expect(screen.getByText('HuskyBids')).toBeInTheDocument();
- expect(screen.getByText('Sign Up')).toBeInTheDocument();
- expect(screen.getByText('Log In')).toBeInTheDocument();
+ // Check for landing page content (text may appear multiple times)
+ expect(screen.getAllByText('HUSKYBIDS').length).toBeGreaterThan(0);
+ // Check for Get Started (sign up) and Log In links
+ const signUpLinks = screen.getAllByRole('link', { name: 'Get Started' });
+ expect(signUpLinks.length).toBeGreaterThan(0);
+ const loginLinks = screen.getAllByRole('link', { name: 'Log In' });
+ expect(loginLinks.length).toBeGreaterThan(0);
// Crucial check: Ensure NO redirect happened
expect(mockReplace).not.toHaveBeenCalled();
expect(mockPush).not.toHaveBeenCalled();
});
- it('renders dashboard link for authenticated users', () => {
+ it('redirects authenticated users to dashboard', () => {
// Simulate authenticated user
mockUseUser.mockReturnValue({
isSignedIn: true,
@@ -58,11 +63,8 @@ describe('Landing Page (Home)', () => {
render(
);
- // Check for dashboard link
- expect(screen.getByText('Go to Dashboard')).toBeInTheDocument();
-
- // Ensure "Sign Up" is NOT present
- expect(screen.queryByText('Sign Up')).not.toBeInTheDocument();
+ // Crucial check: Authenticated users should be auto-redirected
+ expect(mockReplace).toHaveBeenCalledWith('/dashboard');
});
it('does not redirect while loading auth state', () => {
@@ -73,9 +75,9 @@ describe('Landing Page (Home)', () => {
render(
);
- // Should still show the title/basic layout even while loading,
+ // Should still show the title/basic layout even while loading,
// or at least not redirect.
- expect(screen.getByText('HuskyBids')).toBeInTheDocument();
+ expect(screen.getAllByText('HUSKYBIDS').length).toBeGreaterThan(0);
expect(mockReplace).not.toHaveBeenCalled();
});
});
diff --git a/src/app/__tests__/auth-pages.test.jsx b/src/app/__tests__/auth-pages.test.jsx
new file mode 100644
index 000000000..eb8db8633
--- /dev/null
+++ b/src/app/__tests__/auth-pages.test.jsx
@@ -0,0 +1,118 @@
+/**
+ * Auth Pages Tests
+ * Verifies that sign-up and login pages are configured correctly
+ *
+ * CRITICAL: These tests ensure the auth navigation fix is in place.
+ * The routing="hash" prop was removed because it caused auth state
+ * to be stored in URL hash fragments, which server-side middleware
+ * cannot read, causing users to be redirected back to sign-up.
+ */
+import React from 'react';
+import { render } from '@testing-library/react';
+
+// Mock Clerk components
+const mockSignUp = jest.fn();
+const mockSignIn = jest.fn();
+
+jest.mock('@clerk/nextjs', () => ({
+ SignUp: (props) => {
+ mockSignUp(props);
+ return
SignUp Component
;
+ },
+ SignIn: (props) => {
+ mockSignIn(props);
+ return
SignIn Component
;
+ },
+}));
+
+// Mock next/navigation
+jest.mock('next/navigation', () => ({
+ useSearchParams: () => ({
+ get: jest.fn().mockReturnValue(null),
+ }),
+}));
+
+// Import components after mocks
+import SignUpPage from '../sign-up/[[...sign-up]]/page';
+import LoginPage from '../login/[[...sign-in]]/page';
+
+describe('Auth Pages Configuration', () => {
+ beforeEach(() => {
+ mockSignUp.mockClear();
+ mockSignIn.mockClear();
+ });
+
+ describe('SignUp Page', () => {
+ it('should NOT use routing="hash" (auth fix)', () => {
+ render(
);
+
+ // Verify SignUp was called
+ expect(mockSignUp).toHaveBeenCalled();
+
+ const props = mockSignUp.mock.calls[0][0];
+
+ // CRITICAL: routing should NOT be "hash"
+ // Hash routing causes auth state to be invisible to middleware
+ expect(props.routing).not.toBe('hash');
+ });
+
+ it('should have correct path configuration', () => {
+ render(
);
+
+ const props = mockSignUp.mock.calls[0][0];
+
+ expect(props.path).toBe('/sign-up');
+ expect(props.afterSignUpUrl).toBe('/dashboard');
+ expect(props.signInUrl).toBe('/login');
+ });
+
+ it('should use monospace font styling', () => {
+ render(
);
+
+ const props = mockSignUp.mock.calls[0][0];
+
+ expect(props.appearance.variables.fontFamily).toContain('monospace');
+ });
+ });
+
+ describe('Login Page', () => {
+ it('should NOT use routing="hash" (auth fix)', () => {
+ render(
);
+
+ // Verify SignIn was called
+ expect(mockSignIn).toHaveBeenCalled();
+
+ const props = mockSignIn.mock.calls[0][0];
+
+ // CRITICAL: routing should NOT be "hash"
+ // Hash routing causes auth state to be invisible to middleware
+ expect(props.routing).not.toBe('hash');
+ });
+
+ it('should have correct path configuration', () => {
+ render(
);
+
+ const props = mockSignIn.mock.calls[0][0];
+
+ expect(props.path).toBe('/login');
+ expect(props.signUpUrl).toBe('/sign-up');
+ });
+
+ it('should redirect to dashboard by default', () => {
+ render(
);
+
+ const props = mockSignIn.mock.calls[0][0];
+
+ // Default redirect when no ?redirect param
+ expect(props.afterSignInUrl).toBe('/dashboard');
+ });
+
+ it('should use monospace font styling', () => {
+ render(
);
+
+ const props = mockSignIn.mock.calls[0][0];
+
+ expect(props.appearance.variables.fontFamily).toContain('monospace');
+ });
+ });
+});
diff --git a/src/app/admin/settle-bets/page.jsx b/src/app/admin/settle-bets/page.jsx
deleted file mode 100644
index 9acb170c9..000000000
--- a/src/app/admin/settle-bets/page.jsx
+++ /dev/null
@@ -1,169 +0,0 @@
-'use client';
-
-import { useState } from 'react';
-import { useUserContext } from '../../contexts/UserContext';
-import {
- SectionLabel,
- DottedDivider,
- ActionBar,
-} from '@/components/experimental';
-
-export default function SettleBetsPage() {
- const { user, refreshUser } = useUserContext();
- const [loading, setLoading] = useState(false);
- const [result, setResult] = useState(null);
- const [error, setError] = useState(null);
- const [balanceBefore, setBalanceBefore] = useState(null);
- const [balanceAfter, setBalanceAfter] = useState(null);
-
- const handleSettleAll = async () => {
- try {
- setLoading(true);
- setError(null);
- setResult(null);
-
- // Save balance before settlement
- const before = user?.biscuits || 0;
- setBalanceBefore(before);
- console.log('Balance before settlement:', before);
-
- const response = await fetch('/api/bets/settle-all', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- });
-
- const data = await response.json();
-
- if (!response.ok) {
- throw new Error(data.error || 'Failed to settle bets');
- }
-
- setResult(data);
-
- // Refresh user data to update balance in header
- console.log('Settlement complete, refreshing user data...');
- await refreshUser();
-
- // Small delay to ensure state has updated
- setTimeout(() => {
- const after = user?.biscuits || 0;
- setBalanceAfter(after);
- console.log('Balance after settlement:', after);
- console.log('Balance change:', after - before);
- }, 500);
- } catch (err) {
- console.error('Error settling bets:', err);
- setError(err.message);
- } finally {
- setLoading(false);
- }
- };
-
- return (
-
- {/* Header */}
-
-
Settle Bets
-
Process all pending bets for completed games
-
-
-
-
- {/* Settle Button */}
-
-
-
- {error && (
-
- )}
-
- {result && (
-
-
-
Settlement Complete
-
-
- Games Processed:
- {result.gamesProcessed}
-
-
- Bets Settled:
- {result.betsSettled}
-
-
- Won:
- {result.won}
-
-
- Lost:
- {result.lost}
-
-
- Total Payout:
- {result.totalPayout.toLocaleString()} biscuits
-
- {balanceBefore !== null && balanceAfter !== null && (
-
- Your Balance Change:
-
- {balanceBefore.toLocaleString()} → {balanceAfter.toLocaleString()}
- balanceBefore ? 'text-green-400 ml-2' : 'text-zinc-500 ml-2'}>
- ({balanceAfter > balanceBefore ? '+' : ''}{(balanceAfter - balanceBefore).toLocaleString()})
-
-
-
- )}
-
-
-
- {/* Per-game results */}
- {result.games && result.games.length > 0 && (
-
-
Game Details:
- {result.games.map((game, idx) => (
-
-
- {game.homeTeam} vs {game.awayTeam}
-
-
- Winner: {game.winner === 'home' ? game.homeTeam : game.awayTeam}
- Bets: {game.betsSettled}
- Won: {game.won}
- Lost: {game.lost}
- Payout: {game.payout.toLocaleString()}
-
-
- ))}
-
- )}
-
- )}
-
-
-
-
- {/* Actions */}
-
-
- );
-}
diff --git a/src/app/daily-tasks/biscuit.png b/src/app/daily-tasks/biscuit.png
deleted file mode 100644
index 846f73c8e..000000000
Binary files a/src/app/daily-tasks/biscuit.png and /dev/null differ
diff --git a/src/app/dashboard/page.jsx b/src/app/dashboard/page.jsx
index 32994ed87..df2f066e7 100644
--- a/src/app/dashboard/page.jsx
+++ b/src/app/dashboard/page.jsx
@@ -165,7 +165,6 @@ export default function Dashboard() {
diff --git a/src/app/games/page.jsx b/src/app/games/page.jsx
index 3f355423f..8111dee11 100644
--- a/src/app/games/page.jsx
+++ b/src/app/games/page.jsx
@@ -22,7 +22,6 @@ const fetcher = (url) => fetch(url).then(res => {
export default function GamesPage() {
const { user: userData, refreshUser, updateBiscuits } = useUserContext();
-
const [sport, setSport] = useState('all');
const [syncing, setSyncing] = useState(false);
const [showPastGames, setShowPastGames] = useState(false);
diff --git a/src/app/globals.css b/src/app/globals.css
index 75038abac..d325eb5b1 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1,4 +1,4 @@
-@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=Montserrat:wght@400;500;600;700;800;900&display=swap');
+/* Monospace fonts only - no need for external font imports */
@tailwind base;
@tailwind components;
@@ -40,8 +40,8 @@
/* Body Styles */
body {
- @apply bg-zinc-950 text-zinc-300 font-sans;
- font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+ @apply bg-zinc-950 text-zinc-300 font-mono;
+ font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
}
/* Typography Scale */
diff --git a/src/app/leaderboard/page.jsx b/src/app/leaderboard/page.jsx
index ac8659f44..485d5a9d7 100644
--- a/src/app/leaderboard/page.jsx
+++ b/src/app/leaderboard/page.jsx
@@ -160,16 +160,6 @@ const LeaderboardPage = () => {
)}
-
-
-
- {/* Actions */}
-
);
};
diff --git a/src/app/login/page.js b/src/app/login/[[...sign-in]]/page.js
similarity index 99%
rename from src/app/login/page.js
rename to src/app/login/[[...sign-in]]/page.js
index d0f238527..437a87395 100644
--- a/src/app/login/page.js
+++ b/src/app/login/[[...sign-in]]/page.js
@@ -59,7 +59,6 @@ export default function LoginPage() {
socialButtonsVariant: 'iconButton',
}
}}
- routing="hash"
path="/login"
afterSignInUrl={redirectUrl}
signUpUrl="/sign-up"
diff --git a/src/app/not-found.jsx b/src/app/not-found.jsx
new file mode 100644
index 000000000..4faabf2fa
--- /dev/null
+++ b/src/app/not-found.jsx
@@ -0,0 +1,42 @@
+'use client';
+
+import { useUser } from '@clerk/nextjs';
+import { useRouter } from 'next/navigation';
+import { useEffect } from 'react';
+
+/**
+ * Global 404 Not Found Handler
+ *
+ * Provides intelligent redirects based on authentication state:
+ * - Authenticated users → Redirect to /dashboard
+ * - Unauthenticated users → Redirect to /login
+ *
+ * This prevents users from hitting dead-end 404 pages and provides
+ * a better user experience by guiding them to the appropriate location.
+ */
+export default function NotFound() {
+ const { isSignedIn, isLoaded } = useUser();
+ const router = useRouter();
+
+ useEffect(() => {
+ if (isLoaded) {
+ if (isSignedIn) {
+ // Authenticated users go to dashboard
+ router.push('/dashboard');
+ } else {
+ // Unauthenticated users go to login
+ router.push('/login');
+ }
+ }
+ }, [isLoaded, isSignedIn, router]);
+
+ return (
+
+
+
404
+
Page not found
+
Redirecting...
+
+
+ );
+}
diff --git a/src/app/page.jsx b/src/app/page.jsx
index 42aa13116..3f3992583 100644
--- a/src/app/page.jsx
+++ b/src/app/page.jsx
@@ -1,55 +1,175 @@
'use client';
+import { useRouter } from 'next/navigation';
import { useUser } from '@clerk/nextjs';
+import { useEffect } from 'react';
import Link from 'next/link';
+// Feature data
+const features = [
+ {
+ title: 'Live Game Tracking',
+ description: 'Follow UW Huskies football and basketball in real-time with live scores from ESPN.',
+ icon: '○',
+ },
+ {
+ title: 'Dynamic Odds',
+ description: 'Odds update based on betting activity. Popular picks pay less, underdogs pay more.',
+ icon: '◇',
+ },
+ {
+ title: 'Compete & Climb',
+ description: 'See how you rank against other Huskies fans. Top bettors earn bragging rights.',
+ icon: '△',
+ },
+];
+
+// How it works steps
+const steps = [
+ {
+ number: '01',
+ title: 'Sign Up',
+ description: 'Create your account and receive 1,000 biscuits to start betting.',
+ },
+ {
+ number: '02',
+ title: 'Pick Games',
+ description: 'Browse upcoming Washington Huskies games and check the odds.',
+ },
+ {
+ number: '03',
+ title: 'Place Bets',
+ description: 'Bet on your favorite team and watch your biscuits grow.',
+ },
+];
+
export default function Home() {
const { isSignedIn, isLoaded } = useUser();
+ const router = useRouter();
+ useEffect(() => {
+ if (!isLoaded) {
+ return;
+ }
+
+ if (isSignedIn) {
+ router.replace("/dashboard");
+ }
+ }, [isSignedIn, isLoaded]);
return (
-
-
-
-
- HuskyBids
+
+ {/* Header */}
+
-
- {isLoaded && (
- isSignedIn ? (
-
+
+
+ University of Washington
+
+
+ HUSKYBIDS
+
+
+ The premier virtual betting platform for UW Huskies sports.
+ Bet with pts, not money.
+
+
+
+ {isLoaded && !isSignedIn && (
+ <>
+
- Go to Dashboard
+ Get Started
- ) : (
- <>
-
- Sign Up
-
-
- Log In
-
- >
- )
- )}
+
+ Log In
+
+ >
+ )}
+
+
+
+
+ {/* Dotted Divider */}
+
+
+ {/* Features Section */}
+
+
+
+ Features
+
+
+ {features.map((feature) => (
+
+
{feature.icon}
+
{feature.title}
+
+ {feature.description}
+
+
+ ))}
+
-
+
-
);
-}
\ No newline at end of file
+}
diff --git a/src/app/sign-up/page.jsx b/src/app/sign-up/[[...sign-up]]/page.jsx
similarity index 98%
rename from src/app/sign-up/page.jsx
rename to src/app/sign-up/[[...sign-up]]/page.jsx
index ada823156..287bd71f0 100644
--- a/src/app/sign-up/page.jsx
+++ b/src/app/sign-up/[[...sign-up]]/page.jsx
@@ -54,8 +54,8 @@ export default function SignUpPage() {
socialButtonsVariant: 'iconButton',
}
}}
- routing="hash"
path="/sign-up"
+ signInUrl="/login"
afterSignUpUrl="/dashboard"
/>
diff --git a/src/app/tasks/page.jsx b/src/app/tasks/page.jsx
index 2339f620b..dadfea0e2 100644
--- a/src/app/tasks/page.jsx
+++ b/src/app/tasks/page.jsx
@@ -12,6 +12,7 @@ import {
import { StatCardSkeleton } from '@/components/ui/LoadingSkeleton';
import { useUserContext } from '../contexts/UserContext';
import { cn } from '@/shared/utils';
+import FireIcon from '@/components/FireIcon';
// SWR fetcher function
const fetcher = (url) => fetch(url).then(res => {
@@ -165,7 +166,7 @@ export default function TasksPage() {
{/* Streak Bonus Info */}
- 🔥
+
Streak Bonuses
diff --git a/src/components/FireIcon.jsx b/src/components/FireIcon.jsx
new file mode 100644
index 000000000..c34f16dd5
--- /dev/null
+++ b/src/components/FireIcon.jsx
@@ -0,0 +1,125 @@
+/**
+ * FireIcon Component
+ * Modern, clean flame icon
+ */
+
+'use client';
+
+import { memo } from 'react';
+
+const FireIcon = memo(function FireIcon({
+ size = 16,
+ className = '',
+ variant = 'default'
+}) {
+ const sizeValue = typeof size === 'number' ? size : 16;
+
+ const variants = {
+ default: {
+ id: 'flame-default',
+ colors: ['#fcd34d', '#f97316', '#dc2626'],
+ inner: '#fef3c7',
+ },
+ subtle: {
+ id: 'flame-subtle',
+ colors: ['#fdba74', '#ea580c', '#b91c1c'],
+ inner: '#fed7aa',
+ },
+ intense: {
+ id: 'flame-intense',
+ colors: ['#fef08a', '#fbbf24', '#ea580c'],
+ inner: '#fffbeb',
+ },
+ mono: {
+ id: 'flame-mono',
+ colors: ['#d4d4d8', '#71717a', '#3f3f46'],
+ inner: '#f4f4f5',
+ }
+ };
+
+ const colors = variants[variant] || variants.default;
+
+ return (
+
+ );
+});
+
+// Animated version
+FireIcon.Animated = memo(function AnimatedFireIcon({
+ size = 16,
+ className = '',
+ variant = 'intense'
+}) {
+ return (
+
+ );
+});
+
+// Streak counter
+FireIcon.Streak = memo(function FireStreak({
+ count = 0,
+ size = 16,
+ showCount = true,
+ className = ''
+}) {
+ const getVariant = (n) => {
+ if (n >= 7) return 'intense';
+ if (n >= 3) return 'default';
+ return 'subtle';
+ };
+
+ return (
+
+
+ {showCount && (
+
+ {count}
+
+ )}
+
+ );
+});
+
+export default FireIcon;
diff --git a/src/components/experimental/ui/MinimalGameCard.jsx b/src/components/experimental/ui/MinimalGameCard.jsx
index 595a84dd8..5a6fde355 100644
--- a/src/components/experimental/ui/MinimalGameCard.jsx
+++ b/src/components/experimental/ui/MinimalGameCard.jsx
@@ -107,8 +107,12 @@ export default function MinimalGameCard({
)}
{game?.sport && (
-
- {game.sport === 'football' ? : }
+
+ {game.sport === 'football' ? (
+
+ ) : (
+
+ )}
)}
@@ -178,7 +182,7 @@ export default function MinimalGameCard({
e.stopPropagation();
handleBetClick('home');
}}
- className="flex-1 py-2 text-xs text-zinc-500 border border-dotted border-zinc-800 hover:border-purple-600 hover:text-purple-400 transition-colors"
+ className="flex-1 py-2 text-xs text-zinc-500 border border-dotted border-zinc-800 hover:border-zinc-600 hover:text-white transition-colors"
>
{displayTeam1.split(' ').pop()}
{displayHomeOdds.toFixed(2)}x
@@ -187,7 +191,7 @@ export default function MinimalGameCard({
e.stopPropagation();
handleBetClick('away');
}}
- className="flex-1 py-2 text-xs text-zinc-500 border border-dotted border-zinc-800 hover:border-zinc-600 hover:text-zinc-300 transition-colors"
+ className="flex-1 py-2 text-xs text-zinc-500 border border-dotted border-zinc-800 hover:border-zinc-600 hover:text-white transition-colors"
>
{displayTeam2.split(' ').pop()}
{displayAwayOdds.toFixed(2)}x
diff --git a/tailwind.config.js b/tailwind.config.js
index 510e0c119..e842cecac 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -10,6 +10,38 @@ module.exports = {
extend: {
// UW Official Color Palette
colors: {
+ // Minimalist Design System - Semantic Tokens
+ // These are the single source of truth for the UI
+ // Change these to update the entire design system
+ 'primary': {
+ DEFAULT: '#f4f4f5', // zinc-100 - main CTA buttons
+ hover: '#ffffff', // white - button hover state
+ text: '#09090b', // zinc-950 - text on primary buttons
+ },
+ 'secondary': {
+ DEFAULT: 'transparent',
+ border: '#3f3f46', // zinc-700 - secondary button borders
+ 'border-hover': '#71717a', // zinc-500 - border hover
+ text: '#d4d4d8', // zinc-300 - secondary button text
+ 'text-hover': '#ffffff', // white - secondary text hover
+ },
+ 'background': {
+ DEFAULT: '#09090b', // zinc-950 - main background
+ secondary: '#18181b', // zinc-900 - secondary backgrounds
+ tertiary: '#27272a', // zinc-800 - cards/dividers
+ },
+ 'border': {
+ DEFAULT: '#27272a', // zinc-800 - default borders
+ light: '#3f3f46', // zinc-700 - lighter borders
+ },
+ 'text': {
+ DEFAULT: '#d4d4d8', // zinc-300 - default text
+ 'muted-light': '#a1a1aa', //zinc-400
+ muted: '#71717a', // zinc-500 - muted text
+ subtle: '#52525b', // zinc-600 - subtle text
+ placeholder: '#3f3f46', // zinc-700 - placeholder text
+ },
+
// Primary UW Colors
'uw-purple': {
DEFAULT: '#4B2E83',