diff --git a/package.json b/package.json index 1953e18..4f290f9 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "@tanstack/react-query": "^5.56.2", "@tanstack/react-query-devtools": "^5.81.2", "@tanstack/react-query-persist-client": "^5.85.9", + "@tanstack/react-router": "^1.136.18", "@types/marked": "^5.0.2", "@types/react-helmet": "^6.1.11", "@vercel/speed-insights": "^1.2.0", @@ -85,7 +86,6 @@ "react-helmet-async": "^2.0.5", "react-hook-form": "^7.53.0", "react-resizable-panels": "^2.1.3", - "react-router-dom": "^6.26.2", "recharts": "^2.12.7", "sonner": "^1.5.0", "tailwind-merge": "^2.5.2", @@ -101,6 +101,8 @@ "devDependencies": { "@playwright/test": "^1.54.1", "@tailwindcss/typography": "^0.5.15", + "@tanstack/router-devtools": "^1.136.18", + "@tanstack/router-vite-plugin": "^1.136.18", "@types/node": "^22.5.5", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3223399..c8361ae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -110,6 +110,9 @@ importers: '@tanstack/react-query-persist-client': specifier: ^5.85.9 version: 5.90.2(@tanstack/react-query@5.90.2(react@18.3.1))(react@18.3.1) + '@tanstack/react-router': + specifier: ^1.136.18 + version: 1.136.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/marked': specifier: ^5.0.2 version: 5.0.2 @@ -179,9 +182,6 @@ importers: react-resizable-panels: specifier: ^2.1.3 version: 2.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-router-dom: - specifier: ^6.26.2 - version: 6.30.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) recharts: specifier: ^2.12.7 version: 2.15.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -222,6 +222,12 @@ importers: '@tailwindcss/typography': specifier: ^0.5.15 version: 0.5.19(tailwindcss@3.4.17) + '@tanstack/router-devtools': + specifier: ^1.136.18 + version: 1.136.18(@tanstack/react-router@1.136.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@tanstack/router-core@1.136.17)(@types/node@22.18.6)(csstype@3.1.3)(jiti@1.21.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(solid-js@1.9.10)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + '@tanstack/router-vite-plugin': + specifier: ^1.136.18 + version: 1.136.18(@tanstack/react-router@1.136.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@5.4.20(@types/node@22.18.6)(terser@5.44.0)) '@types/node': specifier: ^22.5.5 version: 22.18.6 @@ -317,6 +323,10 @@ packages: resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} engines: {node: '>=6.9.0'} + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} + engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.27.3': resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} @@ -325,8 +335,8 @@ packages: resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.28.3': - resolution: {integrity: sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==} + '@babel/helper-create-class-features-plugin@7.28.5': + resolution: {integrity: sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -346,8 +356,8 @@ packages: resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} - '@babel/helper-member-expression-to-functions@7.27.1': - resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==} + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} engines: {node: '>=6.9.0'} '@babel/helper-module-imports@7.27.1': @@ -392,6 +402,10 @@ packages: resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} @@ -409,6 +423,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1': resolution: {integrity: sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==} engines: {node: '>=6.9.0'} @@ -457,6 +476,18 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-jsx@7.27.1': + resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.27.1': + resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-unicode-sets-regex@7.18.6': resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} engines: {node: '>=6.9.0'} @@ -745,6 +776,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-typescript@7.28.5': + resolution: {integrity: sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-unicode-escapes@7.27.1': resolution: {integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==} engines: {node: '>=6.9.0'} @@ -780,6 +817,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + '@babel/preset-typescript@7.28.5': + resolution: {integrity: sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/runtime@7.28.4': resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} @@ -792,10 +835,18 @@ packages: resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} engines: {node: '>=6.9.0'} + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} + engines: {node: '>=6.9.0'} + '@babel/types@7.28.4': resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} engines: {node: '>=6.9.0'} + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + '@csstools/color-helpers@5.1.0': resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} engines: {node: '>=18'} @@ -1866,10 +1917,6 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - '@remix-run/router@1.23.0': - resolution: {integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==} - engines: {node: '>=14.0.0'} - '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -2144,6 +2191,10 @@ packages: peerDependencies: tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' + '@tanstack/history@1.133.28': + resolution: {integrity: sha512-B7+x7eP2FFvi3fgd3rNH9o/Eixt+pp0zCIdGhnQbAJjFrlwIKGjGnwyJjhWJ5fMQlGks/E2LdDTqEV4W9Plx7g==} + engines: {node: '>=12'} + '@tanstack/query-async-storage-persister@5.90.2': resolution: {integrity: sha512-oyb7IHW85hsRdSZZNPu5dowQJFX3agOR/1O4M6Qc5V7s5dfnex5CqipEu0tbScNRMc2knna2ihQUiEQuTXWrEQ==} @@ -2173,6 +2224,98 @@ packages: peerDependencies: react: ^18 || ^19 + '@tanstack/react-router-devtools@1.136.18': + resolution: {integrity: sha512-yf/xZ978P3kVPh9i/lThydShnb2PG5hzXVor1GPCQ9UEjHC0zjDngz1VqWww9zNhTA2k9p9T7QCH8SVyOWG6rA==} + engines: {node: '>=12'} + peerDependencies: + '@tanstack/react-router': ^1.136.18 + '@tanstack/router-core': ^1.136.17 + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + peerDependenciesMeta: + '@tanstack/router-core': + optional: true + + '@tanstack/react-router@1.136.18': + resolution: {integrity: sha512-KXlzIZ5W6LKAl8Ot2p1CJJ7B6ZkXFnfaJEhOkPWHA0K7sTrQYOphMwdFBKyaYUCfoBrygqVM5g17mWMpQ4Va2A==} + engines: {node: '>=12'} + peerDependencies: + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + + '@tanstack/react-store@0.8.0': + resolution: {integrity: sha512-1vG9beLIuB7q69skxK9r5xiLN3ztzIPfSQSs0GfeqWGO2tGIyInZx0x1COhpx97RKaONSoAb8C3dxacWksm1ow==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/router-core@1.136.17': + resolution: {integrity: sha512-LQsR1Bg9ITRFt9qVU9yrsO6Z3izdva5jzow3s3yUaccBhbryBFQdA5f9HTCpuVidFbqC6eVbi0vGfRkyviK4jw==} + engines: {node: '>=12'} + + '@tanstack/router-devtools-core@1.136.17': + resolution: {integrity: sha512-KlJx89CtMnYDKz1tSBl4y9AiillaVRN81t/YQP2NVoyk1Xz6hkHrd/q/6QJmShHmnhzuY3kWaMzAhP6w0zdRdA==} + engines: {node: '>=12'} + peerDependencies: + '@tanstack/router-core': ^1.136.17 + csstype: ^3.0.10 + solid-js: '>=1.9.5' + peerDependenciesMeta: + csstype: + optional: true + + '@tanstack/router-devtools@1.136.18': + resolution: {integrity: sha512-A8kOCPutsgOko7/lT8H3ViFJGdPD4zDVM3Z6OHW4wYGZvFxa4z9igCPi/vsCvTqv/L/juDO7dT8Fj4S/z4pYGA==} + engines: {node: '>=12'} + peerDependencies: + '@tanstack/react-router': ^1.136.18 + csstype: ^3.0.10 + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + peerDependenciesMeta: + csstype: + optional: true + + '@tanstack/router-generator@1.136.17': + resolution: {integrity: sha512-4kUP5KmaEftN46wz7ZRxNeZSnfBDWfNxPBYDoB1JKx2cILbCvOyCu9l74EqDQ19XSZJ9n3FUYWgWg0pLjSKvtw==} + engines: {node: '>=12'} + + '@tanstack/router-plugin@1.136.18': + resolution: {integrity: sha512-k8MQ+My5njcls2Bvg8J3oi7GjvfNA7smvlPyinYQYCcgJ83e8QT6Mnb6pHOUAJqJx6lBiLfUz/7OC4f1hQ0Ljw==} + engines: {node: '>=12'} + peerDependencies: + '@rsbuild/core': '>=1.0.2' + '@tanstack/react-router': ^1.136.18 + vite: '>=5.0.0 || >=6.0.0 || >=7.0.0' + vite-plugin-solid: ^2.11.10 + webpack: '>=5.92.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + '@tanstack/react-router': + optional: true + vite: + optional: true + vite-plugin-solid: + optional: true + webpack: + optional: true + + '@tanstack/router-utils@1.133.19': + resolution: {integrity: sha512-WEp5D2gPxvlLDRXwD/fV7RXjYtqaqJNXKB/L6OyZEbT+9BG/Ib2d7oG9GSUZNNMGPGYAlhBUOi3xutySsk6rxA==} + engines: {node: '>=12'} + + '@tanstack/router-vite-plugin@1.136.18': + resolution: {integrity: sha512-KsOgFjUjknNF1Oyk5h4kynesfs44OOMBDSYvzko2PBL4KPmlisjqzFeRp+9Ohv/Jh56EYqlX21VfCOVDVFhMJg==} + engines: {node: '>=12'} + + '@tanstack/store@0.8.0': + resolution: {integrity: sha512-Om+BO0YfMZe//X2z0uLF2j+75nQga6TpTJgLJQBiq85aOyZNIhkCgleNcud2KQg4k4v9Y9l+Uhru3qWMPGTOzQ==} + + '@tanstack/virtual-file-routes@1.133.19': + resolution: {integrity: sha512-IKwZENsK7owmW1Lm5FhuHegY/SyQ8KqtL/7mTSnzoKJgfzhrrf9qwKB1rmkKkt+svUuy/Zw3uVEpZtUzQruWtA==} + engines: {node: '>=12'} + '@types/chai@5.2.2': resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} @@ -2338,6 +2481,10 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} + ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} + engines: {node: '>=14'} + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -2364,6 +2511,10 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + ast-types@0.16.1: + resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} + engines: {node: '>=4'} + async-function@1.0.0: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} @@ -2386,6 +2537,9 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} + babel-dead-code-elimination@1.0.10: + resolution: {integrity: sha512-DV5bdJZTzZ0zn0DC24v3jD7Mnidh6xhKa4GfKCbq3sfW8kaWhDdZjP3i81geA8T33tdYqWKw4D3fVv0CwEgKVA==} + babel-plugin-polyfill-corejs2@0.4.14: resolution: {integrity: sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==} peerDependencies: @@ -2535,6 +2689,9 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-es@2.0.0: + resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==} + core-js-compat@3.45.1: resolution: {integrity: sha512-tqTt5T4PzsMIZ430XGviK4vzYSoeNJ6CXODi6c/voxOT6IZqBht5/EKaSNnYiEjjRYxjVz7DQIsOsY0XNi8PIA==} @@ -2677,6 +2834,10 @@ packages: didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + diff@8.0.2: + resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} + engines: {node: '>=0.3.1'} + dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} @@ -2769,6 +2930,11 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + estree-walker@1.0.1: resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==} @@ -2954,6 +3120,11 @@ packages: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} + goober@2.1.18: + resolution: {integrity: sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==} + peerDependencies: + csstype: ^3.0.10 + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -3172,6 +3343,10 @@ packages: isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + isbot@5.1.32: + resolution: {integrity: sha512-VNfjM73zz2IBZmdShMfAUg10prm6t7HFUQmNAEOAVS4YH92ZrZcvkMcGX6cIgBJAzWDzPent/EeAtYEHNPNPBQ==} + engines: {node: '>=18'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -3640,19 +3815,6 @@ packages: react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - react-router-dom@6.30.1: - resolution: {integrity: sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==} - engines: {node: '>=14.0.0'} - peerDependencies: - react: '>=16.8' - react-dom: '>=16.8' - - react-router@6.30.1: - resolution: {integrity: sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - react: '>=16.8' - react-smooth@4.0.4: resolution: {integrity: sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==} peerDependencies: @@ -3690,6 +3852,10 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + recast@0.23.11: + resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} + engines: {node: '>= 4'} + recharts-scale@0.4.5: resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==} @@ -3797,6 +3963,26 @@ packages: serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + seroval-plugins@1.3.3: + resolution: {integrity: sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + + seroval-plugins@1.4.0: + resolution: {integrity: sha512-zir1aWzoiax6pbBVjoYVd0O1QQXgIL3eVGBMsBsNmM8Ukq90yGaWlfx0AB9dTS8GPqrOrbXn79vmItCUP9U3BQ==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + + seroval@1.3.2: + resolution: {integrity: sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==} + engines: {node: '>=10'} + + seroval@1.4.0: + resolution: {integrity: sha512-BdrNXdzlofomLTiRnwJTSEAaGKyHHZkbMXIywOh7zlzp4uZnXErEwl9XZ+N1hJSNpeTtNxWvVwN0wUzAIQ4Hpg==} + engines: {node: '>=10'} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -3854,6 +4040,9 @@ packages: smob@1.5.0: resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} + solid-js@1.9.10: + resolution: {integrity: sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==} + sonner@1.7.4: resolution: {integrity: sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw==} peerDependencies: @@ -3871,6 +4060,10 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} @@ -4002,6 +4195,9 @@ packages: tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tiny-warning@1.0.3: + resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -4120,6 +4316,10 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} + unplugin@2.3.10: + resolution: {integrity: sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==} + engines: {node: '>=18.12.0'} + upath@1.2.0: resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==} engines: {node: '>=4'} @@ -4155,6 +4355,11 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -4215,6 +4420,46 @@ packages: terser: optional: true + vite@7.2.4: + resolution: {integrity: sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + vitest@3.2.4: resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -4261,6 +4506,9 @@ packages: resolution: {integrity: sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==} engines: {node: '>=20'} + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} @@ -4469,6 +4717,14 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 + '@babel/generator@7.28.5': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + '@babel/helper-annotate-as-pure@7.27.3': dependencies: '@babel/types': 7.28.4 @@ -4481,15 +4737,15 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.28.3(@babel/core@7.28.4)': + '@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.4) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -4514,10 +4770,10 @@ snapshots: '@babel/helper-globals@7.28.0': {} - '@babel/helper-member-expression-to-functions@7.27.1': + '@babel/helper-member-expression-to-functions@7.28.5': dependencies: - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color @@ -4548,16 +4804,16 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-wrap-function': 7.28.3 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -4572,13 +4828,15 @@ snapshots: '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-option@7.27.1': {} '@babel/helper-wrap-function@7.28.3': dependencies: '@babel/template': 7.27.2 - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color @@ -4591,11 +4849,15 @@ snapshots: dependencies: '@babel/types': 7.28.4 + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -4622,7 +4884,7 @@ snapshots: dependencies: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -4640,6 +4902,16 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 @@ -4656,7 +4928,7 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.4) - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -4682,7 +4954,7 @@ snapshots: '@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.4) + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color @@ -4690,7 +4962,7 @@ snapshots: '@babel/plugin-transform-class-static-block@7.28.3(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.4) + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color @@ -4703,7 +4975,7 @@ snapshots: '@babel/helper-globals': 7.28.0 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.4) - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -4717,7 +4989,7 @@ snapshots: dependencies: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -4774,7 +5046,7 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -4819,8 +5091,8 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -4860,7 +5132,7 @@ snapshots: '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-transform-destructuring': 7.28.0(@babel/core@7.28.4) '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.4) - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -4893,7 +5165,7 @@ snapshots: '@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.4) + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color @@ -4902,7 +5174,7 @@ snapshots: dependencies: '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.4) + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color @@ -4956,6 +5228,17 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-transform-typescript@7.28.5(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.4) + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.4) + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 @@ -5059,9 +5342,20 @@ snapshots: dependencies: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 esutils: 2.0.3 + '@babel/preset-typescript@7.28.5(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.4) + transitivePeerDependencies: + - supports-color + '@babel/runtime@7.28.4': {} '@babel/template@7.27.2': @@ -5082,11 +5376,28 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/traverse@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + '@babel/types@7.28.4': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@csstools/color-helpers@5.1.0': {} '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': @@ -6038,8 +6349,6 @@ snapshots: '@radix-ui/rect@1.1.1': {} - '@remix-run/router@1.23.0': {} - '@rolldown/pluginutils@1.0.0-beta.27': {} '@rollup/plugin-babel@5.3.1(@babel/core@7.28.4)(rollup@2.79.2)': @@ -6261,6 +6570,8 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 3.4.17 + '@tanstack/history@1.133.28': {} + '@tanstack/query-async-storage-persister@5.90.2': dependencies: '@tanstack/query-core': 5.90.2 @@ -6291,6 +6602,170 @@ snapshots: '@tanstack/query-core': 5.90.2 react: 18.3.1 + '@tanstack/react-router-devtools@1.136.18(@tanstack/react-router@1.136.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@tanstack/router-core@1.136.17)(@types/node@22.18.6)(csstype@3.1.3)(jiti@1.21.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(solid-js@1.9.10)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)': + dependencies: + '@tanstack/react-router': 1.136.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tanstack/router-devtools-core': 1.136.17(@tanstack/router-core@1.136.17)(@types/node@22.18.6)(csstype@3.1.3)(jiti@1.21.7)(solid-js@1.9.10)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + vite: 7.2.4(@types/node@22.18.6)(jiti@1.21.7)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + optionalDependencies: + '@tanstack/router-core': 1.136.17 + transitivePeerDependencies: + - '@types/node' + - csstype + - jiti + - less + - lightningcss + - sass + - sass-embedded + - solid-js + - stylus + - sugarss + - terser + - tsx + - yaml + + '@tanstack/react-router@1.136.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/history': 1.133.28 + '@tanstack/react-store': 0.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tanstack/router-core': 1.136.17 + isbot: 5.1.32 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tiny-invariant: 1.3.3 + tiny-warning: 1.0.3 + + '@tanstack/react-store@0.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/store': 0.8.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + use-sync-external-store: 1.6.0(react@18.3.1) + + '@tanstack/router-core@1.136.17': + dependencies: + '@tanstack/history': 1.133.28 + '@tanstack/store': 0.8.0 + cookie-es: 2.0.0 + seroval: 1.4.0 + seroval-plugins: 1.4.0(seroval@1.4.0) + tiny-invariant: 1.3.3 + tiny-warning: 1.0.3 + + '@tanstack/router-devtools-core@1.136.17(@tanstack/router-core@1.136.17)(@types/node@22.18.6)(csstype@3.1.3)(jiti@1.21.7)(solid-js@1.9.10)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)': + dependencies: + '@tanstack/router-core': 1.136.17 + clsx: 2.1.1 + goober: 2.1.18(csstype@3.1.3) + solid-js: 1.9.10 + tiny-invariant: 1.3.3 + vite: 7.2.4(@types/node@22.18.6)(jiti@1.21.7)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + optionalDependencies: + csstype: 3.1.3 + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + + '@tanstack/router-devtools@1.136.18(@tanstack/react-router@1.136.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@tanstack/router-core@1.136.17)(@types/node@22.18.6)(csstype@3.1.3)(jiti@1.21.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(solid-js@1.9.10)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)': + dependencies: + '@tanstack/react-router': 1.136.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tanstack/react-router-devtools': 1.136.18(@tanstack/react-router@1.136.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@tanstack/router-core@1.136.17)(@types/node@22.18.6)(csstype@3.1.3)(jiti@1.21.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(solid-js@1.9.10)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + clsx: 2.1.1 + goober: 2.1.18(csstype@3.1.3) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + vite: 7.2.4(@types/node@22.18.6)(jiti@1.21.7)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + optionalDependencies: + csstype: 3.1.3 + transitivePeerDependencies: + - '@tanstack/router-core' + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - solid-js + - stylus + - sugarss + - terser + - tsx + - yaml + + '@tanstack/router-generator@1.136.17': + dependencies: + '@tanstack/router-core': 1.136.17 + '@tanstack/router-utils': 1.133.19 + '@tanstack/virtual-file-routes': 1.133.19 + prettier: 3.6.2 + recast: 0.23.11 + source-map: 0.7.6 + tsx: 4.20.6 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@tanstack/router-plugin@1.136.18(@tanstack/react-router@1.136.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@5.4.20(@types/node@22.18.6)(terser@5.44.0))': + dependencies: + '@babel/core': 7.28.4 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.4) + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 + '@tanstack/router-core': 1.136.17 + '@tanstack/router-generator': 1.136.17 + '@tanstack/router-utils': 1.133.19 + '@tanstack/virtual-file-routes': 1.133.19 + babel-dead-code-elimination: 1.0.10 + chokidar: 3.6.0 + unplugin: 2.3.10 + zod: 3.25.76 + optionalDependencies: + '@tanstack/react-router': 1.136.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + vite: 5.4.20(@types/node@22.18.6)(terser@5.44.0) + transitivePeerDependencies: + - supports-color + + '@tanstack/router-utils@1.133.19': + dependencies: + '@babel/core': 7.28.4 + '@babel/generator': 7.28.3 + '@babel/parser': 7.28.4 + '@babel/preset-typescript': 7.28.5(@babel/core@7.28.4) + ansis: 4.2.0 + diff: 8.0.2 + pathe: 2.0.3 + tinyglobby: 0.2.15 + transitivePeerDependencies: + - supports-color + + '@tanstack/router-vite-plugin@1.136.18(@tanstack/react-router@1.136.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@5.4.20(@types/node@22.18.6)(terser@5.44.0))': + dependencies: + '@tanstack/router-plugin': 1.136.18(@tanstack/react-router@1.136.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@5.4.20(@types/node@22.18.6)(terser@5.44.0)) + transitivePeerDependencies: + - '@rsbuild/core' + - '@tanstack/react-router' + - supports-color + - vite + - vite-plugin-solid + - webpack + + '@tanstack/store@0.8.0': {} + + '@tanstack/virtual-file-routes@1.133.19': {} + '@types/chai@5.2.2': dependencies: '@types/deep-eql': 4.0.2 @@ -6446,6 +6921,8 @@ snapshots: ansi-styles@6.2.3: {} + ansis@4.2.0: {} + any-promise@1.3.0: {} anymatch@3.1.3: @@ -6476,6 +6953,10 @@ snapshots: assertion-error@2.0.1: {} + ast-types@0.16.1: + dependencies: + tslib: 2.8.1 + async-function@1.0.0: {} async@3.2.6: {} @@ -6496,6 +6977,15 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 + babel-dead-code-elimination@1.0.10: + dependencies: + '@babel/core': 7.28.4 + '@babel/parser': 7.28.4 + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 + transitivePeerDependencies: + - supports-color + babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.28.4): dependencies: '@babel/compat-data': 7.28.4 @@ -6659,6 +7149,8 @@ snapshots: convert-source-map@2.0.0: {} + cookie-es@2.0.0: {} + core-js-compat@3.45.1: dependencies: browserslist: 4.26.2 @@ -6789,6 +7281,8 @@ snapshots: didyoumean@1.2.2: {} + diff@8.0.2: {} + dlv@1.1.3: {} dom-helpers@5.2.1: @@ -6969,6 +7463,8 @@ snapshots: escalade@3.2.0: {} + esprima@4.0.1: {} + estree-walker@1.0.1: {} estree-walker@2.0.2: {} @@ -7153,6 +7649,10 @@ snapshots: define-properties: 1.2.1 gopd: 1.2.0 + goober@2.1.18(csstype@3.1.3): + dependencies: + csstype: 3.1.3 + gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -7360,6 +7860,8 @@ snapshots: isarray@2.0.5: {} + isbot@5.1.32: {} + isexe@2.0.0: {} jackspeak@3.4.3: @@ -7778,18 +8280,6 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-router-dom@6.30.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@remix-run/router': 1.23.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-router: 6.30.1(react@18.3.1) - - react-router@6.30.1(react@18.3.1): - dependencies: - '@remix-run/router': 1.23.0 - react: 18.3.1 - react-smooth@4.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: fast-equals: 5.3.2 @@ -7829,6 +8319,14 @@ snapshots: dependencies: picomatch: 2.3.1 + recast@0.23.11: + dependencies: + ast-types: 0.16.1 + esprima: 4.0.1 + source-map: 0.6.1 + tiny-invariant: 1.3.3 + tslib: 2.8.1 + recharts-scale@0.4.5: dependencies: decimal.js-light: 2.5.1 @@ -7981,6 +8479,18 @@ snapshots: dependencies: randombytes: 2.1.0 + seroval-plugins@1.3.3(seroval@1.3.2): + dependencies: + seroval: 1.3.2 + + seroval-plugins@1.4.0(seroval@1.4.0): + dependencies: + seroval: 1.4.0 + + seroval@1.3.2: {} + + seroval@1.4.0: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -8056,6 +8566,12 @@ snapshots: smob@1.5.0: {} + solid-js@1.9.10: + dependencies: + csstype: 3.1.3 + seroval: 1.3.2 + seroval-plugins: 1.3.3(seroval@1.3.2) + sonner@1.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 @@ -8070,6 +8586,8 @@ snapshots: source-map@0.6.1: {} + source-map@0.7.6: {} + source-map@0.8.0-beta.0: dependencies: whatwg-url: 7.1.0 @@ -8259,6 +8777,8 @@ snapshots: tiny-invariant@1.3.3: {} + tiny-warning@1.0.3: {} + tinybench@2.9.0: {} tinyexec@0.3.2: {} @@ -8377,6 +8897,13 @@ snapshots: universalify@2.0.1: {} + unplugin@2.3.10: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.15.0 + picomatch: 4.0.3 + webpack-virtual-modules: 0.6.2 + upath@1.2.0: {} update-browserslist-db@1.1.3(browserslist@4.26.2): @@ -8404,6 +8931,10 @@ snapshots: dependencies: react: 18.3.1 + use-sync-external-store@1.6.0(react@18.3.1): + dependencies: + react: 18.3.1 + util-deprecate@1.0.2: {} vaul@0.9.9(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): @@ -8471,6 +9002,22 @@ snapshots: fsevents: 2.3.3 terser: 5.44.0 + vite@7.2.4(@types/node@22.18.6)(jiti@1.21.7)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1): + dependencies: + esbuild: 0.25.10 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.52.2 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 22.18.6 + fsevents: 2.3.3 + jiti: 1.21.7 + terser: 5.44.0 + tsx: 4.20.6 + yaml: 2.8.1 + vitest@3.2.4(@types/node@22.18.6)(@vitest/ui@3.2.4)(jsdom@27.0.0(postcss@8.5.6))(terser@5.44.0): dependencies: '@types/chai': 5.2.2 @@ -8523,6 +9070,8 @@ snapshots: webidl-conversions@8.0.0: {} + webpack-virtual-modules@0.6.2: {} + whatwg-encoding@3.1.1: dependencies: iconv-lite: 0.6.3 diff --git a/src/App.tsx b/src/App.tsx deleted file mode 100644 index 75092b4..0000000 --- a/src/App.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { SpeedInsights } from "@vercel/speed-insights/react"; -import { Toaster } from "@/components/ui/toaster"; -import { Toaster as Sonner } from "@/components/ui/sonner"; -import { TooltipProvider } from "@/components/ui/tooltip"; -import { BrowserRouter } from "react-router-dom"; -import { HelmetProvider } from "react-helmet-async"; -import { CookieConsentBanner } from "@/components/layout/legal/CookieConsentBanner"; -import { OfflineIndicator } from "@/components/ui/OfflineIndicator"; -import { - getSubdomainInfo, - shouldRedirectFromWww, - getNonWwwRedirectUrl, -} from "@/lib/subdomain"; -import { AuthProvider } from "@/contexts/AuthContext"; -import { useState, useEffect } from "react"; -import { FestivalEditionProvider } from "./contexts/FestivalEditionContext"; -import { AppRoutes } from "./components/router/AppRoutes"; - -function App() { - const [subdomainInfo] = useState(() => getSubdomainInfo()); - - // Redirect www.getupline.com to getupline.com - useEffect(() => { - if (shouldRedirectFromWww()) { - window.location.href = getNonWwwRedirectUrl(); - } - }, []); - - return ( - - - - - - - - - - - - - - - - - - ); -} - -export default App; diff --git a/src/components/invite/useInviteValidation.ts b/src/components/invite/useInviteValidation.ts index e4c585c..66e3c93 100644 --- a/src/components/invite/useInviteValidation.ts +++ b/src/components/invite/useInviteValidation.ts @@ -1,5 +1,5 @@ import { useState, useEffect } from "react"; -import { useSearchParams } from "react-router-dom"; +import { useSearch } from "@tanstack/react-router"; import { useToast } from "@/components/ui/use-toast"; import { useInviteValidationQuery, @@ -7,17 +7,17 @@ import { } from "@/hooks/queries/useInviteValidationQuery"; export function useInviteValidation() { - const [searchParams] = useSearchParams(); + const search = useSearch(); const [inviteToken, setInviteToken] = useState(null); const { toast } = useToast(); // Extract token from search params useEffect(() => { - const token = searchParams.get("invite"); + const token = search.invite; if (token) { setInviteToken(token); } - }, [searchParams]); + }, [search]); const { data: inviteValidation, diff --git a/src/components/layout/AppFooter.tsx b/src/components/layout/AppFooter.tsx index 5c03054..d136815 100644 --- a/src/components/layout/AppFooter.tsx +++ b/src/components/layout/AppFooter.tsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { Link } from "react-router-dom"; +import { Link } from "@tanstack/react-router"; import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; import { Settings } from "lucide-react"; diff --git a/src/components/layout/AppHeader/AdminActions.tsx b/src/components/layout/AppHeader/AdminActions.tsx index 7436246..7ee32c1 100644 --- a/src/components/layout/AppHeader/AdminActions.tsx +++ b/src/components/layout/AppHeader/AdminActions.tsx @@ -1,6 +1,6 @@ import { Menu, Settings } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { Link } from "react-router-dom"; +import { Link } from "@tanstack/react-router"; import { DropdownMenu, DropdownMenuContent, diff --git a/src/components/layout/AppHeader/AppBranding.tsx b/src/components/layout/AppHeader/AppBranding.tsx index f36925a..ebc2459 100644 --- a/src/components/layout/AppHeader/AppBranding.tsx +++ b/src/components/layout/AppHeader/AppBranding.tsx @@ -1,4 +1,4 @@ -import { Link } from "react-router-dom"; +import { Link } from "@tanstack/react-router"; import { Music } from "lucide-react"; import { useAuth } from "@/contexts/AuthContext"; import { useFestivalEdition } from "@/contexts/FestivalEditionContext"; diff --git a/src/components/layout/AppHeader/Navigation.tsx b/src/components/layout/AppHeader/Navigation.tsx index c6d46ba..7346c97 100644 --- a/src/components/layout/AppHeader/Navigation.tsx +++ b/src/components/layout/AppHeader/Navigation.tsx @@ -1,4 +1,4 @@ -import { Link, useNavigate } from "react-router-dom"; +import { Link } from "@tanstack/react-router"; import { ArrowLeft, Users } from "lucide-react"; import { Button } from "@/components/ui/button"; import { @@ -49,7 +49,6 @@ export function Navigation({ isMobile, }: NavigationProps) { const { user } = useAuth(); - const navigate = useNavigate(); return (
@@ -58,7 +57,7 @@ export function Navigation({ navigate(-1)} + onClick={() => window.history.back()} className="border-purple-400/50 text-purple-300 hover:bg-purple-600 hover:text-white hover:border-purple-600 transition-colors" tooltip={backLabel} isMobile={isMobile} diff --git a/src/components/layout/AppHeader/UserMenu.tsx b/src/components/layout/AppHeader/UserMenu.tsx index fc57187..f2cd425 100644 --- a/src/components/layout/AppHeader/UserMenu.tsx +++ b/src/components/layout/AppHeader/UserMenu.tsx @@ -11,7 +11,7 @@ import { import { Button } from "@/components/ui/button"; import { UserAvatar } from "./UserAvatar"; import { Database } from "@/integrations/supabase/types"; -import { Link } from "react-router-dom"; +import { Link } from "@tanstack/react-router"; import { useUserPermissionsQuery } from "@/hooks/queries/auth/useUserPermissions"; type Profile = Database["public"]["Tables"]["profiles"]["Row"]; diff --git a/src/components/router/EditionRoutes.tsx b/src/components/router/EditionRoutes.tsx deleted file mode 100644 index c6f2c01..0000000 --- a/src/components/router/EditionRoutes.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { Navigate, Route } from "react-router-dom"; -import EditionLayout from "@/pages/EditionView/EditionLayout"; -import { SetDetails } from "@/pages/SetDetails"; -import { ExploreSetPage } from "@/pages/ExploreSetPage/ExploreSetPage"; - -// Tab components -import { ArtistsTab } from "@/pages/EditionView/tabs/ArtistsTab/ArtistsTab"; -import { MapTab } from "@/pages/EditionView/tabs/MapTab"; -import { InfoTab } from "@/pages/EditionView/tabs/InfoTab"; -import { SocialTab } from "@/pages/EditionView/tabs/SocialTab"; -import { ScheduleTabTimeline } from "@/pages/EditionView/tabs/ScheduleTab/TimelineTab"; -import { ScheduleTabList } from "@/pages/EditionView/tabs/ScheduleTab/list/ListTab"; -import { ScheduleTab } from "@/pages/EditionView/tabs/ScheduleTab"; - -interface EditionRoutesProps { - basePath: string; - WrapperComponent?: React.ComponentType<{ component: React.ComponentType }>; -} - -export function createEditionRoutes({ - basePath, - WrapperComponent, -}: EditionRoutesProps) { - const EditionComponent = WrapperComponent - ? () => - : EditionLayout; - - const SetDetailsComponent = WrapperComponent - ? () => - : SetDetails; - - const ExploreComponent = WrapperComponent - ? () => - : ExploreSetPage; - - return [ - }> - {/* Nested tab routes */} - } /> - } /> - } /> - } /> - } /> - }> - } /> - } /> - } /> - - , - } - />, - } - />, - ]; -} diff --git a/src/components/router/GlobalRoutes.tsx b/src/components/router/GlobalRoutes.tsx deleted file mode 100644 index bc8794e..0000000 --- a/src/components/router/GlobalRoutes.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { Navigate, Route, Routes } from "react-router-dom"; - -import AdminAnalytics from "@/pages/admin/Analytics/AdminAnalytics"; -import AdminFestivals from "@/pages/admin/festivals/AdminFestivals"; -import FestivalDetail from "@/pages/admin/festivals/FestivalDetail"; -import FestivalEdition from "@/pages/admin/festivals/FestivalEdition"; -import FestivalSets from "@/pages/admin/festivals/FestivalSets"; -import FestivalStages from "@/pages/admin/festivals/FestivalStages"; -import AdminLayout from "@/pages/admin/AdminLayout"; -import CookiePolicy from "@/pages/legal/CookiePolicy"; -import GroupDetail from "@/pages/groups/GroupDetail"; -import Groups from "@/pages/groups/Groups"; -import PrivacyPolicy from "@/pages/legal/PrivacyPolicy"; -import TermsOfService from "@/pages/legal/TermsOfService"; -import NotFound from "@/pages/NotFound"; -import { AdminRolesTable } from "@/pages/admin/Roles/AdminRolesTable"; -import { DuplicateArtistsPage } from "@/pages/admin/ArtistsManagement/DuplicateArtistsPage"; -import { ArtistBulkEditor } from "@/pages/admin/ArtistsManagement/ArtistBulkEditor"; -import { CSVImportPage } from "@/pages/admin/festivals/CSVImportPage"; - -export function GlobalRoutes() { - return ( - - {/* Global routes (not scoped to festival/edition) */} - } /> - } /> - - {/* Admin routes */} - }> - } /> - } /> - } /> - } /> - } /> - } /> - } - /> - }> - }> - }> - } /> - } /> - } /> - - - - - - {/* Legal pages */} - } /> - } /> - } /> - - } /> - - ); -} diff --git a/src/components/router/MainDomainRoutes.tsx b/src/components/router/MainDomainRoutes.tsx deleted file mode 100644 index a0e4889..0000000 --- a/src/components/router/MainDomainRoutes.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Routes, Route } from "react-router-dom"; - -import { SubdomainRedirect } from "./SubdomainRedirect"; -import FestivalSelection from "@/pages/FestivalSelection"; -import EditionSelection from "@/pages/EditionSelection"; -import { GlobalRoutes } from "./GlobalRoutes"; -import { createEditionRoutes } from "./EditionRoutes"; -import { useState } from "react"; - -/** - * Routes for main domain access (getupline.com) - * Includes festival selection and full admin interface - */ -export function MainDomainRoutes() { - const [editionRoutes] = useState( - createEditionRoutes({ - basePath: "/festivals/:festivalSlug/editions/:editionSlug", - WrapperComponent: SubdomainRedirect, - }), - ); - - return ( - <> - - {/* Festival/Edition Selection Routes */} - } /> - {/* Festival routes redirect to subdomains */} - } - /> - {/* Edition routes with subdomain redirect wrapper */} - {editionRoutes} - - } /> - - - ); -} diff --git a/src/components/router/SubdomainRedirect.tsx b/src/components/router/SubdomainRedirect.tsx deleted file mode 100644 index 5aa77b7..0000000 --- a/src/components/router/SubdomainRedirect.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { useEffect } from "react"; -import { useParams } from "react-router-dom"; -import { - createFestivalSubdomainUrl, - isMainGetuplineDomain, -} from "@/lib/subdomain"; - -interface SubdomainRedirectProps { - component: React.ComponentType; -} -/** - * Component that redirects main domain festival URLs to subdomains - * Used for: /festivals/boom-festival -> boom-festival.getupline.com - * For localhost development, renders the appropriate component directly - */ -export function SubdomainRedirect({ - component: Component, -}: SubdomainRedirectProps) { - const { festivalSlug, editionSlug, setSlug } = useParams<{ - festivalSlug?: string; - editionSlug?: string; - setSlug?: string; - }>(); - - const shouldNotRedirect = !isMainGetuplineDomain(); - - useEffect(() => { - if (!festivalSlug || shouldNotRedirect) { - return; - } - - // Build the target path based on current route - let targetPath = "/"; - - if (editionSlug && setSlug) { - targetPath = `/editions/${editionSlug}/sets/${setSlug}`; - } else if (editionSlug && window.location.pathname.includes("schedule")) { - targetPath = `/editions/${editionSlug}/schedule`; - } else if (editionSlug) { - targetPath = `/editions/${editionSlug}`; - } - - // Redirect to subdomain - const subdomainUrl = createFestivalSubdomainUrl(festivalSlug, targetPath); - window.location.href = subdomainUrl; - }, [festivalSlug, editionSlug, setSlug, shouldNotRedirect]); - - if (shouldNotRedirect) { - return ; - } - - // Show loading message while redirecting (production only) - return ( - <> -
-
-
Redirecting...
-
- Taking you to{" "} - {festivalSlug - ? `${festivalSlug}.getupline.com` - : "the festival site"} -
-
-
- {/* */} - - ); -} diff --git a/src/components/router/SubdomainRoutes.tsx b/src/components/router/SubdomainRoutes.tsx deleted file mode 100644 index 51b16c1..0000000 --- a/src/components/router/SubdomainRoutes.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Routes, Route } from "react-router-dom"; -import EditionSelection from "@/pages/EditionSelection"; -import { GlobalRoutes } from "./GlobalRoutes"; -import { createEditionRoutes } from "./EditionRoutes"; -import { useState } from "react"; - -/** - * Routes for subdomain access (boom-festival.getupline.com) - * Root path shows edition selection for the festival - */ -export function SubdomainRoutes() { - const [editionRoutes] = useState(() => - createEditionRoutes({ - basePath: "/editions/:editionSlug", - }), - ); - - return ( - - } /> - {/* Edition-specific routes */} - {editionRoutes} - - } /> - - ); -} diff --git a/src/contexts/FestivalEditionContext.tsx b/src/contexts/FestivalEditionContext.tsx index 59faa47..40402e3 100644 --- a/src/contexts/FestivalEditionContext.tsx +++ b/src/contexts/FestivalEditionContext.tsx @@ -5,7 +5,7 @@ import { useEffect, useMemo, } from "react"; -import { matchPath, Navigate, useLocation } from "react-router-dom"; +import { useLocation, useNavigate } from "@tanstack/react-router"; import { useFestivalBySlugQuery } from "@/hooks/queries/festivals/useFestivalBySlug"; import { Festival } from "@/hooks/queries/festivals/types"; import { useFestivalEditionBySlugQuery } from "@/hooks/queries/festivals/editions/useFestivalEditionBySlug"; @@ -46,10 +46,12 @@ function getSlugs(pathname: string) { let basePath = ""; // For main domain, extract festival slug from URL path if (pathname.includes("/festivals/")) { - const match = matchPath({ path: "/festivals/:festivalSlug/*" }, pathname); - festivalSlug = match?.params.festivalSlug || festivalSlug || ""; - pathname = pathname.replace(`/festivals/${festivalSlug}`, ""); - basePath = `/festivals/${festivalSlug}`; + const festivalMatch = pathname.match(/\/festivals\/([^/]+)/); + if (festivalMatch) { + festivalSlug = festivalMatch[1]; + pathname = pathname.replace(`/festivals/${festivalSlug}`, ""); + basePath = `/festivals/${festivalSlug}`; + } } if (!pathname.includes("/editions")) { @@ -59,26 +61,8 @@ function getSlugs(pathname: string) { }; } - const matchWithSlash = matchPath( - { path: "/editions/:editionSlug/*" }, - pathname, - ); - - if (matchWithSlash) { - const editionSlug = matchWithSlash?.params.editionSlug || ""; - - return { - basePath: basePath + `/editions/${editionSlug}`, - festivalSlug, - editionSlug, - }; - } - const matchWithoutSlash = matchPath( - { path: "/editions/:editionSlug" }, - pathname, - ); - - const editionSlug = matchWithoutSlash?.params.editionSlug || ""; + const editionMatch = pathname.match(/\/editions\/([^/]+)/); + const editionSlug = editionMatch ? editionMatch[1] : ""; return { basePath: basePath + `/editions/${editionSlug}`, @@ -98,6 +82,7 @@ export function FestivalEditionProvider({ children, }: PropsWithChildren) { const { festivalSlug, editionSlug, basePath } = useParseSlugs(); + const navigate = useNavigate(); const festivalQuery = useFestivalBySlugQuery(festivalSlug); @@ -147,6 +132,12 @@ export function FestivalEditionProvider({ } }, [editionQuery.error, toast, editionSlug]); + useEffect(() => { + if (festivalQuery.error || editionQuery.error) { + navigate({ to: "/" }); + } + }, [festivalQuery.error, editionQuery.error, navigate]); + if (festivalQuery.error || editionQuery.error) { return ( @@ -156,7 +147,6 @@ export function FestivalEditionProvider({

No valid festival or edition found

-
diff --git a/src/hooks/useTimelineUrlState.ts b/src/hooks/useTimelineUrlState.ts index 905b636..d760d66 100644 --- a/src/hooks/useTimelineUrlState.ts +++ b/src/hooks/useTimelineUrlState.ts @@ -1,5 +1,6 @@ -import { useCallback } from "react"; -import { useSearchParams } from "react-router-dom"; +import { useCallback, useMemo } from "react"; +import { useSearch, useNavigate } from "@tanstack/react-router"; +import type { TimelineSearch } from "@/lib/searchSchemas"; export type TimelineView = "horizontal" | "list"; export type TimeFilter = "all" | "morning" | "afternoon" | "evening"; @@ -19,61 +20,62 @@ const defaultState: TimelineState = { }; export function useTimelineUrlState() { - const [searchParams, setSearchParams] = useSearchParams(); + const search = useSearch(); + const navigate = useNavigate(); const getStateFromUrl = useCallback((): TimelineState => { return { - timelineView: - (searchParams.get("view") as TimelineView) || defaultState.timelineView, - selectedDay: searchParams.get("day") || defaultState.selectedDay, - selectedTime: - (searchParams.get("time") as TimeFilter) || defaultState.selectedTime, + timelineView: search.view || defaultState.timelineView, + selectedDay: search.day || defaultState.selectedDay, + selectedTime: search.time || defaultState.selectedTime, selectedStages: - searchParams.get("stages")?.split(",").filter(Boolean) || + search.stages?.split(",").filter(Boolean) || defaultState.selectedStages, }; - }, [searchParams]); + }, [search]); const updateTimelineState = useCallback( (updates: Partial) => { const currentState = getStateFromUrl(); const newState = { ...currentState, ...updates }; - const newParams = new URLSearchParams(); + const newSearchParams: TimelineSearch = {}; // Only add non-default values to URL if (newState.timelineView !== defaultState.timelineView) { - newParams.set("view", newState.timelineView); + newSearchParams.view = newState.timelineView; } if (newState.selectedDay !== defaultState.selectedDay) { - newParams.set("day", newState.selectedDay); + newSearchParams.day = newState.selectedDay; } if (newState.selectedTime !== defaultState.selectedTime) { - newParams.set("time", newState.selectedTime); + newSearchParams.time = newState.selectedTime; } if (newState.selectedStages.length > 0) { - newParams.set("stages", newState.selectedStages.join(",")); + newSearchParams.stages = newState.selectedStages.join(","); } - setSearchParams(newParams, { replace: true }); + navigate({ to: ".", search: () => newSearchParams, replace: true }); }, - [getStateFromUrl, setSearchParams], + [getStateFromUrl, navigate], ); const clearTimelineFilters = useCallback(() => { const currentState = getStateFromUrl(); - const newParams = new URLSearchParams(); + const newSearchParams: TimelineSearch = {}; // Keep view when clearing filters if (currentState.timelineView !== defaultState.timelineView) { - newParams.set("view", currentState.timelineView); + newSearchParams.view = currentState.timelineView; } - setSearchParams(newParams, { replace: true }); - }, [getStateFromUrl, setSearchParams]); + navigate({ to: ".", search: () => newSearchParams, replace: true }); + }, [getStateFromUrl, navigate]); + + const state = useMemo(() => getStateFromUrl(), [getStateFromUrl]); return { - state: getStateFromUrl(), + state, updateState: updateTimelineState, clearFilters: clearTimelineFilters, }; diff --git a/src/hooks/useUrlState.ts b/src/hooks/useUrlState.ts index b52bb23..a2453e5 100644 --- a/src/hooks/useUrlState.ts +++ b/src/hooks/useUrlState.ts @@ -1,5 +1,6 @@ import { useCallback } from "react"; -import { useSearchParams } from "react-router-dom"; +import { useNavigate, useSearch } from "@tanstack/react-router"; +import type { FilterSortSearch } from "@/lib/searchSchemas"; export type SortOption = | "name-asc" @@ -20,7 +21,7 @@ export interface FilterSortState { groupId?: string; invite?: string; sortLocked?: boolean; - votePerspective?: string; // For filtering votes by group + votePerspective?: string; } const defaultState: FilterSortState = { @@ -37,89 +38,85 @@ const defaultState: FilterSortState = { }; export function useUrlState() { - const [searchParams, setSearchParams] = useSearchParams(); + const navigate = useNavigate(); + const search = useSearch(); const getStateFromUrl = useCallback((): FilterSortState => { return { - sort: (searchParams.get("sort") as SortOption) || defaultState.sort, + sort: search.sort || defaultState.sort, stages: - searchParams.get("stages")?.split(",").filter(Boolean) || - defaultState.stages, + search.stages?.split(",").filter(Boolean) || defaultState.stages, genres: - searchParams.get("genres")?.split(",").filter(Boolean) || - defaultState.genres, + search.genres?.split(",").filter(Boolean) || defaultState.genres, minRating: - parseInt(searchParams.get("minRating") || "0") || - defaultState.minRating, - timelineView: - (searchParams.get("timelineView") as TimelineView) || - defaultState.timelineView, + parseInt(search.minRating || "0") || defaultState.minRating, + timelineView: search.timelineView || defaultState.timelineView, use24Hour: - searchParams.get("use24Hour") === "true" || defaultState.use24Hour, - groupId: searchParams.get("groupId") || defaultState.groupId, - invite: searchParams.get("invite") || defaultState.invite, + search.use24Hour === "true" || defaultState.use24Hour, + groupId: search.groupId || defaultState.groupId, + invite: search.invite || defaultState.invite, sortLocked: - searchParams.get("sortLocked") === "true" || defaultState.sortLocked, + search.sortLocked === "true" || defaultState.sortLocked, votePerspective: - searchParams.get("votePerspective") || defaultState.votePerspective, + search.votePerspective || defaultState.votePerspective, }; - }, [searchParams]); + }, [search]); const updateUrlState = useCallback( (updates: Partial) => { const currentState = getStateFromUrl(); const newState = { ...currentState, ...updates }; - const newParams = new URLSearchParams(); + const newParams: FilterSortSearch = {}; // Only add non-default values to URL if (newState.sort !== defaultState.sort) { - newParams.set("sort", newState.sort); + newParams.sort = newState.sort; } if (newState.stages.length > 0) { - newParams.set("stages", newState.stages.join(",")); + newParams.stages = newState.stages.join(","); } if (newState.genres.length > 0) { - newParams.set("genres", newState.genres.join(",")); + newParams.genres = newState.genres.join(","); } if (newState.minRating > 0) { - newParams.set("minRating", newState.minRating.toString()); + newParams.minRating = newState.minRating.toString(); } if (newState.timelineView !== defaultState.timelineView) { - newParams.set("timelineView", newState.timelineView); + newParams.timelineView = newState.timelineView; } if (newState.use24Hour !== defaultState.use24Hour) { - newParams.set("use24Hour", newState.use24Hour.toString()); + newParams.use24Hour = newState.use24Hour.toString(); } if (newState.groupId) { - newParams.set("groupId", newState.groupId); + newParams.groupId = newState.groupId; } if (newState.invite) { - newParams.set("invite", newState.invite); + newParams.invite = newState.invite; } if (newState.sortLocked) { - newParams.set("sortLocked", newState.sortLocked.toString()); + newParams.sortLocked = newState.sortLocked.toString(); } if (newState.votePerspective) { - newParams.set("votePerspective", newState.votePerspective); + newParams.votePerspective = newState.votePerspective; } - setSearchParams(newParams, { replace: true }); + navigate({ to: ".", search: () => newParams, replace: true }); }, - [getStateFromUrl, setSearchParams], + [getStateFromUrl, navigate], ); const clearFilters = useCallback(() => { const currentState = getStateFromUrl(); - const newParams = new URLSearchParams(); + const newParams: FilterSortSearch = {}; // Keep invite parameter when clearing filters if (currentState.invite) { - newParams.set("invite", currentState.invite); + newParams.invite = currentState.invite; } - setSearchParams(newParams, { replace: true }); - }, [getStateFromUrl, setSearchParams]); + navigate({ to: ".", search: () => newParams, replace: true }); + }, [getStateFromUrl, navigate]); return { state: getStateFromUrl(), diff --git a/src/lib/searchSchemas.ts b/src/lib/searchSchemas.ts new file mode 100644 index 0000000..f32d63d --- /dev/null +++ b/src/lib/searchSchemas.ts @@ -0,0 +1,35 @@ +import { z } from "zod"; + +export const sortOptionSchema = z.enum([ + "name-asc", + "name-desc", + "rating-desc", + "popularity-desc", + "date-asc", +]); + +export const timelineViewSchema = z.enum(["horizontal", "list"]); + +export const filterSortSearchSchema = z.object({ + sort: sortOptionSchema.optional(), + stages: z.string().optional(), + genres: z.string().optional(), + minRating: z.string().optional(), + timelineView: timelineViewSchema.optional(), + use24Hour: z.string().optional(), + groupId: z.string().optional(), + invite: z.string().optional(), + sortLocked: z.string().optional(), + votePerspective: z.string().optional(), +}); + +export type FilterSortSearch = z.infer; + +export const timelineSearchSchema = z.object({ + view: timelineViewSchema.optional(), + day: z.string().optional(), + time: z.enum(["all", "morning", "afternoon", "evening"]).optional(), + stages: z.string().optional(), +}); + +export type TimelineSearch = z.infer; diff --git a/src/main.tsx b/src/main.tsx index 07b40a8..41a44d7 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,7 +2,9 @@ import { createRoot } from "react-dom/client"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { PostHogProvider } from "posthog-js/react"; -import App from "./App.tsx"; +import { RouterProvider, createRouter } from "@tanstack/react-router"; +import { routeTree } from "./routeTree.gen"; +import NotFound from "./pages/NotFound"; import "./index.css"; const queryClient = new QueryClient({ @@ -17,18 +19,34 @@ const queryClient = new QueryClient({ }, }); +const router = createRouter({ + routeTree, + context: { + queryClient, + }, + defaultPreload: "intent", + defaultPreloadStaleTime: 0, + defaultNotFoundComponent: NotFound, +}); + +declare module "@tanstack/react-router" { + interface Register { + router: typeof router; + } +} + createRoot(document.getElementById("root")!).render( - + , diff --git a/src/pages/EditionSelection.tsx b/src/pages/EditionSelection.tsx index 97f32cc..9a8331c 100644 --- a/src/pages/EditionSelection.tsx +++ b/src/pages/EditionSelection.tsx @@ -10,7 +10,7 @@ import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { useFestivalEdition } from "@/contexts/FestivalEditionContext"; import { AppHeader } from "@/components/layout/AppHeader"; -import { Link, useNavigate } from "react-router-dom"; +import { Link, useNavigate } from "@tanstack/react-router"; import { useFestivalEditionsForFestivalQuery } from "@/hooks/queries/festivals/editions/useFestivalEditionsForFestival"; import { FestivalEdition } from "@/hooks/queries/festivals/editions/types"; import { useEffect } from "react"; @@ -31,11 +31,20 @@ export default function EditionSelection() { ) { // If we're on a subdomain, navigate to /editions/slug // If we're on main domain, navigate to /festivals/festival-slug/editions/slug - const targetPath = subdomainInfo.isSubdomain - ? `/editions/${editionListQuery.data[0].slug}` - : `/festivals/${festival?.slug}/editions/${editionListQuery.data[0].slug}`; + const editionSlug = editionListQuery.data[0].slug; + const festivalSlug = festival.slug; - navigate(targetPath); + if (subdomainInfo.isSubdomain) { + navigate({ + to: "/festivals/$festivalSlug/editions/$editionSlug", + params: { festivalSlug, editionSlug }, + }); + } else { + navigate({ + to: "/festivals/$festivalSlug/editions/$editionSlug", + params: { festivalSlug, editionSlug }, + }); + } } }, [ editionListQuery.data, diff --git a/src/pages/EditionView/EditionLayout.tsx b/src/pages/EditionView/EditionLayout.tsx index 253da08..c0645c5 100644 --- a/src/pages/EditionView/EditionLayout.tsx +++ b/src/pages/EditionView/EditionLayout.tsx @@ -2,7 +2,7 @@ import { AppHeader } from "@/components/layout/AppHeader"; import { MainTabNavigation } from "./TabNavigation/TabNavigation"; import ErrorBoundary from "@/components/ErrorBoundary"; import { useFestivalEdition } from "@/contexts/FestivalEditionContext"; -import { Outlet } from "react-router-dom"; +import { Outlet } from "@tanstack/react-router"; import { useCustomLinksQuery } from "@/hooks/queries/custom-links/useCustomLinks"; export default function EditionView() { diff --git a/src/pages/EditionView/TabNavigation/DesktopTabButton.tsx b/src/pages/EditionView/TabNavigation/DesktopTabButton.tsx index 7f9b27c..8ef726c 100644 --- a/src/pages/EditionView/TabNavigation/DesktopTabButton.tsx +++ b/src/pages/EditionView/TabNavigation/DesktopTabButton.tsx @@ -1,26 +1,43 @@ import { cn } from "@/lib/utils"; -import { NavLink } from "react-router-dom"; +import { Link, useParams } from "@tanstack/react-router"; import { TabButtonProps } from "./types"; -export function DesktopTabButton({ config, basePath }: TabButtonProps) { +const tabRoutes = { + sets: "/festivals/$festivalSlug/editions/$editionSlug/sets", + schedule: "/festivals/$festivalSlug/editions/$editionSlug/schedule", + map: "/festivals/$festivalSlug/editions/$editionSlug/map", + info: "/festivals/$festivalSlug/editions/$editionSlug/info", + social: "/festivals/$festivalSlug/editions/$editionSlug/social", + explore: "/festivals/$festivalSlug/editions/$editionSlug/explore", +} as const; + +export function DesktopTabButton({ config }: TabButtonProps) { + const { festivalSlug, editionSlug } = useParams(); + return ( - - cn( - ` - flex items-center justify-center gap-2 + to={tabRoutes[config.key]} + params={{ festivalSlug, editionSlug }} + activeProps={{ + className: cn( + `flex items-center justify-center gap-2 + px-6 py-3 rounded-lg + transition-all duration-200 active:scale-95 + bg-purple-600 text-white shadow-lg`, + ), + }} + inactiveProps={{ + className: cn( + `flex items-center justify-center gap-2 px-6 py-3 rounded-lg - transition-all duration-200 active:scale-95`, - isActive - ? "bg-purple-600 text-white shadow-lg" - : "text-purple-200 hover:text-white hover:bg-white/10", - ) - } + transition-all duration-200 active:scale-95 + text-purple-200 hover:text-white hover:bg-white/10`, + ), + }} > {config.label} - + ); } diff --git a/src/pages/EditionView/TabNavigation/MobileTabButton.tsx b/src/pages/EditionView/TabNavigation/MobileTabButton.tsx index d77f95e..8f8c4f7 100644 --- a/src/pages/EditionView/TabNavigation/MobileTabButton.tsx +++ b/src/pages/EditionView/TabNavigation/MobileTabButton.tsx @@ -1,16 +1,31 @@ -import { NavLink } from "react-router-dom"; +import { Link, useParams } from "@tanstack/react-router"; import { TabButtonProps } from "./types"; -export function MobileTabButton({ config, basePath }: TabButtonProps) { +const tabRoutes = { + sets: "/festivals/$festivalSlug/editions/$editionSlug/sets", + schedule: "/festivals/$festivalSlug/editions/$editionSlug/schedule", + map: "/festivals/$festivalSlug/editions/$editionSlug/map", + info: "/festivals/$festivalSlug/editions/$editionSlug/info", + social: "/festivals/$festivalSlug/editions/$editionSlug/social", + explore: "/festivals/$festivalSlug/editions/$editionSlug/explore", +} as const; + +export function MobileTabButton({ config }: TabButtonProps) { + const { festivalSlug, editionSlug } = useParams(); + return ( - ` - flex-1 flex flex-col items-center justify-center - py-2 px-1 transition-colors duration-200 min-h-16 - ${isActive ? "text-purple-400" : "text-gray-400 active:text-purple-300"} - `} + to={tabRoutes[config.key]} + params={{ festivalSlug, editionSlug }} + activeProps={{ + className: `flex-1 flex flex-col items-center justify-center + py-2 px-1 transition-colors duration-200 min-h-16 text-purple-400`, + }} + inactiveProps={{ + className: `flex-1 flex flex-col items-center justify-center + py-2 px-1 transition-colors duration-200 min-h-16 text-gray-400 active:text-purple-300`, + }} > {({ isActive }) => ( <> @@ -24,6 +39,6 @@ export function MobileTabButton({ config, basePath }: TabButtonProps) { )} - + ); } diff --git a/src/pages/EditionView/tabs/ArtistsTab/SetCard/SetImage.tsx b/src/pages/EditionView/tabs/ArtistsTab/SetCard/SetImage.tsx index 8964e75..051201e 100644 --- a/src/pages/EditionView/tabs/ArtistsTab/SetCard/SetImage.tsx +++ b/src/pages/EditionView/tabs/ArtistsTab/SetCard/SetImage.tsx @@ -1,4 +1,4 @@ -import { Link } from "react-router-dom"; +import { Link, useParams } from "@tanstack/react-router"; import { ArtistImageLoader } from "@/components/ArtistImageLoader"; import { useFestivalSet } from "../FestivalSetContext"; import { MixedArtistImage } from "@/pages/SetDetails/MixedArtistImage"; @@ -10,6 +10,7 @@ interface SetImageProps { export function SetImage({ className = "", size = "lg" }: SetImageProps) { const { set } = useFestivalSet(); + const { festivalSlug, editionSlug } = useParams(); const isMultiArtist = set.artists.length > 1; const sizeClasses = { @@ -21,7 +22,11 @@ export function SetImage({ className = "", size = "lg" }: SetImageProps) { const containerClass = `${sizeClasses[size]} ${className} overflow-hidden rounded-lg hover:opacity-90 transition-opacity cursor-pointer`; return ( - + {isMultiArtist ? ( - cn( + {label} - + ); } diff --git a/src/pages/EditionView/tabs/ScheduleTab/horizontal/SetHeader.tsx b/src/pages/EditionView/tabs/ScheduleTab/horizontal/SetHeader.tsx index 138f78b..1e5fb2c 100644 --- a/src/pages/EditionView/tabs/ScheduleTab/horizontal/SetHeader.tsx +++ b/src/pages/EditionView/tabs/ScheduleTab/horizontal/SetHeader.tsx @@ -1,4 +1,4 @@ -import { Link } from "react-router-dom"; +import { Link, useParams } from "@tanstack/react-router"; import type { ScheduleSet } from "@/hooks/useScheduleData"; interface SetHeaderProps { @@ -6,10 +6,13 @@ interface SetHeaderProps { } export function SetHeader({ set }: SetHeaderProps) { + const { festivalSlug, editionSlug } = useParams(); + return (
{set.name} diff --git a/src/pages/EditionView/tabs/ScheduleTab/list/MobileSetCard.tsx b/src/pages/EditionView/tabs/ScheduleTab/list/MobileSetCard.tsx index 513b244..77ad6c8 100644 --- a/src/pages/EditionView/tabs/ScheduleTab/list/MobileSetCard.tsx +++ b/src/pages/EditionView/tabs/ScheduleTab/list/MobileSetCard.tsx @@ -1,5 +1,5 @@ import { Card, CardContent } from "@/components/ui/card"; -import { Link } from "react-router-dom"; +import { Link, useParams } from "@tanstack/react-router"; import { Clock } from "lucide-react"; import { format, differenceInMinutes } from "date-fns"; import { VoteButtons } from "../VoteButtons"; @@ -11,6 +11,7 @@ interface MobileSetCardProps { } export function MobileSetCard({ set }: MobileSetCardProps) { + const { festivalSlug, editionSlug } = useParams(); const duration = set.startTime && set.endTime ? differenceInMinutes(set.endTime, set.startTime) @@ -22,7 +23,8 @@ export function MobileSetCard({ set }: MobileSetCardProps) { {/* Artist name */}
{set.name} diff --git a/src/pages/ExploreSetPage/ExploreSetPage.tsx b/src/pages/ExploreSetPage/ExploreSetPage.tsx index f2c8d37..f9c8eb9 100644 --- a/src/pages/ExploreSetPage/ExploreSetPage.tsx +++ b/src/pages/ExploreSetPage/ExploreSetPage.tsx @@ -1,4 +1,4 @@ -import { useNavigate } from "react-router-dom"; +import { useNavigate } from "@tanstack/react-router"; import { useFestivalEdition } from "@/contexts/FestivalEditionContext"; import { LoadingState } from "./components/LoadingState"; import { EmptyState } from "./components/EmptyState"; @@ -55,7 +55,6 @@ export function ExploreSetPage() {
{ if (isLastSet) { - navigate(`${basePath}/sets`); + navigate({ from: "/festivals/$festivalSlug/editions/$editionSlug/explore", to: "../sets" }); } else { setDirection(null); } @@ -143,7 +142,7 @@ export function ExploreSetPage() { setSkippedCount((prev) => prev + 1); setTimeout(() => { if (isLastSet) { - navigate(`${basePath}/sets`); + navigate({ from: "/festivals/$festivalSlug/editions/$editionSlug/explore", to: "../sets" }); } else { setCurrentIndex((prev) => prev + 1); setDirection(null); diff --git a/src/pages/ExploreSetPage/components/EmptyState.tsx b/src/pages/ExploreSetPage/components/EmptyState.tsx index ff60746..e5a944d 100644 --- a/src/pages/ExploreSetPage/components/EmptyState.tsx +++ b/src/pages/ExploreSetPage/components/EmptyState.tsx @@ -1,6 +1,6 @@ import { Button } from "@/components/ui/button"; import { ArrowLeft } from "lucide-react"; -import { Link } from "react-router-dom"; +import { Link } from "@tanstack/react-router"; interface EmptyStateProps { basePath: string; diff --git a/src/pages/ExploreSetPage/components/ExplorePageHeader.tsx b/src/pages/ExploreSetPage/components/ExplorePageHeader.tsx index 746a214..3e16adb 100644 --- a/src/pages/ExploreSetPage/components/ExplorePageHeader.tsx +++ b/src/pages/ExploreSetPage/components/ExplorePageHeader.tsx @@ -1,11 +1,10 @@ -import { Link } from "react-router-dom"; +import { Link } from "@tanstack/react-router"; import { Button } from "@/components/ui/button"; import { ArrowLeft } from "lucide-react"; import { ExplorationProgress } from "../ExplorationProgress"; import { ProgressInfoTooltip } from "./ProgressInfoTooltip"; interface ExplorePageHeaderProps { - basePath: string; editionName: string; currentIndex: number; totalSets: number; @@ -15,7 +14,6 @@ interface ExplorePageHeaderProps { } export function ExplorePageHeader({ - basePath, editionName, currentIndex, totalSets, @@ -32,7 +30,10 @@ export function ExplorePageHeader({ size="sm" className="text-white hover:bg-white/20 flex items-center " > - + Back diff --git a/src/pages/FestivalSelection.tsx b/src/pages/FestivalSelection.tsx index 5121b42..176c7a9 100644 --- a/src/pages/FestivalSelection.tsx +++ b/src/pages/FestivalSelection.tsx @@ -13,7 +13,7 @@ import { createFestivalSubdomainUrl, isMainGetuplineDomain, } from "@/lib/subdomain"; -import { Link } from "react-router-dom"; +import { Link } from "@tanstack/react-router"; import { useCustomLinksQuery } from "@/hooks/queries/custom-links/useCustomLinks"; import { PageTitle } from "@/components/PageTitle/PageTitle"; import { TopBar } from "@/components/layout/TopBar"; @@ -104,7 +104,8 @@ function FestivalCard({ festival }: { festival: Festival }) { return ( diff --git a/src/pages/NotFound.tsx b/src/pages/NotFound.tsx index e602217..15e8b5e 100644 --- a/src/pages/NotFound.tsx +++ b/src/pages/NotFound.tsx @@ -1,4 +1,4 @@ -import { useLocation } from "react-router-dom"; +import { useLocation } from "@tanstack/react-router"; import { useEffect } from "react"; function NotFound() { diff --git a/src/pages/SetDetails.tsx b/src/pages/SetDetails.tsx index 5af8f68..8a5054c 100644 --- a/src/pages/SetDetails.tsx +++ b/src/pages/SetDetails.tsx @@ -1,4 +1,4 @@ -import { useParams } from "react-router-dom"; +import { useParams } from "@tanstack/react-router"; import { ArtistImageCard } from "./SetDetails/SetImageCard"; import { MixedArtistImage } from "./SetDetails/MixedArtistImage"; import { SetInfoCard } from "./SetDetails/SetInfoCard"; @@ -18,7 +18,7 @@ import { FestivalIndicator } from "@/components/layout/AppHeader/FestivalIndicat export function SetDetails() { const { user } = useAuth(); - const { setSlug } = useParams<{ setSlug: string }>(); + const { setSlug } = useParams(); const { edition, festival } = useFestivalEdition(); const { state: urlState } = useUrlState(); const setQuery = useSetBySlugQuery({ diff --git a/src/pages/SetDetails/SetNotFoundState.tsx b/src/pages/SetDetails/SetNotFoundState.tsx index e130bd0..e06b901 100644 --- a/src/pages/SetDetails/SetNotFoundState.tsx +++ b/src/pages/SetDetails/SetNotFoundState.tsx @@ -1,4 +1,4 @@ -import { Link } from "react-router-dom"; +import { Link } from "@tanstack/react-router"; import { Button } from "@/components/ui/button"; import { ArrowLeft, Music } from "lucide-react"; import { useFestivalEdition } from "@/contexts/FestivalEditionContext"; diff --git a/src/pages/admin/AdminLayout.tsx b/src/pages/admin/AdminLayout.tsx index 6c4af12..92929a0 100644 --- a/src/pages/admin/AdminLayout.tsx +++ b/src/pages/admin/AdminLayout.tsx @@ -1,6 +1,6 @@ import { useAuth } from "@/contexts/AuthContext"; import { TopBar } from "@/components/layout/TopBar"; -import { Outlet, useNavigate, useLocation } from "react-router-dom"; +import { Outlet, useNavigate, useLocation } from "@tanstack/react-router"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useUserPermissionsQuery } from "@/hooks/queries/auth/useUserPermissions"; import { useEffect } from "react"; @@ -21,12 +21,12 @@ export default function AdminLayout() { if (authLoading || isLoadingPermissions) return; if (!user) { - navigate("/"); + navigate({ to: "/" }); return; } if (!canEdit) { - navigate("/"); + navigate({ to: "/" }); } }, [user, authLoading, navigate, isLoadingPermissions, canEdit]); @@ -43,16 +43,16 @@ export default function AdminLayout() { function handleTabChange(value: string) { switch (value) { case "artists": - navigate("/admin"); + navigate({ to: "/admin" }); break; case "festivals": - navigate("/admin/festivals"); + navigate({ to: "/admin/festivals" }); break; case "analytics": - navigate("/admin/analytics"); + navigate({ to: "/admin/analytics" }); break; case "admins": - navigate("/admin/admins"); + navigate({ to: "/admin/admins" }); break; } } diff --git a/src/pages/admin/ArtistsManagement/DuplicateArtistsPage.tsx b/src/pages/admin/ArtistsManagement/DuplicateArtistsPage.tsx index 57d3301..2c9b574 100644 --- a/src/pages/admin/ArtistsManagement/DuplicateArtistsPage.tsx +++ b/src/pages/admin/ArtistsManagement/DuplicateArtistsPage.tsx @@ -6,7 +6,7 @@ import { AlertTriangle, Copy, ArrowLeft, Zap } from "lucide-react"; import { useDuplicateArtistsQuery } from "@/hooks/queries/artists/useDuplicateArtists"; import { DuplicateGroupCard } from "./DuplicateGroupCard"; import { BulkMergeDialog } from "./BulkMergeDialog"; -import { Link } from "react-router-dom"; +import { Link } from "@tanstack/react-router"; export function DuplicateArtistsPage() { const duplicatesQuery = useDuplicateArtistsQuery(); diff --git a/src/pages/admin/ArtistsManagement/components/BulkEditorHeader.tsx b/src/pages/admin/ArtistsManagement/components/BulkEditorHeader.tsx index 46513b9..b443465 100644 --- a/src/pages/admin/ArtistsManagement/components/BulkEditorHeader.tsx +++ b/src/pages/admin/ArtistsManagement/components/BulkEditorHeader.tsx @@ -1,7 +1,7 @@ import { CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Grid3X3, Plus, Copy } from "lucide-react"; -import { Link } from "react-router-dom"; +import { Link } from "@tanstack/react-router"; import { SoundCloudSyncButton } from "./SoundCloudSyncButton"; interface BulkEditorHeaderProps { diff --git a/src/pages/admin/festivals/AdminFestivals.tsx b/src/pages/admin/festivals/AdminFestivals.tsx index 98d7f23..f490773 100644 --- a/src/pages/admin/festivals/AdminFestivals.tsx +++ b/src/pages/admin/festivals/AdminFestivals.tsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { Outlet, useNavigate, useParams } from "react-router-dom"; +import { Outlet, useNavigate, useParams } from "@tanstack/react-router"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Calendar, Plus } from "lucide-react"; import { FestivalDialog } from "./FestivalDialog"; @@ -12,15 +12,13 @@ export default function AdminFestivals() { const [isEditDialogOpen, setIsEditDialogOpen] = useState(false); const navigate = useNavigate(); - const { festivalSlug = "" } = useParams<{ - festivalSlug?: string; - }>(); + const { festivalSlug = "" } = useParams(); function handleFestivalChange(festivalSlug: string) { if (festivalSlug === "none") { - navigate("/admin/festivals"); + navigate({ to: "/admin/festivals" }); } else { - navigate(`/admin/festivals/${festivalSlug}`); + navigate({ to: "/admin/festivals/$festivalSlug", params: { festivalSlug } }); } } diff --git a/src/pages/admin/festivals/CSVImportPage.tsx b/src/pages/admin/festivals/CSVImportPage.tsx index d9fed10..020074a 100644 --- a/src/pages/admin/festivals/CSVImportPage.tsx +++ b/src/pages/admin/festivals/CSVImportPage.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from "react"; -import { useParams, useNavigate, useSearchParams } from "react-router-dom"; +import { useParams, useNavigate, useSearch } from "@tanstack/react-router"; import { useToast } from "@/hooks/use-toast"; import { Button } from "@/components/ui/button"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; @@ -53,8 +53,8 @@ function getUserTimezone(): string { export function CSVImportPage() { const { festivalId: urlFestivalId, editionId: urlEditionId } = useParams(); const navigate = useNavigate(); - const [searchParams] = useSearchParams(); - const defaultTab = (searchParams.get("tab") as "stages" | "sets") || "stages"; + const search = useSearch(); + const defaultTab = (search.tab === "sets" ? "sets" : "stages") satisfies "stages" | "sets"; const [selectedFestivalId, setSelectedFestivalId] = useState( urlFestivalId || "", @@ -102,13 +102,17 @@ export function CSVImportPage() { function handleFestivalChange(festivalId: string) { setSelectedFestivalId(festivalId); setSelectedEditionId(""); - navigate(`/admin/festivals/${festivalId}/import`, { replace: true }); + // Don't navigate with empty editionId - just update state + // User will select an edition which will then trigger navigation } function handleEditionChange(editionId: string) { setSelectedEditionId(editionId); - if (selectedFestivalId) { - navigate(`/admin/festivals/${selectedFestivalId}/${editionId}/import`, { + if (selectedFestivalId && editionId) { + navigate({ + to: "/admin/festivals/$festivalId/$editionId/import", + params: { festivalId: selectedFestivalId, editionId }, + search: (prev) => ({ ...prev }), replace: true, }); } @@ -321,7 +325,7 @@ export function CSVImportPage() {
diff --git a/src/pages/admin/festivals/SetsManagement/SetManagement.tsx b/src/pages/admin/festivals/SetsManagement/SetManagement.tsx index a41c0c1..5ffedc3 100644 --- a/src/pages/admin/festivals/SetsManagement/SetManagement.tsx +++ b/src/pages/admin/festivals/SetsManagement/SetManagement.tsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { Link, useOutletContext } from "react-router-dom"; +import { Link, useRouteContext } from "@tanstack/react-router"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Loader2, Plus, Music, Upload } from "lucide-react"; @@ -14,7 +14,7 @@ interface SetManagementProps {} export function SetManagement(_props: SetManagementProps) { // All hooks must be at the top level - const { edition } = useOutletContext<{ edition: FestivalEdition }>(); + const { edition } = useRouteContext(); const { data: sets = [], isLoading } = useSetsQuery(); const [isDialogOpen, setIsDialogOpen] = useState(false); const [editingSet, setEditingSet] = useState(null); @@ -74,7 +74,9 @@ export function SetManagement(_props: SetManagementProps) {