diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..4e79670
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,7 @@
+node_modules
+dist
+.env
+.env.*
+!.env.example
+Dockerfile
+.dockerignore
diff --git a/.env.example b/.env.example
index c1ac2ab..f426c69 100644
--- a/.env.example
+++ b/.env.example
@@ -1,6 +1,9 @@
# API
VITE_API_URL='http://localhost:40287'
+# SITE (absolute origin used for SEO og/twitter URLs during SSR)
+VITE_SITE_URL='http://localhost:3000'
+
# CROWDIN
CROWDIN_PROJECT_ID=
-CROWDIN_API_TOKEN=
\ No newline at end of file
+CROWDIN_API_TOKEN=
diff --git a/.github/PULL_REQUEST_TEMPLATE.MD b/.github/PULL_REQUEST_TEMPLATE.MD
index 69d2636..f61643a 100644
--- a/.github/PULL_REQUEST_TEMPLATE.MD
+++ b/.github/PULL_REQUEST_TEMPLATE.MD
@@ -1,5 +1,3 @@
-
-
## Description
diff --git a/.github/workflows/build-files.yml b/.github/workflows/build-files.yml
index bcd05bd..cee0df9 100644
--- a/.github/workflows/build-files.yml
+++ b/.github/workflows/build-files.yml
@@ -3,11 +3,9 @@ name: Build Files
on:
push:
branches:
- - main
- develop
pull_request:
branches:
- - main
- develop
jobs:
diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
new file mode 100644
index 0000000..8e8ebad
--- /dev/null
+++ b/.github/workflows/cd.yml
@@ -0,0 +1,34 @@
+name: Docker Image Build and Publish
+
+on:
+ push:
+ branches:
+ - main
+
+jobs:
+ publish:
+ name: Docker Build
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v4
+ with:
+ driver-opts: |
+ image=moby/buildkit:v0.10.6
+
+ - name: Login to DockerHub
+ uses: docker/login-action@v4
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+
+ - name: Build and publish to registry
+ uses: docker/build-push-action@v7
+ with:
+ push: true
+ tags: trackgeek/web:latest
diff --git a/.github/workflows/check-format-code.yml b/.github/workflows/check-format-code.yml
index 457d170..f021d6f 100644
--- a/.github/workflows/check-format-code.yml
+++ b/.github/workflows/check-format-code.yml
@@ -3,11 +3,9 @@ name: Check Format Code
on:
push:
branches:
- - main
- develop
pull_request:
branches:
- - main
- develop
jobs:
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..b58b22f
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,29 @@
+FROM oven/bun:1 AS build
+
+WORKDIR /app
+
+COPY package.json bun.lock ./
+RUN bun install --frozen-lockfile
+
+COPY . .
+
+ARG VITE_API_URL
+ARG VITE_SITE_URL
+ENV VITE_API_URL=$VITE_API_URL
+ENV VITE_SITE_URL=$VITE_SITE_URL
+
+RUN bun run build
+
+FROM oven/bun:1-slim
+
+WORKDIR /app
+ENV NODE_ENV=production
+
+COPY --from=build /app/package.json /app/bun.lock ./
+RUN bun install --frozen-lockfile --production
+
+COPY --from=build /app/dist ./dist
+
+EXPOSE 3000
+
+CMD ["bun", "run", "start"]
diff --git a/bun.lock b/bun.lock
index 63a8913..e80b236 100644
--- a/bun.lock
+++ b/bun.lock
@@ -23,8 +23,9 @@
"@tanstack/react-pacer": "^0.22.0",
"@tanstack/react-query": "^5.99.2",
"@tanstack/react-query-devtools": "^5.99.2",
- "@tanstack/react-router": "^1.168.23",
- "@tanstack/react-router-devtools": "^1.166.13",
+ "@tanstack/react-router": "^1.170.18",
+ "@tanstack/react-router-devtools": "^1.167.0",
+ "@tanstack/react-start": "^1.168.32",
"@tanstack/react-table": "^8.21.3",
"@tiptap/core": "^3.22.4",
"@tiptap/extension-list": "^3.22.4",
@@ -69,6 +70,7 @@
"react-spinners": "^0.17.0",
"remark-gfm": "^4.0.1",
"sonner": "^2.0.7",
+ "srvx": "^0.12.4",
"tailwind-merge": "^3.4.0",
"tailwindcss": "^4.2.4",
"tippy.js": "^6.3.7",
@@ -79,7 +81,7 @@
"@biomejs/biome": "2.4.12",
"@crowdin/cli": "^4.14.1",
"@tanstack/devtools-vite": "^0.6.0",
- "@tanstack/router-plugin": "^1.167.22",
+ "@tanstack/router-plugin": "^1.168.23",
"@types/node": "^25.6.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
@@ -108,8 +110,6 @@
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="],
- "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="],
-
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
@@ -120,10 +120,6 @@
"@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
- "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="],
-
- "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="],
-
"@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="],
"@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
@@ -272,6 +268,14 @@
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
+ "@oozcitak/dom": ["@oozcitak/dom@2.0.2", "", { "dependencies": { "@oozcitak/infra": "^2.0.2", "@oozcitak/url": "^3.0.0", "@oozcitak/util": "^10.0.0" } }, "sha512-GjpKhkSYC3Mj4+lfwEyI1dqnsKTgwGy48ytZEhm4A/xnH/8z9M3ZVXKr/YGQi3uCLs1AEBS+x5T2JPiueEDW8w=="],
+
+ "@oozcitak/infra": ["@oozcitak/infra@2.0.2", "", { "dependencies": { "@oozcitak/util": "^10.0.0" } }, "sha512-2g+E7hoE2dgCz/APPOEK5s3rMhJvNxSMBrP+U+j1OWsIbtSpWxxlUjq1lU8RIsFJNYv7NMlnVsCuHcUzJW+8vA=="],
+
+ "@oozcitak/url": ["@oozcitak/url@3.0.0", "", { "dependencies": { "@oozcitak/infra": "^2.0.2", "@oozcitak/util": "^10.0.0" } }, "sha512-ZKfET8Ak1wsLAiLWNfFkZc/BraDccuTJKR6svTYc7sVjbR+Iu0vtXdiDMY4o6jaFl5TW2TlS7jbLl4VovtAJWQ=="],
+
+ "@oozcitak/util": ["@oozcitak/util@10.0.0", "", {}, "sha512-hAX0pT/73190NLqBPPWSdBVGtbY6VOhWYK3qqHqtXQ1gK7kS2yz4+ivsN07hpJ6I3aeMtKP6J6npsEKOAzuTLA=="],
+
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="],
"@oxc-project/types": ["@oxc-project/types@0.126.0", "", {}, "sha512-oGfVtjAgwQVVpfBrbtk4e1XDyWHRFta6BS3GWVzrF8xYBT2VGQAk39yJS/wFSMrZqoiCU4oghT3Ch0HaHGIHcQ=="],
@@ -490,7 +494,7 @@
"@tanstack/devtools-vite": ["@tanstack/devtools-vite@0.6.0", "", { "dependencies": { "@babel/core": "^7.28.4", "@babel/generator": "^7.28.3", "@babel/parser": "^7.28.4", "@babel/traverse": "^7.28.4", "@babel/types": "^7.28.4", "@tanstack/devtools-client": "0.0.6", "@tanstack/devtools-event-bus": "0.4.1", "chalk": "^5.6.2", "launch-editor": "^2.11.1", "picomatch": "^4.0.3" }, "peerDependencies": { "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "bin": { "intent": "bin/intent.js" } }, "sha512-h0r0ct7zlrgjkhmn4QW6wRjgUXd4JMs+r7gtx+BXo9f5H9Y+jtUdtvC0rnZcPto6gw/9yMUq7yOmMK5qDWRExg=="],
- "@tanstack/history": ["@tanstack/history@1.161.6", "", {}, "sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg=="],
+ "@tanstack/history": ["@tanstack/history@1.162.0", "", {}, "sha512-79pf/RkhteYZTRgcR4F9kbk84P2N8rugQJswxfIqovlbRiT3yI7eBE+5QorIrZaOKktsgzRlXh1l/du/xpl4iA=="],
"@tanstack/pacer": ["@tanstack/pacer@0.21.0", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.4.3", "@tanstack/store": "^0.11.0" } }, "sha512-EZyU3fQvdz7faj0vBVrPd5ejkSS9pbGKtrGZI56dLJgotca6iZKhaNDRv7dweG4dnWx0LovE9YYf1MAErGpbLQ=="],
@@ -506,29 +510,47 @@
"@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.99.2", "", { "dependencies": { "@tanstack/query-devtools": "5.99.2" }, "peerDependencies": { "@tanstack/react-query": "^5.99.2", "react": "^18 || ^19" } }, "sha512-8txkK9A9XBNTB8RoxVgfp6W3qwBr25tNP10L4yu3KuyhAdEvccECfIRzesSwMVk/wpVVioAr+hbMtUkMMF+WVw=="],
- "@tanstack/react-router": ["@tanstack/react-router@1.168.23", "", { "dependencies": { "@tanstack/history": "1.161.6", "@tanstack/react-store": "^0.9.3", "@tanstack/router-core": "1.168.15", "isbot": "^5.1.22" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-+GblieDnutG6oipJJPNtRJjrWF8QTZEG/l0532+BngFkVK48oHNOcvIkSoAFYftK1egAwM7KBxXsb0Ou+X6/MQ=="],
+ "@tanstack/react-router": ["@tanstack/react-router@1.170.18", "", { "dependencies": { "@tanstack/history": "1.162.0", "@tanstack/react-store": "^0.9.3", "@tanstack/router-core": "1.171.15", "isbot": "^5.1.22" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-wpbGYZEp/fmz1q4bn7BD8VZ+/VZ7GBqSJv5V969pU+chP8y7dquWDmKTFMohvUegb9lg12m1uPVvD6kB2wORvQ=="],
+
+ "@tanstack/react-router-devtools": ["@tanstack/react-router-devtools@1.167.0", "", { "dependencies": { "@tanstack/router-devtools-core": "1.168.0" }, "peerDependencies": { "@tanstack/react-router": "^1.170.0", "@tanstack/router-core": "^1.170.0", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" }, "optionalPeers": ["@tanstack/router-core"] }, "sha512-nGw095EG7IHx0h5NtlEmzf6vcCTaFNPWdTSuDKazajhN0ct/v/TkekJ9J6KYUCeV1a8/2ZmToc58M+0rrOyn7w=="],
+
+ "@tanstack/react-start": ["@tanstack/react-start@1.168.32", "", { "dependencies": { "@tanstack/react-router": "1.170.18", "@tanstack/react-start-client": "1.168.16", "@tanstack/react-start-rsc": "0.1.31", "@tanstack/react-start-server": "1.167.22", "@tanstack/router-utils": "1.162.2", "@tanstack/start-client-core": "1.170.14", "@tanstack/start-plugin-core": "1.171.24", "@tanstack/start-server-core": "1.169.17", "pathe": "^2.0.3" }, "peerDependencies": { "@rsbuild/core": "^2.0.0", "@vitejs/plugin-rsc": "*", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0", "vite": ">=7.0.0" }, "optionalPeers": ["@rsbuild/core", "@vitejs/plugin-rsc", "vite"] }, "sha512-y1WXHo+jPfHxiuuN1m+br06IcriiBQnEWryBdbKdEOS5vw2PmnOj+Cgf1/YcGOqtSougScWFeE2rL1FXWvsLLg=="],
+
+ "@tanstack/react-start-client": ["@tanstack/react-start-client@1.168.16", "", { "dependencies": { "@tanstack/react-router": "1.170.18", "@tanstack/router-core": "1.171.15", "@tanstack/start-client-core": "1.170.14" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-1OfHgy0wpHwe2tlB3FxMeA+IMX6Il/QAMf+8UdXuimReIc2Lz3BkMLBL38k4GIxBguX9sI8EMLO5jlTZ4e1olw=="],
- "@tanstack/react-router-devtools": ["@tanstack/react-router-devtools@1.166.13", "", { "dependencies": { "@tanstack/router-devtools-core": "1.167.3" }, "peerDependencies": { "@tanstack/react-router": "^1.168.15", "@tanstack/router-core": "^1.168.11", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" }, "optionalPeers": ["@tanstack/router-core"] }, "sha512-6yKRFFJrEEOiGp5RAAuGCYsl81M4XAhJmLcu9PKj+HZle4A3dsP60lwHoqQYWHMK9nKKFkdXR+D8qxzxqtQbEA=="],
+ "@tanstack/react-start-rsc": ["@tanstack/react-start-rsc@0.1.31", "", { "dependencies": { "@tanstack/react-router": "1.170.18", "@tanstack/router-core": "1.171.15", "@tanstack/router-utils": "1.162.2", "@tanstack/start-client-core": "1.170.14", "@tanstack/start-fn-stubs": "1.162.0", "@tanstack/start-plugin-core": "1.171.24", "@tanstack/start-server-core": "1.169.17", "@tanstack/start-storage-context": "1.167.17", "pathe": "^2.0.3" }, "peerDependencies": { "@rspack/core": ">=2.0.0-0", "@vitejs/plugin-rsc": ">=0.5.20", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0", "react-server-dom-rspack": ">=0.0.2" }, "optionalPeers": ["@rspack/core", "@vitejs/plugin-rsc", "react-server-dom-rspack"] }, "sha512-WxjkXYflq550vTNJpdPyMaPC+Vyh88L5wOL+SiDTjPMGne9ad7FZmoJxqfCFEv1e7HVKMH/mMoE8619TsTNVzQ=="],
+
+ "@tanstack/react-start-server": ["@tanstack/react-start-server@1.167.22", "", { "dependencies": { "@tanstack/react-router": "1.170.18", "@tanstack/router-core": "1.171.15", "@tanstack/start-server-core": "1.169.17" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-eH2PeHuLfL3R5YzE9+y2FfcE4Ld1LNV2ZfrCNVPJMMJFt+9nXDaRHg9BsEmc+JkTAGzz3FKLyQEoWwpbG6Ehqg=="],
"@tanstack/react-store": ["@tanstack/react-store@0.11.0", "", { "dependencies": { "@tanstack/store": "0.11.0", "use-sync-external-store": "^1.6.0" }, "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" } }, "sha512-tX4YXh3PDkmpvGQWkWqKpzs/MSqbtuwY9dWdWhtV9Q50PmO+jOkUKIWIX4G85dwt7lxdHLXsiaEKPdKmC8F41w=="],
"@tanstack/react-table": ["@tanstack/react-table@8.21.3", "", { "dependencies": { "@tanstack/table-core": "8.21.3" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww=="],
- "@tanstack/router-core": ["@tanstack/router-core@1.168.15", "", { "dependencies": { "@tanstack/history": "1.161.6", "cookie-es": "^3.0.0", "seroval": "^1.5.0", "seroval-plugins": "^1.5.0" }, "bin": { "intent": "bin/intent.js" } }, "sha512-Wr0424NDtD8fT/uALobMZ9DdcfsTyXtW5IPR++7zvW8/7RaIOeaqXpVDId8ywaGtqPWLWOfaUg2zUtYtukoXYA=="],
+ "@tanstack/router-core": ["@tanstack/router-core@1.171.15", "", { "dependencies": { "@tanstack/history": "1.162.0", "cookie-es": "^3.0.0", "seroval": "^1.5.4", "seroval-plugins": "^1.5.4" } }, "sha512-IILCDcLaItMZQ2jEmCABHY1Nhjjn5XUvwpQp3e4Nmu+vfg0BgYFuu/QASz2SwE2ZNbVMrvt8X/wxa+Gg5aErxA=="],
+
+ "@tanstack/router-devtools-core": ["@tanstack/router-devtools-core@1.168.0", "", { "dependencies": { "clsx": "^2.1.1", "goober": "^2.1.16" }, "peerDependencies": { "@tanstack/router-core": "^1.170.0", "csstype": "^3.0.10" }, "optionalPeers": ["csstype"] }, "sha512-wQoQhlBK7nlZgqzaqdYXKWNTpdHdsaREdaPhFZVH0/Ador+F+eM3/NF2i3f2LPeS0GgKraZUQXe1Q/1+KHyEYg=="],
+
+ "@tanstack/router-generator": ["@tanstack/router-generator@1.167.21", "", { "dependencies": { "@babel/types": "^7.28.5", "@tanstack/router-core": "1.171.15", "@tanstack/router-utils": "1.162.2", "@tanstack/virtual-file-routes": "1.162.0", "jiti": "^2.7.0", "magic-string": "^0.30.21", "prettier": "^3.5.0", "zod": "^4.4.3" } }, "sha512-m3oXZyienj8owialdyoZ0txHQrnEx/Ra+D9kWtar5fC2cWZr5Pvxl86VY2mX5RRLC5QLKLeRGT1x4HV95wHVDQ=="],
+
+ "@tanstack/router-plugin": ["@tanstack/router-plugin@1.168.23", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "@tanstack/router-core": "1.171.15", "@tanstack/router-generator": "1.167.21", "@tanstack/router-utils": "1.162.2", "chokidar": "^5.0.0", "unplugin": "^3.0.0", "zod": "^4.4.3" }, "peerDependencies": { "@rsbuild/core": ">=1.0.2 || ^2.0.0", "@tanstack/react-router": "^1.170.18", "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0", "vite-plugin-solid": "^2.11.10 || ^3.0.0-0", "webpack": ">=5.92.0" }, "optionalPeers": ["@rsbuild/core", "@tanstack/react-router", "vite", "vite-plugin-solid", "webpack"] }, "sha512-0+PIcvnaAimFwjoEIeV3h7LKjzC8zNnp7pH2UamdKwQ9QlY99WU9V0Xl0zbM0i9hrUa/mKgWPDAzELmPUu5fMA=="],
+
+ "@tanstack/router-utils": ["@tanstack/router-utils@1.162.2", "", { "dependencies": { "@babel/generator": "^7.28.5", "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "ansis": "^4.1.0", "babel-dead-code-elimination": "^1.0.12", "diff": "^8.0.2", "pathe": "^2.0.3", "tinyglobby": "^0.2.15" } }, "sha512-hTWqJtqIFFdvuCl8WXNyrodp2L9zo2G37xKRrcVmVRWpAB2h+U1LuRAfS4tsFTiWOIoE/B+WDVFB8JpoEdw6jQ=="],
- "@tanstack/router-devtools-core": ["@tanstack/router-devtools-core@1.167.3", "", { "dependencies": { "clsx": "^2.1.1", "goober": "^2.1.16" }, "peerDependencies": { "@tanstack/router-core": "^1.168.11", "csstype": "^3.0.10" }, "optionalPeers": ["csstype"] }, "sha512-fJ1VMhyQgnoashTrP763c2HRc9kofgF61L7Jb3F6eTHAmCKtGVx8BRtiFt37sr3U0P0jmaaiiSPGP6nT5JtVNg=="],
+ "@tanstack/start-client-core": ["@tanstack/start-client-core@1.170.14", "", { "dependencies": { "@tanstack/router-core": "1.171.15", "@tanstack/start-fn-stubs": "1.162.0", "@tanstack/start-storage-context": "1.167.17", "seroval": "^1.5.4" } }, "sha512-yasBgEIFSWysL4EiFIGwp638nCoXXKiTqkc48EP2oty4OyNsZPTC1yfJ82zjq2KGkTAYtIaeMl7otqqRl1n85Q=="],
- "@tanstack/router-generator": ["@tanstack/router-generator@1.166.32", "", { "dependencies": { "@babel/types": "^7.28.5", "@tanstack/router-core": "1.168.15", "@tanstack/router-utils": "1.161.6", "@tanstack/virtual-file-routes": "1.161.7", "magic-string": "^0.30.21", "prettier": "^3.5.0", "tsx": "^4.19.2", "zod": "^3.24.2" } }, "sha512-VuusKwEXcgKq+myq1JQfZogY8scTXIIeFls50dJ/UXgCXWp5n14iFreYNlg41wURcak2oA3M+t2TVfD0xUUD6g=="],
+ "@tanstack/start-fn-stubs": ["@tanstack/start-fn-stubs@1.162.0", "", {}, "sha512-QWfUZ3Yo923tdQn38LyKMU8rcTw69zc+T4dAvgTWV4O56SqFRsGfS0lSWIMhJRwXIx/bvdi7nTUBDdZtTHtpTQ=="],
- "@tanstack/router-plugin": ["@tanstack/router-plugin@1.167.22", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@tanstack/router-core": "1.168.15", "@tanstack/router-generator": "1.166.32", "@tanstack/router-utils": "1.161.6", "@tanstack/virtual-file-routes": "1.161.7", "chokidar": "^3.6.0", "unplugin": "^2.1.2", "zod": "^3.24.2" }, "peerDependencies": { "@rsbuild/core": ">=1.0.2", "@tanstack/react-router": "^1.168.21", "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0", "vite-plugin-solid": "^2.11.10 || ^3.0.0-0", "webpack": ">=5.92.0" }, "optionalPeers": ["@rsbuild/core", "@tanstack/react-router", "vite", "vite-plugin-solid", "webpack"], "bin": { "intent": "bin/intent.js" } }, "sha512-wYPzIvBK8bcmXVUpZfSgGBXOrfBAdF4odKevz6rejio5rEd947NtKDF5R7eYdwlAOmRqYpLJnJ1QHkc5t8bY4w=="],
+ "@tanstack/start-plugin-core": ["@tanstack/start-plugin-core@1.171.24", "", { "dependencies": { "@babel/code-frame": "7.27.1", "@babel/core": "^7.28.5", "@babel/types": "^7.28.5", "@tanstack/router-core": "1.171.15", "@tanstack/router-generator": "1.167.21", "@tanstack/router-plugin": "1.168.23", "@tanstack/router-utils": "1.162.2", "@tanstack/start-server-core": "1.169.17", "exsolve": "^1.0.7", "lightningcss": "^1.32.0", "pathe": "^2.0.3", "picomatch": "^4.0.3", "seroval": "^1.5.4", "source-map": "^0.7.6", "srvx": "^0.11.9", "tinyglobby": "^0.2.15", "ufo": "^1.5.4", "vitefu": "^1.1.1", "xmlbuilder2": "^4.0.3", "zod": "^4.4.3" }, "peerDependencies": { "@rsbuild/core": "^2.0.0", "vite": ">=7.0.0" }, "optionalPeers": ["@rsbuild/core", "vite"] }, "sha512-l/tm+T0ntXHeIzr9kJDTJ2IDNZC0yFazjkvbEVeZsDOrJ8F+HiZmY+tXYqI5/nDYkwxY0DVQr+kGsTRVb6y2Jw=="],
- "@tanstack/router-utils": ["@tanstack/router-utils@1.161.6", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/generator": "^7.28.5", "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "ansis": "^4.1.0", "babel-dead-code-elimination": "^1.0.12", "diff": "^8.0.2", "pathe": "^2.0.3", "tinyglobby": "^0.2.15" } }, "sha512-nRcYw+w2OEgK6VfjirYvGyPLOK+tZQz1jkYcmH5AjMamQ9PycnlxZF2aEZtPpNoUsaceX2bHptn6Ub5hGXqNvw=="],
+ "@tanstack/start-server-core": ["@tanstack/start-server-core@1.169.17", "", { "dependencies": { "@tanstack/history": "1.162.0", "@tanstack/router-core": "1.171.15", "@tanstack/start-client-core": "1.170.14", "@tanstack/start-storage-context": "1.167.17", "fetchdts": "^0.1.6", "h3-v2": "npm:h3@2.0.1-rc.20", "seroval": "^1.5.4" } }, "sha512-u0N+PHJhMHnzfnlXYI9F+A/qweDe3E2X0mfkORPGIEkNQgvS548RA9fjwvixR2en5b848CfpEqUzwFhm/tQ40Q=="],
+
+ "@tanstack/start-storage-context": ["@tanstack/start-storage-context@1.167.17", "", { "dependencies": { "@tanstack/router-core": "1.171.15" } }, "sha512-ntkDyGx0PE0opIlWNAMpkMb8qkjR4uyCUOfC0CiT0STM25+EcwPuwYNfDXXeVObMrTAPgsQ4yOj3xdY0Xr4ptw=="],
"@tanstack/store": ["@tanstack/store@0.11.0", "", {}, "sha512-WlzzCt3xi0G6pCAJu1U+2jiECwabETDpQDi3hfkFZvJii9AuZqEKbOiVarX1/bWhTNjU486yQtJCCasi/0q+Cw=="],
"@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="],
- "@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.161.7", "", { "bin": { "intent": "bin/intent.js" } }, "sha512-olW33+Cn+bsCsZKPwEGhlkqS6w3M2slFv11JIobdnCFKMLG97oAI2kWKdx5/zsywTL8flpnoIgaZZPlQTFYhdQ=="],
+ "@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.162.0", "", {}, "sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA=="],
"@tiptap/core": ["@tiptap/core@3.22.4", "", { "peerDependencies": { "@tiptap/pm": "3.22.4" } }, "sha512-vGIGm/HpqLg8EAAQXQ+koV+/S828OEpzocfWcPOwo1u2QUVf9dQG47Yy6JJ8zFFaJwfv4dBcOXli+7BrJwsxDQ=="],
@@ -654,13 +676,11 @@
"@xyflow/system": ["@xyflow/system@0.0.76", "", { "dependencies": { "@types/d3-drag": "^3.0.7", "@types/d3-interpolate": "^3.0.4", "@types/d3-selection": "^3.0.10", "@types/d3-transition": "^3.0.8", "@types/d3-zoom": "^3.0.8", "d3-drag": "^3.0.0", "d3-interpolate": "^3.0.1", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0" } }, "sha512-hvwvnRS1B3REwVDlWexsq7YQaPZeG3/mKo1jv38UmnpWmxihp14bW6VtEOuHEwJX2FvzFw8k77LyKSk/wiZVNA=="],
- "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
-
"animejs": ["animejs@4.3.6", "", {}, "sha512-rzZ4bDc8JAtyx6hYwxj7s5M/yWfnM5qqY4hZDnhy1cWFvMb6H5/necHS2sbCY3WQTDbRLuZL10dPXSxSCFOr/w=="],
"ansis": ["ansis@4.2.0", "", {}, "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig=="],
- "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
+ "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="],
@@ -678,8 +698,6 @@
"better-call": ["better-call@1.3.5", "", { "dependencies": { "@better-auth/utils": "^0.4.0", "@better-fetch/fetch": "^1.1.21", "rou3": "^0.7.12", "set-cookie-parser": "^3.0.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-kOFJkBP7utAQLEYrobZm3vkTH8mXq5GNgvjc5/XEST1ilVHaxXUXfeDeFlqoETMtyqS4+3/h4ONX2i++ebZrvA=="],
- "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
-
"blurhash": ["blurhash@2.0.5", "", {}, "sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
@@ -704,7 +722,7 @@
"character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="],
- "chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
+ "chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
@@ -806,6 +824,8 @@
"execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
+ "exsolve": ["exsolve@1.1.0", "", {}, "sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw=="],
+
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
"fast-equals": ["fast-equals@5.4.0", "", {}, "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw=="],
@@ -816,6 +836,8 @@
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
+ "fetchdts": ["fetchdts@0.1.7", "", {}, "sha512-YoZjBdafyLIop9lSxXVI33oLD5kN31q4Td+CasofLLYeLXRFeOsuOw0Uo+XNRi9PZlbfdlN2GmRtm4tCEQ9/KA=="],
+
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
@@ -850,6 +872,8 @@
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
+ "h3-v2": ["h3@2.0.1-rc.20", "", { "dependencies": { "rou3": "^0.8.1", "srvx": "^0.11.13" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"], "bin": { "h3": "bin/h3.mjs" } }, "sha512-28ljodXuUp0fZovdiSRq4G9OgrxCztrJe5VdYzXAB7ueRvI7pIUqLU14Xi3XqdYJ/khXjfpUOOD2EQa6CmBgsg=="],
+
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
@@ -880,8 +904,6 @@
"is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="],
- "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
-
"is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
@@ -906,6 +928,8 @@
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
+ "js-yaml": ["js-yaml@4.3.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q=="],
+
"jsesc": ["jsesc@3.1.0", "", { "bin": "bin/jsesc" }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
"json5": ["json5@2.2.3", "", { "bin": "lib/cli.js" }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
@@ -1068,8 +1092,6 @@
"node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
- "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
-
"npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
"nuqs": ["nuqs@2.8.9", "", { "dependencies": { "@standard-schema/spec": "1.0.0" }, "peerDependencies": { "@remix-run/react": ">=2", "@tanstack/react-router": "^1", "next": ">=14.2.0", "react": ">=18.2.0 || ^19.0.0-0", "react-router": "^5 || ^6 || ^7", "react-router-dom": "^5 || ^6 || ^7" }, "optionalPeers": ["@remix-run/react", "@tanstack/react-router", "next", "react-router", "react-router-dom"] }, "sha512-8ou6AEwsxMWSYo2qkfZtYFVzngwbKmg4c00HVxC1fF6CEJv3Fwm6eoZmfVPALB+vw8Udo7KL5uy96PFcYe1BIQ=="],
@@ -1152,7 +1174,7 @@
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
- "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
+ "readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
"remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="],
@@ -1180,9 +1202,9 @@
"semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
- "seroval": ["seroval@1.5.0", "", {}, "sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw=="],
+ "seroval": ["seroval@1.5.6", "", {}, "sha512-rVQVWjjSvlINzaQPZH5JFqsqEsIWdTxY3iJZCnTL/5gQbXIRooVZKI60tVCkOVfzcRPejboxO2t0P89dg5mQaA=="],
- "seroval-plugins": ["seroval-plugins@1.5.0", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-EAHqADIQondwRZIdeW2I636zgsODzoBDwb3PT/+7TLDWyw1Dy/Xv7iGUIEXXav7usHDE9HVhOU61irI3EnyyHA=="],
+ "seroval-plugins": ["seroval-plugins@1.5.6", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-HXuLAX2pu/UByPpaeo/TaMfvMIi+1QqIoPJYCcAtU8QkVNwgR6MPlGuCQTErV1JwraaMbYaWVIBX7mppzGLATQ=="],
"set-cookie-parser": ["set-cookie-parser@3.0.1", "", {}, "sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q=="],
@@ -1200,10 +1222,14 @@
"sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="],
+ "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
+
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
+ "srvx": ["srvx@0.12.4", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-RixzFlMn3dvzDTpKIAXhXrqL4cy6vScNCP0VVwgVbUBU94o+DiXLsFnAZetbyKAEnK6Ox3hzHXWGsyv/Iibv7g=="],
+
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
"strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
@@ -1240,6 +1266,8 @@
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
+ "ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="],
+
"undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
"unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
@@ -1258,7 +1286,7 @@
"unpic": ["unpic@4.2.2", "", {}, "sha512-z6T2ScMgRV2y2H8MwwhY5xHZWXhUx/YxtOCGJwfURSl7ypVy4HpLIMWoIZKnnxQa/RKzM0kg8hUh0paIrpLfvw=="],
- "unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="],
+ "unplugin": ["unplugin@3.3.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.4", "webpack-virtual-modules": "^0.6.2" }, "peerDependencies": { "@farmfe/core": "*", "@rspack/core": "*", "bun-types-no-globals": "*", "esbuild": "*", "rolldown": "*", "rollup": "*", "unloader": "*", "vite": "*", "webpack": "*" }, "optionalPeers": ["@farmfe/core", "@rspack/core", "bun-types-no-globals", "esbuild", "rolldown", "rollup", "unloader", "vite", "webpack"] }, "sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg=="],
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": "cli.js" }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
@@ -1276,6 +1304,8 @@
"vite": ["vite@8.0.9", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.10", "rolldown": "1.0.0-rc.16", "tinyglobby": "^0.2.16" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-t7g7GVRpMXjNpa67HaVWI/8BWtdVIQPCL2WoozXXA7LBGEFK4AkkKkHx2hAQf5x1GZSlcmEDPkVLSGahxnEEZw=="],
+ "vitefu": ["vitefu@1.1.3", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="],
+
"void-elements": ["void-elements@3.1.0", "", {}, "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w=="],
"w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="],
@@ -1290,6 +1320,8 @@
"ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
+ "xmlbuilder2": ["xmlbuilder2@4.0.3", "", { "dependencies": { "@oozcitak/dom": "^2.0.2", "@oozcitak/infra": "^2.0.2", "@oozcitak/util": "^10.0.0", "js-yaml": "^4.1.1" } }, "sha512-bx8Q1STctnNaaDymWnkfQLKofs0mGNN7rLLapJlGuV3VlvegD7Ls4ggMjE3aUSWItCCzU0PEv45lI87iSigiCA=="],
+
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
"yauzl": ["yauzl@3.2.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "pend": "~1.2.0" } }, "sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w=="],
@@ -1346,16 +1378,24 @@
"@tanstack/react-router/@tanstack/react-store": ["@tanstack/react-store@0.9.3", "", { "dependencies": { "@tanstack/store": "0.9.3", "use-sync-external-store": "^1.6.0" }, "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" } }, "sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg=="],
- "@tanstack/router-generator/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
+ "@tanstack/router-generator/jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
- "@tanstack/router-plugin/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
+ "@tanstack/router-generator/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
- "@types/react-helmet/@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="],
+ "@tanstack/router-plugin/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
+
+ "@tanstack/start-plugin-core/srvx": ["srvx@0.11.22", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-LqZxxBDMKuMAZzFzJnDCkFOrs9MZQZr0LvHiO/SuSZVdQaXD7xQ5UWTUxheJrQPve1qk9MG2B/yttUvJxw8egQ=="],
- "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
+ "@tanstack/start-plugin-core/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
+
+ "@types/react-helmet/@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="],
"cmdk/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="],
+ "h3-v2/rou3": ["rou3@0.8.1", "", {}, "sha512-ePa+XGk00/3HuCqrEnK3LxJW7I0SdNg6EFzKUJG73hMAdDcOUC/i/aSz7LSDwLrGr33kal/rqOGydzwl6U7zBA=="],
+
+ "h3-v2/srvx": ["srvx@0.11.22", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-LqZxxBDMKuMAZzFzJnDCkFOrs9MZQZr0LvHiO/SuSZVdQaXD7xQ5UWTUxheJrQPve1qk9MG2B/yttUvJxw8egQ=="],
+
"i18next-browser-languagedetector/@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="],
"lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
@@ -1372,11 +1412,11 @@
"radix-ui/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
- "readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
-
"rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.16", "", {}, "sha512-45+YtqxLYKDWQouLKCrpIZhke+nXxhsw+qAHVzHDVwttyBlHNBVs2K25rDXrZzhpTp9w1FlAlvweV1H++fdZoA=="],
- "unplugin/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
+ "solid-js/seroval": ["seroval@1.5.0", "", {}, "sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw=="],
+
+ "solid-js/seroval-plugins": ["seroval-plugins@1.5.0", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-EAHqADIQondwRZIdeW2I636zgsODzoBDwb3PT/+7TLDWyw1Dy/Xv7iGUIEXXav7usHDE9HVhOU61irI3EnyyHA=="],
"@radix-ui/react-popper/@floating-ui/react-dom/@floating-ui/dom": ["@floating-ui/dom@1.7.4", "", { "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" } }, "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA=="],
diff --git a/index.html b/index.html
deleted file mode 100644
index 3590eb7..0000000
--- a/index.html
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/package.json b/package.json
index 6da2c4c..e245720 100644
--- a/package.json
+++ b/package.json
@@ -12,9 +12,9 @@
"crowdin:download": "crowdin download",
"crowdin:upload": "crowdin upload",
"crowdin:status": "crowdin status",
- "dev": "NODE_ENV=development vite",
+ "dev": "vite dev",
"build": "tsc -b && vite build",
- "start": "NODE_ENV=production vite preview"
+ "start": "srvx --prod -s ../client dist/server/server.js"
},
"dependencies": {
"@base-ui/react": "^1.4.1",
@@ -35,8 +35,9 @@
"@tanstack/react-pacer": "^0.22.0",
"@tanstack/react-query": "^5.99.2",
"@tanstack/react-query-devtools": "^5.99.2",
- "@tanstack/react-router": "^1.168.23",
- "@tanstack/react-router-devtools": "^1.166.13",
+ "@tanstack/react-router": "^1.170.18",
+ "@tanstack/react-router-devtools": "^1.167.0",
+ "@tanstack/react-start": "^1.168.32",
"@tanstack/react-table": "^8.21.3",
"@tiptap/core": "^3.22.4",
"@tiptap/extension-list": "^3.22.4",
@@ -81,6 +82,7 @@
"react-spinners": "^0.17.0",
"remark-gfm": "^4.0.1",
"sonner": "^2.0.7",
+ "srvx": "^0.12.4",
"tailwind-merge": "^3.4.0",
"tailwindcss": "^4.2.4",
"tippy.js": "^6.3.7",
@@ -91,7 +93,7 @@
"@biomejs/biome": "2.4.12",
"@crowdin/cli": "^4.14.1",
"@tanstack/devtools-vite": "^0.6.0",
- "@tanstack/router-plugin": "^1.167.22",
+ "@tanstack/router-plugin": "^1.168.23",
"@types/node": "^25.6.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
diff --git a/src/components/pages/user/progress-tab.tsx b/src/components/pages/user/progress-tab.tsx
index 22dfabc..976e07b 100644
--- a/src/components/pages/user/progress-tab.tsx
+++ b/src/components/pages/user/progress-tab.tsx
@@ -115,8 +115,10 @@ export function UserProgressTab({
progressQuery.fetchNextPage();
} else {
const allItems = rows
+ .filter((row) => !selectedSection || row.status === selectedSection)
.map((row) => progressToItem(contentType, row))
- .filter((item): item is FavoriteItem => item !== null);
+ .filter((item): item is FavoriteItem => item !== null)
+ .filter((item) => !normalizedQuery || item.title.toLowerCase().includes(normalizedQuery));
if (allItems.length > 0) {
const randomItem = allItems[Math.floor(Math.random() * allItems.length)];
@@ -124,7 +126,18 @@ export function UserProgressTab({
}
setIsRandomizing(false);
}
- }, [isRandomizing, progressQuery.isLoading, progressQuery.isFetchingNextPage, progressQuery.hasNextPage, rows]);
+ }, [
+ isRandomizing,
+ progressQuery.isLoading,
+ progressQuery.isFetchingNextPage,
+ progressQuery.hasNextPage,
+ progressQuery.fetchNextPage,
+ rows,
+ contentType,
+ selectedSection,
+ normalizedQuery,
+ navigate,
+ ]);
const handleContentTypeChange = (value: ApiTypes.ReviewContentType) => {
onContentTypeChange(value);
diff --git a/src/components/shared/modals/game.tsx b/src/components/shared/modals/game.tsx
index 730ec75..7b85a08 100644
--- a/src/components/shared/modals/game.tsx
+++ b/src/components/shared/modals/game.tsx
@@ -1,22 +1,108 @@
+import { zodResolver } from "@hookform/resolvers/zod";
import { Icon } from "@iconify/react";
-import { useMutation } from "@tanstack/react-query";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Image } from "@unpic/react";
import { format } from "date-fns";
-import { type DragEvent, useRef, useState } from "react";
+import type { TFunction } from "i18next";
+import { type DragEvent, useEffect, useMemo, useRef, useState } from "react";
+import { Controller, useForm } from "react-hook-form";
import { Trans, useTranslation } from "react-i18next";
-import { useGameReview, useUploadImage } from "@/hooks/game.ts";
-import { api, apiEndpoints } from "@/lib/api.ts";
+import { toast } from "sonner";
+import z from "zod";
+import { useUploadImage } from "@/hooks/game.ts";
+import { type ApiTypes, api, apiEndpoints } from "@/lib/api.ts";
import { useSession } from "@/lib/auth.ts";
import { Button } from "../../ui/button";
import { Calendar } from "../../ui/calendar";
import { Checkbox } from "../../ui/checkbox";
-import { Field, FieldLabel } from "../../ui/field";
+import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "../../ui/dialog";
+import { Field, FieldError, FieldLabel } from "../../ui/field";
import { Input } from "../../ui/input";
import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupText } from "../../ui/input-group";
import { Popover, PopoverContent, PopoverTrigger } from "../../ui/popover";
+import { RatingGroupAdvanced } from "../../ui/rating-group-advanced";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../../ui/select";
import { Textarea } from "../../ui/textarea";
+type ProgressStatus = "Planning" | "Playing" | "Completed" | "Paused" | "Dropped";
+
+const STATUS_OPTIONS = ["planning", "playing", "played", "replaying", "dropped", "paused"] as const;
+
+const STATUS_TO_ENUM: Record = {
+ planning: "Planning",
+ playing: "Playing",
+ played: "Completed",
+ replaying: "Playing",
+ dropped: "Dropped",
+ paused: "Paused",
+};
+
+const ENUM_TO_STATUS: Record = {
+ Planning: "planning",
+ Playing: "playing",
+ Completed: "played",
+ Paused: "paused",
+ Dropped: "dropped",
+};
+
+const COMPLETION_OPTIONS = ["mainStory", "mainStoryPlusExtras", "100%", "endless"] as const;
+
+const SUMMARY_MAX_LENGTH = 500;
+const REVIEW_NOTES_MAX_LENGTH = 1000;
+const PROGRESS_NOTES_MAX_LENGTH = 1000;
+
+function createProgressSchema(t: TFunction) {
+ return z.object({
+ status: z.string(),
+ completion: z.string(),
+ progress: z.string(),
+ playCount: z.string(),
+ startDate: z.date().optional(),
+ finishDate: z.date().optional(),
+ notes: z.string().trim().max(PROGRESS_NOTES_MAX_LENGTH, t("feed:progress.errors.notesMax")),
+ });
+}
+
+type ProgressFormData = z.infer>;
+
+function createReviewSchema(t: TFunction) {
+ return z.object({
+ overall: z.string(),
+ graphics: z.string(),
+ sound: z.string(),
+ story: z.string(),
+ gameplay: z.string(),
+ summary: z.string().trim().max(SUMMARY_MAX_LENGTH, t("feed:review.errors.summaryMax")),
+ notes: z.string().trim().max(REVIEW_NOTES_MAX_LENGTH, t("feed:review.errors.notesMax")),
+ recommended: z.boolean(),
+ });
+}
+
+type ReviewFormData = z.infer>;
+
+interface GameProgressData {
+ id: string;
+ status: ProgressStatus;
+ playCount: number | null;
+ completion: string | null;
+ progress: number | null;
+ notes: string | null;
+ startedAt: string | null;
+ completedAt: string | null;
+}
+
+interface GameReviewData {
+ id: string;
+ overall: number | string;
+ graphics: number | string | null;
+ sound: number | string | null;
+ story: number | string | null;
+ gameplay: number | string | null;
+ summary: string | null;
+ notes: string | null;
+ recommended: boolean | null;
+}
+
interface PendingScreenshot {
file: File;
previewUrl: string;
@@ -26,61 +112,235 @@ interface PendingScreenshot {
interface GameModalProps {
gameId?: string;
- initialStartDate?: Date;
- onStatusChange?: (status: string) => void;
- onSaveSuccess?: (status: string, reviewId?: string) => void;
+ onClose?: () => void;
}
-export function GameModal({ gameId, initialStartDate, onStatusChange, onSaveSuccess }: GameModalProps) {
- const [startDate, setStartDate] = useState(initialStartDate);
- const [finishDate, setFinishDate] = useState();
- const [customLists, setCustomLists] = useState(["2026", "Favorites", "Play Later"]);
- const [selectedStatus, setSelectedStatus] = useState(null);
- const [selectedCompletion, setSelectedCompletion] = useState(null);
- const [progress, setProgress] = useState(0);
- const [replays, setReplays] = useState(0);
- const [notes, setNotes] = useState("");
+export function GameModal({ gameId, onClose }: GameModalProps) {
+ const { t } = useTranslation();
+ const queryClient = useQueryClient();
+ const session = useSession();
+ const userId = session?.data?.user?.id;
+ const enabled = !!userId && !!gameId;
+
+ const progressSchema = useMemo(() => createProgressSchema(t), [t]);
+
+ const progressForm = useForm({
+ resolver: zodResolver(progressSchema),
+ defaultValues: {
+ status: "",
+ completion: "",
+ progress: "",
+ playCount: "",
+ startDate: undefined,
+ finishDate: undefined,
+ notes: "",
+ },
+ });
+
+ const progressStatus = progressForm.watch("status");
+ const progressNotes = progressForm.watch("notes") ?? "";
+
+ const reviewSchema = useMemo(() => createReviewSchema(t), [t]);
+
+ const reviewForm = useForm({
+ resolver: zodResolver(reviewSchema),
+ defaultValues: {
+ overall: "0",
+ graphics: "0",
+ sound: "0",
+ story: "0",
+ gameplay: "0",
+ summary: "",
+ notes: "",
+ recommended: false,
+ },
+ });
+
+ const summary = reviewForm.watch("summary") ?? "";
+ const reviewNotes = reviewForm.watch("notes") ?? "";
+
const [newListInput, setNewListInput] = useState(null);
+ const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
const [pendingScreenshots, setPendingScreenshots] = useState([]);
const fileInputRef = useRef(null);
- console.log(selectedCompletion);
+ const uploadImageMutation = useUploadImage();
- const session = useSession();
- const userId = session?.data?.user?.id;
+ const progressQuery = useQuery({
+ queryKey: ["gameProgress", gameId, userId],
+ queryFn: () =>
+ api
+ .get(apiEndpoints.gameProgress, { params: { userId, gameId } })
+ .then(({ data }) => data.gameProgresses.items[0] ?? null),
+ enabled,
+ });
- const gameReviewMutation = useGameReview();
- const uploadImageMutation = useUploadImage();
+ const reviewQuery = useQuery({
+ queryKey: ["gameReview", gameId, userId],
+ queryFn: () =>
+ api
+ .get(`${apiEndpoints.gameReview}/?gameId=${gameId}&userId=${userId}`)
+ .then(({ data }) => data.gameReviews.items[0] ?? null),
+ enabled,
+ });
- const createListMutation = useMutation({
- mutationFn: async (name: string) => {
- const { data } = await api.post(apiEndpoints.list, {
- name,
- userId,
- type: "Game",
+ const listsQuery = useQuery({
+ queryKey: ["gameLists", userId],
+ queryFn: () =>
+ api
+ .get(apiEndpoints.getListsByUserId(userId as string), {
+ params: { type: "Game", itemsPerPage: 50 },
+ })
+ .then(({ data }) => data.lists.items),
+ enabled: !!userId,
+ });
+
+ const lists = listsQuery.data ?? [];
+
+ const listStatusQuery = useQuery({
+ queryKey: ["gameListStatus", gameId, userId],
+ queryFn: () =>
+ api
+ .get(apiEndpoints.getListStatus, {
+ params: { type: "Game", gameId },
+ })
+ .then(({ data }) => data.listIds),
+ enabled,
+ });
+
+ const listIds = listStatusQuery.data ?? [];
+
+ const isInList = (listId: string) => listIds.includes(listId);
+
+ useEffect(() => {
+ const progress = progressQuery.data;
+ if (!progress) return;
+
+ progressForm.reset({
+ status: ENUM_TO_STATUS[progress.status] ?? "",
+ completion: progress.completion ?? "",
+ progress: progress.progress != null ? String(progress.progress) : "",
+ playCount: progress.playCount != null ? String(progress.playCount) : "",
+ notes: progress.notes ?? "",
+ startDate: progress.startedAt ? new Date(progress.startedAt) : undefined,
+ finishDate: progress.completedAt ? new Date(progress.completedAt) : undefined,
+ });
+ }, [progressQuery.data, progressForm.reset]);
+
+ useEffect(() => {
+ const review = reviewQuery.data;
+ if (!review) return;
+
+ reviewForm.reset({
+ overall: String(Number(review.overall)),
+ graphics: review.graphics != null ? String(Number(review.graphics)) : "0",
+ sound: review.sound != null ? String(Number(review.sound)) : "0",
+ story: review.story != null ? String(Number(review.story)) : "0",
+ gameplay: review.gameplay != null ? String(Number(review.gameplay)) : "0",
+ summary: review.summary ?? "",
+ notes: review.notes ?? "",
+ recommended: !!review.recommended,
+ });
+ }, [reviewQuery.data, reviewForm.reset]);
+
+ const invalidateProgress = () => {
+ queryClient.invalidateQueries({ queryKey: ["gameProgress", gameId, userId] });
+ queryClient.invalidateQueries({ queryKey: ["game"] });
+ };
+
+ const saveProgressMutation = useMutation({
+ mutationFn: (data: ProgressFormData) => {
+ const status = STATUS_TO_ENUM[data.status];
+
+ return api.post(apiEndpoints.gameProgress, {
+ gameId,
+ status,
+ playCount: data.playCount ? Number(data.playCount) : undefined,
+ completion: data.completion || undefined,
+ progress: data.progress ? Number(data.progress) : undefined,
+ notes: data.notes.trim() || undefined,
+ startedAt: data.startDate ?? undefined,
+ completedAt: status === "Completed" ? (data.finishDate ?? new Date()) : (data.finishDate ?? undefined),
});
- return data;
},
+ onSuccess: invalidateProgress,
});
- const { t } = useTranslation();
+ const saveReviewMutation = useMutation({
+ mutationFn: async (data: ReviewFormData): Promise => {
+ const body = {
+ gameId,
+ overall: Number(data.overall),
+ graphics: Number(data.graphics) || undefined,
+ sound: Number(data.sound) || undefined,
+ story: Number(data.story) || undefined,
+ gameplay: Number(data.gameplay) || undefined,
+ summary: data.summary.trim() || undefined,
+ notes: data.notes.trim() || undefined,
+ recommended: data.recommended,
+ };
+
+ const existing = reviewQuery.data;
+
+ if (existing) {
+ await api.patch(`${apiEndpoints.gameReview}/${existing.id}`, body);
+ return existing.id;
+ }
+
+ const { data: response } = await api.post(apiEndpoints.gameReview, body);
+ return response.gameReview?.id;
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["gameReview", gameId, userId] });
+ queryClient.invalidateQueries({ queryKey: ["gameReviews", gameId] });
+ queryClient.invalidateQueries({ queryKey: ["game"] });
+ },
+ });
+
+ const deleteProgressMutation = useMutation({
+ mutationFn: () => api.delete(apiEndpoints.resetGameTracking(gameId as string)),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["gameProgress", gameId, userId] });
+ queryClient.invalidateQueries({ queryKey: ["gameReview", gameId, userId] });
+ queryClient.invalidateQueries({ queryKey: ["gameReviews", gameId] });
+ queryClient.invalidateQueries({ queryKey: ["gameScreenshots", gameId] });
+ queryClient.invalidateQueries({ queryKey: ["game"] });
+ setConfirmDeleteOpen(false);
+ onClose?.();
+ },
+ onError: () => toast.error(t("api:INTERNAL_SERVER_ERROR")),
+ });
- const handleAddList = () => setNewListInput("");
+ const toggleListMutation = useMutation({
+ mutationFn: ({ listId, isMember }: { listId: string; isMember: boolean }) => {
+ const body = { type: "Game", listId, userId, gameId };
+
+ return isMember
+ ? api.delete(apiEndpoints.listItem(listId), { data: body })
+ : api.post(apiEndpoints.listItem(listId), body);
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["gameListStatus", gameId, userId] });
+ queryClient.invalidateQueries({ queryKey: ["gameContainingLists", gameId] });
+ },
+ onError: () => toast.error(t("api:INTERNAL_SERVER_ERROR")),
+ });
+
+ const createListMutation = useMutation({
+ mutationFn: (name: string) => api.post(apiEndpoints.list, { name, userId, type: "Game" }),
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ["gameLists", userId] }),
+ onError: () => toast.error(t("api:INTERNAL_SERVER_ERROR")),
+ });
const handleNewListBlur = () => {
- if (newListInput?.trim()) {
- const trimmed = newListInput.trim();
- setCustomLists((prev) => [...prev, trimmed]);
+ const trimmed = newListInput?.trim();
+ if (trimmed) {
createListMutation.mutate(trimmed);
}
setNewListInput(null);
};
- const toggleCustomList = (list: string) => {
- setCustomLists((prev) => (prev.includes(list) ? prev.filter((l) => l !== list) : [...prev, list]));
- };
-
const handleFileSelect = (files: FileList | null) => {
if (!files) return;
const newScreenshots: PendingScreenshot[] = Array.from(files).map((file) => ({
@@ -104,39 +364,54 @@ export function GameModal({ gameId, initialStartDate, onStatusChange, onSaveSucc
});
};
+ const uploadScreenshots = async (gameReviewId: string) => {
+ await Promise.all(
+ pendingScreenshots.map(async (screenshot) => {
+ const imageUrl = await uploadImageMutation.mutateAsync(screenshot.file);
+ await api.post(apiEndpoints.gameReviewScreenshot, {
+ gameReviewId,
+ isSpoiler: screenshot.isSpoiler,
+ url: imageUrl,
+ description: screenshot.description || undefined,
+ });
+ }),
+ );
+ setPendingScreenshots([]);
+ queryClient.invalidateQueries({ queryKey: ["gameScreenshots", gameId] });
+ };
+
const handleSave = async () => {
- if (!selectedStatus || !userId || !gameId) return;
+ const progress = progressForm.getValues();
- if (selectedStatus === "played" || selectedStatus === "replayed") {
- const review = await gameReviewMutation.mutateAsync({
- gameId,
- userId,
- overall: 0,
- notes,
- });
+ if (!gameId || !progress.status) {
+ onClose?.();
+ return;
+ }
+
+ try {
+ if (!(await progressForm.trigger())) return;
+
+ await saveProgressMutation.mutateAsync(progress);
+
+ const review = reviewForm.getValues();
+
+ if ((progress.status === "played" || progress.status === "dropped") && Number(review.overall) > 0) {
+ if (!(await reviewForm.trigger())) return;
- if (pendingScreenshots.length > 0 && review?.id) {
- await Promise.all(
- pendingScreenshots.map(async (screenshot) => {
- const imageUrl = await uploadImageMutation.mutateAsync(screenshot.file);
- await api.post(apiEndpoints.gameReviewScreenshot, {
- gameReviewId: review.id,
- isSpoiler: screenshot.isSpoiler,
- url: imageUrl,
- description: screenshot.description || undefined,
- });
- }),
- );
+ const reviewId = await saveReviewMutation.mutateAsync(review);
+
+ if (reviewId && pendingScreenshots.length > 0) {
+ await uploadScreenshots(reviewId);
+ }
}
- onSaveSuccess?.(selectedStatus, review?.id);
- return;
+ onClose?.();
+ } catch {
+ toast.error(t("api:INTERNAL_SERVER_ERROR"));
}
-
- onSaveSuccess?.(selectedStatus);
};
- const isSaving = gameReviewMutation.isPending || uploadImageMutation.isPending || createListMutation.isPending;
+ const isSaving = saveProgressMutation.isPending || saveReviewMutation.isPending || uploadImageMutation.isPending;
return (
@@ -152,49 +427,52 @@ export function GameModal({ gameId, initialStartDate, onStatusChange, onSaveSucc
{t("library:status")}
-
+ (
+
+ )}
+ />
{t("feed:completionStatus.label")}
-
+ (
+
+ )}
+ />
@@ -208,8 +486,7 @@ export function GameModal({ gameId, initialStartDate, onStatusChange, onSaveSucc
min={0}
max={100}
placeholder="0"
- value={progress}
- onChange={(e) => setProgress(Number(e.target.value))}
+ {...progressForm.register("progress")}
/>
%
@@ -227,8 +504,7 @@ export function GameModal({ gameId, initialStartDate, onStatusChange, onSaveSucc
min={0}
placeholder="0"
className="bg-background"
- value={replays}
- onChange={(e) => setReplays(Number(e.target.value))}
+ {...progressForm.register("playCount")}
/>
@@ -246,50 +522,62 @@ export function GameModal({ gameId, initialStartDate, onStatusChange, onSaveSucc
{t("feed:startDate")}
-
-
-
-
-
-
-
-
+ (
+
+
+
+
+
+
+
+
+ )}
+ />
{t("feed:finishDate")}
-
-
-
-
-
-
-
-
+ (
+
+
+
+
+
+
+
+
+ )}
+ />
@@ -385,45 +673,46 @@ export function GameModal({ gameId, initialStartDate, onStatusChange, onSaveSucc
-
+
-
-
+
+
{t("feed:customLists")}
-
-
- {["2026", "Favorites", "Play Later"].map((list) => (
-
+
+ {lists.map((list) => (
+
toggleCustomList(list)}
+ id={list.id}
+ checked={isInList(list.id)}
+ disabled={toggleListMutation.isPending}
+ onCheckedChange={() => toggleListMutation.mutate({ listId: list.id, isMember: isInList(list.id) })}
/>
-
- {list}
-
-
- ))}
- {customLists.slice(3).map((list) => (
-
- toggleCustomList(list)}
- />
-
- {list}
+
+ {list.name}
))}
@@ -438,23 +727,214 @@ export function GameModal({ gameId, initialStartDate, onStatusChange, onSaveSucc
onKeyDown={(e) => e.key === "Enter" && e.currentTarget.blur()}
placeholder={t("feed:newList")}
className="h-6 text-sm bg-background px-2 py-0"
+ aria-label={t("feed:newList")}
/>
)}
+ {lists.length === 0 && newListInput === null && (
+
{t("library:noLists")}
+ )}
-
-
+ {(progressStatus === "played" || progressStatus === "dropped" || progressStatus === "replaying") && (
+
+
+
+ {t("feed:review")}
+
+
+
+
+
+ {t("feed:overall")}
+ (
+
+ )}
+ />
+
+
+ {t("feed:criteries.graphics")}
+ (
+
+ )}
+ />
+
+
+ {t("feed:criteries.soundtrack")}
+ (
+
+ )}
+ />
+
+
+ {t("feed:criteries.story")}
+ (
+
+ )}
+ />
+
+
+ {t("feed:criteries.gameplay")}
+ (
+
+ )}
+ />
+
+
+
+
+ {t("feed:summary")}
+
+
+
+ {reviewForm.formState.errors.summary?.message ? (
+ {reviewForm.formState.errors.summary.message}
+ ) : (
+
+ )}
+
+ {summary.length}/{SUMMARY_MAX_LENGTH}
+
+
+
+
+
+ {t("feed:notes")}
+
+
+
+ {reviewForm.formState.errors.notes?.message ? (
+ {reviewForm.formState.errors.notes.message}
+ ) : (
+
+ )}
+
+ {reviewNotes.length}/{REVIEW_NOTES_MAX_LENGTH}
+
+
+
+
+ (
+ field.onChange(checked === true)}
+ />
+ )}
+ />
+
+ {t("feed:recommended")}
+
+
+
+
+ )}
+
+
+
setConfirmDeleteOpen(true)}
+ >
+
+
-
+ onClose?.()}>
{t("feed:cancel")}
-
+
{isSaving ? t("feed:saving") : t("feed:save")}
diff --git a/src/lib/api.ts b/src/lib/api.ts
index 424cd1e..e6d1b04 100644
--- a/src/lib/api.ts
+++ b/src/lib/api.ts
@@ -5,7 +5,8 @@ export const api = axios.create({
baseURL: import.meta.env.VITE_API_URL,
withCredentials: true,
headers: {
- "X-TrackGeek-Language": window?.localStorage?.getItem(LANGUAGE_TOKEN) ?? DEFAULT_LANGUAGE,
+ "X-TrackGeek-Language":
+ (typeof window !== "undefined" && window.localStorage?.getItem(LANGUAGE_TOKEN)) || DEFAULT_LANGUAGE,
"X-TrackGeek-Version": "1.0.0",
},
});
@@ -860,6 +861,8 @@ export const apiEndpoints = {
gameReviewScreenshot: "/game/review/screenshot",
gameReview: "/game/review",
gameProgress: "/game/progress",
+ getGameProgress: (userId: string, gameId: string) => `/game/progress?userId=${userId}&gameId=${gameId}`,
+ resetGameTracking: (gameId: string) => `/game/tracking/${gameId}`,
refreshGameData: "/game/refresh",
getAnimeDetails: (id: string) => `/anime/detail/${id}`,
getAnimeEpisodeDetails: (id: string) => `/anime/detail/${id}/episode`,
diff --git a/src/lib/i18n/config.ts b/src/lib/i18n/config.ts
index b89803a..104f917 100644
--- a/src/lib/i18n/config.ts
+++ b/src/lib/i18n/config.ts
@@ -32,7 +32,7 @@ i18next
.init({
resources: resources as Resource,
supportedLngs: SUPPORTED_LANGUAGES.map((lang) => lang.id),
- lng: window.localStorage.getItem(LANGUAGE_TOKEN) ?? DEFAULT_LANGUAGE,
+ lng: (typeof window !== "undefined" && window.localStorage.getItem(LANGUAGE_TOKEN)) || DEFAULT_LANGUAGE,
fallbackLng: DEFAULT_LANGUAGE,
interpolation: {
escapeValue: false,
diff --git a/src/lib/utils/seo.ts b/src/lib/utils/seo.ts
index 6f74115..847a765 100644
--- a/src/lib/utils/seo.ts
+++ b/src/lib/utils/seo.ts
@@ -11,8 +11,8 @@ export const seo = ({
keywords = "media tracker, open-source, self-hostable, progress tracker, anime list, game backlog, movie watchlist, TV show stats, manga tracker, book tracker, privacy-focused, entertainment dashboard",
image = "/logo-128.png",
}: SeoOptions) => {
+ const origin = typeof window !== "undefined" ? window.location.origin : (import.meta.env.VITE_SITE_URL ?? "");
const url = typeof document !== "undefined" ? document.URL : "";
- const origin = typeof window !== "undefined" ? window.location.origin : "";
const resolvedImage = image?.startsWith("http") ? image : `${origin}${image}`;
return [
diff --git a/src/main.tsx b/src/main.tsx
deleted file mode 100644
index dd29297..0000000
--- a/src/main.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { createRouter, RouterProvider } from "@tanstack/react-router";
-import { StrictMode } from "react";
-import { createRoot } from "react-dom/client";
-import { NotFoundComponent } from "@/components/shared/404.tsx";
-import { authClient } from "./lib/auth";
-import { routeTree } from "./routeTree.gen";
-
-const router = createRouter({
- routeTree,
- defaultPreload: "intent",
- scrollRestoration: true,
- context: {
- auth: authClient,
- },
- defaultNotFoundComponent: () => {
- return ;
- },
-});
-
-declare module "@tanstack/react-router" {
- interface Register {
- router: typeof router;
- }
-}
-
-const rootElement = document.getElementById("root");
-
-if (rootElement && !rootElement.innerHTML) {
- const root = createRoot(rootElement);
-
- root.render(
-
-
- ,
- );
-}
diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts
index bd0c5b2..e0ccea5 100644
--- a/src/routeTree.gen.ts
+++ b/src/routeTree.gen.ts
@@ -9,87 +9,86 @@
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root'
-import { Route as TosRouteImport } from './routes/tos'
-import { Route as SearchRouteImport } from './routes/search'
-import { Route as ResetPasswordRouteImport } from './routes/reset-password'
-import { Route as PrivacyPolicyRouteImport } from './routes/privacy-policy'
-import { Route as FeedRouteImport } from './routes/feed'
-import { Route as DonateRouteImport } from './routes/donate'
-import { Route as CreditsRouteImport } from './routes/credits'
-import { Route as CompareRouteImport } from './routes/compare'
-import { Route as AddDataRouteImport } from './routes/add-data'
-import { Route as AuthenticatedRouteImport } from './routes/_authenticated'
import { Route as IndexRouteImport } from './routes/index'
-import { Route as TvIndexRouteImport } from './routes/tv/index'
-import { Route as MovieIndexRouteImport } from './routes/movie/index'
-import { Route as MangaIndexRouteImport } from './routes/manga/index'
-import { Route as GameIndexRouteImport } from './routes/game/index'
-import { Route as BookIndexRouteImport } from './routes/book/index'
+import { Route as AuthenticatedRouteImport } from './routes/_authenticated'
+import { Route as AddDataRouteImport } from './routes/add-data'
+import { Route as CompareRouteImport } from './routes/compare'
+import { Route as CreditsRouteImport } from './routes/credits'
+import { Route as DonateRouteImport } from './routes/donate'
+import { Route as FeedRouteImport } from './routes/feed'
+import { Route as PrivacyPolicyRouteImport } from './routes/privacy-policy'
+import { Route as ResetPasswordRouteImport } from './routes/reset-password'
+import { Route as SearchRouteImport } from './routes/search'
+import { Route as TosRouteImport } from './routes/tos'
+import { Route as AuthenticatedBillingRouteImport } from './routes/_authenticated/billing'
+import { Route as AuthenticatedNotificationsRouteImport } from './routes/_authenticated/notifications'
+import { Route as AuthenticatedSettingsRouteImport } from './routes/_authenticated/settings'
import { Route as AnimeIndexRouteImport } from './routes/anime/index'
-import { Route as TvSlugRouteImport } from './routes/tv/$slug'
-import { Route as ReviewIdRouteImport } from './routes/review/$id'
-import { Route as PostIdRouteImport } from './routes/post/$id'
-import { Route as MovieSlugRouteImport } from './routes/movie/$slug'
-import { Route as MangaSlugRouteImport } from './routes/manga/$slug'
-import { Route as ListSlugRouteImport } from './routes/list/$slug'
-import { Route as GameSlugRouteImport } from './routes/game/$slug'
-import { Route as BookSlugRouteImport } from './routes/book/$slug'
import { Route as AnimeSlugRouteImport } from './routes/anime/$slug'
-import { Route as AuthenticatedSettingsRouteImport } from './routes/_authenticated/settings'
-import { Route as AuthenticatedNotificationsRouteImport } from './routes/_authenticated/notifications'
-import { Route as AuthenticatedBillingRouteImport } from './routes/_authenticated/billing'
-import { Route as UserUsernameIndexRouteImport } from './routes/user/$username/index'
-import { Route as TvUpcomingIndexRouteImport } from './routes/tv/upcoming/index'
-import { Route as TvTrendingIndexRouteImport } from './routes/tv/trending/index'
-import { Route as TvPopularIndexRouteImport } from './routes/tv/popular/index'
-import { Route as TvAiringIndexRouteImport } from './routes/tv/airing/index'
-import { Route as MovieUpcomingIndexRouteImport } from './routes/movie/upcoming/index'
-import { Route as MovieTrendingIndexRouteImport } from './routes/movie/trending/index'
-import { Route as MoviePopularIndexRouteImport } from './routes/movie/popular/index'
-import { Route as MovieAiringIndexRouteImport } from './routes/movie/airing/index'
-import { Route as MangaUpcomingIndexRouteImport } from './routes/manga/upcoming/index'
-import { Route as MangaTopIndexRouteImport } from './routes/manga/top/index'
-import { Route as MangaRecommendationsIndexRouteImport } from './routes/manga/recommendations/index'
-import { Route as MangaPublishingIndexRouteImport } from './routes/manga/publishing/index'
-import { Route as GameUpcomingIndexRouteImport } from './routes/game/upcoming/index'
-import { Route as GameRecentIndexRouteImport } from './routes/game/recent/index'
-import { Route as GamePopularIndexRouteImport } from './routes/game/popular/index'
-import { Route as GameAnticipatedIndexRouteImport } from './routes/game/anticipated/index'
-import { Route as BookUpcomingIndexRouteImport } from './routes/book/upcoming/index'
-import { Route as BookTrendingIndexRouteImport } from './routes/book/trending/index'
-import { Route as AnimeUpcomingIndexRouteImport } from './routes/anime/upcoming/index'
-import { Route as AnimeTopIndexRouteImport } from './routes/anime/top/index'
-import { Route as AnimeRecommendationsIndexRouteImport } from './routes/anime/recommendations/index'
+import { Route as BookIndexRouteImport } from './routes/book/index'
+import { Route as BookSlugRouteImport } from './routes/book/$slug'
+import { Route as GameIndexRouteImport } from './routes/game/index'
+import { Route as GameSlugRouteImport } from './routes/game/$slug'
+import { Route as ListSlugRouteImport } from './routes/list/$slug'
+import { Route as MangaIndexRouteImport } from './routes/manga/index'
+import { Route as MangaSlugRouteImport } from './routes/manga/$slug'
+import { Route as MovieIndexRouteImport } from './routes/movie/index'
+import { Route as MovieSlugRouteImport } from './routes/movie/$slug'
+import { Route as PostIdRouteImport } from './routes/post/$id'
+import { Route as ReviewIdRouteImport } from './routes/review/$id'
+import { Route as TvIndexRouteImport } from './routes/tv/index'
+import { Route as TvSlugRouteImport } from './routes/tv/$slug'
import { Route as AnimeAiringIndexRouteImport } from './routes/anime/airing/index'
-import { Route as MovieFranchisesSlugRouteImport } from './routes/movie/franchises/$slug'
-import { Route as GameFranchisesSlugRouteImport } from './routes/game/franchises/$slug'
+import { Route as AnimeRecommendationsIndexRouteImport } from './routes/anime/recommendations/index'
+import { Route as AnimeTopIndexRouteImport } from './routes/anime/top/index'
+import { Route as AnimeUpcomingIndexRouteImport } from './routes/anime/upcoming/index'
import { Route as BookFranchisesSlugRouteImport } from './routes/book/franchises/$slug'
-import { Route as AuthenticatedDonateSuccessIndexRouteImport } from './routes/_authenticated/donate/success/index'
+import { Route as BookTrendingIndexRouteImport } from './routes/book/trending/index'
+import { Route as BookUpcomingIndexRouteImport } from './routes/book/upcoming/index'
+import { Route as GameAnticipatedIndexRouteImport } from './routes/game/anticipated/index'
+import { Route as GameFranchisesSlugRouteImport } from './routes/game/franchises/$slug'
+import { Route as GamePopularIndexRouteImport } from './routes/game/popular/index'
+import { Route as GameRecentIndexRouteImport } from './routes/game/recent/index'
+import { Route as GameUpcomingIndexRouteImport } from './routes/game/upcoming/index'
+import { Route as MangaPublishingIndexRouteImport } from './routes/manga/publishing/index'
+import { Route as MangaRecommendationsIndexRouteImport } from './routes/manga/recommendations/index'
+import { Route as MangaTopIndexRouteImport } from './routes/manga/top/index'
+import { Route as MangaUpcomingIndexRouteImport } from './routes/manga/upcoming/index'
+import { Route as MovieAiringIndexRouteImport } from './routes/movie/airing/index'
+import { Route as MovieFranchisesSlugRouteImport } from './routes/movie/franchises/$slug'
+import { Route as MoviePopularIndexRouteImport } from './routes/movie/popular/index'
+import { Route as MovieTrendingIndexRouteImport } from './routes/movie/trending/index'
+import { Route as MovieUpcomingIndexRouteImport } from './routes/movie/upcoming/index'
+import { Route as TvAiringIndexRouteImport } from './routes/tv/airing/index'
+import { Route as TvPopularIndexRouteImport } from './routes/tv/popular/index'
+import { Route as TvTrendingIndexRouteImport } from './routes/tv/trending/index'
+import { Route as TvUpcomingIndexRouteImport } from './routes/tv/upcoming/index'
+import { Route as UserUsernameIndexRouteImport } from './routes/user/$username/index'
import { Route as AuthenticatedDonateErrorIndexRouteImport } from './routes/_authenticated/donate/error/index'
+import { Route as AuthenticatedDonateSuccessIndexRouteImport } from './routes/_authenticated/donate/success/index'
-const TosRoute = TosRouteImport.update({
- id: '/tos',
- path: '/tos',
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
getParentRoute: () => rootRouteImport,
} as any)
-const SearchRoute = SearchRouteImport.update({
- id: '/search',
- path: '/search',
+const AuthenticatedRoute = AuthenticatedRouteImport.update({
+ id: '/_authenticated',
getParentRoute: () => rootRouteImport,
} as any)
-const ResetPasswordRoute = ResetPasswordRouteImport.update({
- id: '/reset-password',
- path: '/reset-password',
+const AddDataRoute = AddDataRouteImport.update({
+ id: '/add-data',
+ path: '/add-data',
getParentRoute: () => rootRouteImport,
} as any)
-const PrivacyPolicyRoute = PrivacyPolicyRouteImport.update({
- id: '/privacy-policy',
- path: '/privacy-policy',
+const CompareRoute = CompareRouteImport.update({
+ id: '/compare',
+ path: '/compare',
getParentRoute: () => rootRouteImport,
} as any)
-const FeedRoute = FeedRouteImport.update({
- id: '/feed',
- path: '/feed',
+const CreditsRoute = CreditsRouteImport.update({
+ id: '/credits',
+ path: '/credits',
getParentRoute: () => rootRouteImport,
} as any)
const DonateRoute = DonateRouteImport.update({
@@ -97,48 +96,55 @@ const DonateRoute = DonateRouteImport.update({
path: '/donate',
getParentRoute: () => rootRouteImport,
} as any)
-const CreditsRoute = CreditsRouteImport.update({
- id: '/credits',
- path: '/credits',
+const FeedRoute = FeedRouteImport.update({
+ id: '/feed',
+ path: '/feed',
getParentRoute: () => rootRouteImport,
} as any)
-const CompareRoute = CompareRouteImport.update({
- id: '/compare',
- path: '/compare',
+const PrivacyPolicyRoute = PrivacyPolicyRouteImport.update({
+ id: '/privacy-policy',
+ path: '/privacy-policy',
getParentRoute: () => rootRouteImport,
} as any)
-const AddDataRoute = AddDataRouteImport.update({
- id: '/add-data',
- path: '/add-data',
+const ResetPasswordRoute = ResetPasswordRouteImport.update({
+ id: '/reset-password',
+ path: '/reset-password',
getParentRoute: () => rootRouteImport,
} as any)
-const AuthenticatedRoute = AuthenticatedRouteImport.update({
- id: '/_authenticated',
+const SearchRoute = SearchRouteImport.update({
+ id: '/search',
+ path: '/search',
getParentRoute: () => rootRouteImport,
} as any)
-const IndexRoute = IndexRouteImport.update({
- id: '/',
- path: '/',
+const TosRoute = TosRouteImport.update({
+ id: '/tos',
+ path: '/tos',
getParentRoute: () => rootRouteImport,
} as any)
-const TvIndexRoute = TvIndexRouteImport.update({
- id: '/tv/',
- path: '/tv/',
- getParentRoute: () => rootRouteImport,
+const AuthenticatedBillingRoute = AuthenticatedBillingRouteImport.update({
+ id: '/billing',
+ path: '/billing',
+ getParentRoute: () => AuthenticatedRoute,
} as any)
-const MovieIndexRoute = MovieIndexRouteImport.update({
- id: '/movie/',
- path: '/movie/',
- getParentRoute: () => rootRouteImport,
+const AuthenticatedNotificationsRoute =
+ AuthenticatedNotificationsRouteImport.update({
+ id: '/notifications',
+ path: '/notifications',
+ getParentRoute: () => AuthenticatedRoute,
+ } as any)
+const AuthenticatedSettingsRoute = AuthenticatedSettingsRouteImport.update({
+ id: '/settings',
+ path: '/settings',
+ getParentRoute: () => AuthenticatedRoute,
} as any)
-const MangaIndexRoute = MangaIndexRouteImport.update({
- id: '/manga/',
- path: '/manga/',
+const AnimeIndexRoute = AnimeIndexRouteImport.update({
+ id: '/anime/',
+ path: '/anime/',
getParentRoute: () => rootRouteImport,
} as any)
-const GameIndexRoute = GameIndexRouteImport.update({
- id: '/game/',
- path: '/game/',
+const AnimeSlugRoute = AnimeSlugRouteImport.update({
+ id: '/anime/$slug',
+ path: '/anime/$slug',
getParentRoute: () => rootRouteImport,
} as any)
const BookIndexRoute = BookIndexRouteImport.update({
@@ -146,29 +152,29 @@ const BookIndexRoute = BookIndexRouteImport.update({
path: '/book/',
getParentRoute: () => rootRouteImport,
} as any)
-const AnimeIndexRoute = AnimeIndexRouteImport.update({
- id: '/anime/',
- path: '/anime/',
+const BookSlugRoute = BookSlugRouteImport.update({
+ id: '/book/$slug',
+ path: '/book/$slug',
getParentRoute: () => rootRouteImport,
} as any)
-const TvSlugRoute = TvSlugRouteImport.update({
- id: '/tv/$slug',
- path: '/tv/$slug',
+const GameIndexRoute = GameIndexRouteImport.update({
+ id: '/game/',
+ path: '/game/',
getParentRoute: () => rootRouteImport,
} as any)
-const ReviewIdRoute = ReviewIdRouteImport.update({
- id: '/review/$id',
- path: '/review/$id',
+const GameSlugRoute = GameSlugRouteImport.update({
+ id: '/game/$slug',
+ path: '/game/$slug',
getParentRoute: () => rootRouteImport,
} as any)
-const PostIdRoute = PostIdRouteImport.update({
- id: '/post/$id',
- path: '/post/$id',
+const ListSlugRoute = ListSlugRouteImport.update({
+ id: '/list/$slug',
+ path: '/list/$slug',
getParentRoute: () => rootRouteImport,
} as any)
-const MovieSlugRoute = MovieSlugRouteImport.update({
- id: '/movie/$slug',
- path: '/movie/$slug',
+const MangaIndexRoute = MangaIndexRouteImport.update({
+ id: '/manga/',
+ path: '/manga/',
getParentRoute: () => rootRouteImport,
} as any)
const MangaSlugRoute = MangaSlugRouteImport.update({
@@ -176,95 +182,100 @@ const MangaSlugRoute = MangaSlugRouteImport.update({
path: '/manga/$slug',
getParentRoute: () => rootRouteImport,
} as any)
-const ListSlugRoute = ListSlugRouteImport.update({
- id: '/list/$slug',
- path: '/list/$slug',
+const MovieIndexRoute = MovieIndexRouteImport.update({
+ id: '/movie/',
+ path: '/movie/',
getParentRoute: () => rootRouteImport,
} as any)
-const GameSlugRoute = GameSlugRouteImport.update({
- id: '/game/$slug',
- path: '/game/$slug',
+const MovieSlugRoute = MovieSlugRouteImport.update({
+ id: '/movie/$slug',
+ path: '/movie/$slug',
getParentRoute: () => rootRouteImport,
} as any)
-const BookSlugRoute = BookSlugRouteImport.update({
- id: '/book/$slug',
- path: '/book/$slug',
+const PostIdRoute = PostIdRouteImport.update({
+ id: '/post/$id',
+ path: '/post/$id',
getParentRoute: () => rootRouteImport,
} as any)
-const AnimeSlugRoute = AnimeSlugRouteImport.update({
- id: '/anime/$slug',
- path: '/anime/$slug',
+const ReviewIdRoute = ReviewIdRouteImport.update({
+ id: '/review/$id',
+ path: '/review/$id',
getParentRoute: () => rootRouteImport,
} as any)
-const AuthenticatedSettingsRoute = AuthenticatedSettingsRouteImport.update({
- id: '/settings',
- path: '/settings',
- getParentRoute: () => AuthenticatedRoute,
+const TvIndexRoute = TvIndexRouteImport.update({
+ id: '/tv/',
+ path: '/tv/',
+ getParentRoute: () => rootRouteImport,
} as any)
-const AuthenticatedNotificationsRoute =
- AuthenticatedNotificationsRouteImport.update({
- id: '/notifications',
- path: '/notifications',
- getParentRoute: () => AuthenticatedRoute,
- } as any)
-const AuthenticatedBillingRoute = AuthenticatedBillingRouteImport.update({
- id: '/billing',
- path: '/billing',
- getParentRoute: () => AuthenticatedRoute,
+const TvSlugRoute = TvSlugRouteImport.update({
+ id: '/tv/$slug',
+ path: '/tv/$slug',
+ getParentRoute: () => rootRouteImport,
} as any)
-const UserUsernameIndexRoute = UserUsernameIndexRouteImport.update({
- id: '/user/$username/',
- path: '/user/$username/',
+const AnimeAiringIndexRoute = AnimeAiringIndexRouteImport.update({
+ id: '/anime/airing/',
+ path: '/anime/airing/',
getParentRoute: () => rootRouteImport,
} as any)
-const TvUpcomingIndexRoute = TvUpcomingIndexRouteImport.update({
- id: '/tv/upcoming/',
- path: '/tv/upcoming/',
+const AnimeRecommendationsIndexRoute =
+ AnimeRecommendationsIndexRouteImport.update({
+ id: '/anime/recommendations/',
+ path: '/anime/recommendations/',
+ getParentRoute: () => rootRouteImport,
+ } as any)
+const AnimeTopIndexRoute = AnimeTopIndexRouteImport.update({
+ id: '/anime/top/',
+ path: '/anime/top/',
getParentRoute: () => rootRouteImport,
} as any)
-const TvTrendingIndexRoute = TvTrendingIndexRouteImport.update({
- id: '/tv/trending/',
- path: '/tv/trending/',
+const AnimeUpcomingIndexRoute = AnimeUpcomingIndexRouteImport.update({
+ id: '/anime/upcoming/',
+ path: '/anime/upcoming/',
getParentRoute: () => rootRouteImport,
} as any)
-const TvPopularIndexRoute = TvPopularIndexRouteImport.update({
- id: '/tv/popular/',
- path: '/tv/popular/',
+const BookFranchisesSlugRoute = BookFranchisesSlugRouteImport.update({
+ id: '/book/franchises/$slug',
+ path: '/book/franchises/$slug',
getParentRoute: () => rootRouteImport,
} as any)
-const TvAiringIndexRoute = TvAiringIndexRouteImport.update({
- id: '/tv/airing/',
- path: '/tv/airing/',
+const BookTrendingIndexRoute = BookTrendingIndexRouteImport.update({
+ id: '/book/trending/',
+ path: '/book/trending/',
getParentRoute: () => rootRouteImport,
} as any)
-const MovieUpcomingIndexRoute = MovieUpcomingIndexRouteImport.update({
- id: '/movie/upcoming/',
- path: '/movie/upcoming/',
+const BookUpcomingIndexRoute = BookUpcomingIndexRouteImport.update({
+ id: '/book/upcoming/',
+ path: '/book/upcoming/',
getParentRoute: () => rootRouteImport,
} as any)
-const MovieTrendingIndexRoute = MovieTrendingIndexRouteImport.update({
- id: '/movie/trending/',
- path: '/movie/trending/',
+const GameAnticipatedIndexRoute = GameAnticipatedIndexRouteImport.update({
+ id: '/game/anticipated/',
+ path: '/game/anticipated/',
getParentRoute: () => rootRouteImport,
} as any)
-const MoviePopularIndexRoute = MoviePopularIndexRouteImport.update({
- id: '/movie/popular/',
- path: '/movie/popular/',
+const GameFranchisesSlugRoute = GameFranchisesSlugRouteImport.update({
+ id: '/game/franchises/$slug',
+ path: '/game/franchises/$slug',
getParentRoute: () => rootRouteImport,
} as any)
-const MovieAiringIndexRoute = MovieAiringIndexRouteImport.update({
- id: '/movie/airing/',
- path: '/movie/airing/',
+const GamePopularIndexRoute = GamePopularIndexRouteImport.update({
+ id: '/game/popular/',
+ path: '/game/popular/',
getParentRoute: () => rootRouteImport,
} as any)
-const MangaUpcomingIndexRoute = MangaUpcomingIndexRouteImport.update({
- id: '/manga/upcoming/',
- path: '/manga/upcoming/',
+const GameRecentIndexRoute = GameRecentIndexRouteImport.update({
+ id: '/game/recent/',
+ path: '/game/recent/',
getParentRoute: () => rootRouteImport,
} as any)
-const MangaTopIndexRoute = MangaTopIndexRouteImport.update({
- id: '/manga/top/',
- path: '/manga/top/',
+const GameUpcomingIndexRoute = GameUpcomingIndexRouteImport.update({
+ id: '/game/upcoming/',
+ path: '/game/upcoming/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const MangaPublishingIndexRoute = MangaPublishingIndexRouteImport.update({
+ id: '/manga/publishing/',
+ path: '/manga/publishing/',
getParentRoute: () => rootRouteImport,
} as any)
const MangaRecommendationsIndexRoute =
@@ -273,89 +284,78 @@ const MangaRecommendationsIndexRoute =
path: '/manga/recommendations/',
getParentRoute: () => rootRouteImport,
} as any)
-const MangaPublishingIndexRoute = MangaPublishingIndexRouteImport.update({
- id: '/manga/publishing/',
- path: '/manga/publishing/',
- getParentRoute: () => rootRouteImport,
-} as any)
-const GameUpcomingIndexRoute = GameUpcomingIndexRouteImport.update({
- id: '/game/upcoming/',
- path: '/game/upcoming/',
+const MangaTopIndexRoute = MangaTopIndexRouteImport.update({
+ id: '/manga/top/',
+ path: '/manga/top/',
getParentRoute: () => rootRouteImport,
} as any)
-const GameRecentIndexRoute = GameRecentIndexRouteImport.update({
- id: '/game/recent/',
- path: '/game/recent/',
+const MangaUpcomingIndexRoute = MangaUpcomingIndexRouteImport.update({
+ id: '/manga/upcoming/',
+ path: '/manga/upcoming/',
getParentRoute: () => rootRouteImport,
} as any)
-const GamePopularIndexRoute = GamePopularIndexRouteImport.update({
- id: '/game/popular/',
- path: '/game/popular/',
+const MovieAiringIndexRoute = MovieAiringIndexRouteImport.update({
+ id: '/movie/airing/',
+ path: '/movie/airing/',
getParentRoute: () => rootRouteImport,
} as any)
-const GameAnticipatedIndexRoute = GameAnticipatedIndexRouteImport.update({
- id: '/game/anticipated/',
- path: '/game/anticipated/',
+const MovieFranchisesSlugRoute = MovieFranchisesSlugRouteImport.update({
+ id: '/movie/franchises/$slug',
+ path: '/movie/franchises/$slug',
getParentRoute: () => rootRouteImport,
} as any)
-const BookUpcomingIndexRoute = BookUpcomingIndexRouteImport.update({
- id: '/book/upcoming/',
- path: '/book/upcoming/',
+const MoviePopularIndexRoute = MoviePopularIndexRouteImport.update({
+ id: '/movie/popular/',
+ path: '/movie/popular/',
getParentRoute: () => rootRouteImport,
} as any)
-const BookTrendingIndexRoute = BookTrendingIndexRouteImport.update({
- id: '/book/trending/',
- path: '/book/trending/',
+const MovieTrendingIndexRoute = MovieTrendingIndexRouteImport.update({
+ id: '/movie/trending/',
+ path: '/movie/trending/',
getParentRoute: () => rootRouteImport,
} as any)
-const AnimeUpcomingIndexRoute = AnimeUpcomingIndexRouteImport.update({
- id: '/anime/upcoming/',
- path: '/anime/upcoming/',
+const MovieUpcomingIndexRoute = MovieUpcomingIndexRouteImport.update({
+ id: '/movie/upcoming/',
+ path: '/movie/upcoming/',
getParentRoute: () => rootRouteImport,
} as any)
-const AnimeTopIndexRoute = AnimeTopIndexRouteImport.update({
- id: '/anime/top/',
- path: '/anime/top/',
+const TvAiringIndexRoute = TvAiringIndexRouteImport.update({
+ id: '/tv/airing/',
+ path: '/tv/airing/',
getParentRoute: () => rootRouteImport,
} as any)
-const AnimeRecommendationsIndexRoute =
- AnimeRecommendationsIndexRouteImport.update({
- id: '/anime/recommendations/',
- path: '/anime/recommendations/',
- getParentRoute: () => rootRouteImport,
- } as any)
-const AnimeAiringIndexRoute = AnimeAiringIndexRouteImport.update({
- id: '/anime/airing/',
- path: '/anime/airing/',
+const TvPopularIndexRoute = TvPopularIndexRouteImport.update({
+ id: '/tv/popular/',
+ path: '/tv/popular/',
getParentRoute: () => rootRouteImport,
} as any)
-const MovieFranchisesSlugRoute = MovieFranchisesSlugRouteImport.update({
- id: '/movie/franchises/$slug',
- path: '/movie/franchises/$slug',
+const TvTrendingIndexRoute = TvTrendingIndexRouteImport.update({
+ id: '/tv/trending/',
+ path: '/tv/trending/',
getParentRoute: () => rootRouteImport,
} as any)
-const GameFranchisesSlugRoute = GameFranchisesSlugRouteImport.update({
- id: '/game/franchises/$slug',
- path: '/game/franchises/$slug',
+const TvUpcomingIndexRoute = TvUpcomingIndexRouteImport.update({
+ id: '/tv/upcoming/',
+ path: '/tv/upcoming/',
getParentRoute: () => rootRouteImport,
} as any)
-const BookFranchisesSlugRoute = BookFranchisesSlugRouteImport.update({
- id: '/book/franchises/$slug',
- path: '/book/franchises/$slug',
+const UserUsernameIndexRoute = UserUsernameIndexRouteImport.update({
+ id: '/user/$username/',
+ path: '/user/$username/',
getParentRoute: () => rootRouteImport,
} as any)
-const AuthenticatedDonateSuccessIndexRoute =
- AuthenticatedDonateSuccessIndexRouteImport.update({
- id: '/donate/success/',
- path: '/donate/success/',
- getParentRoute: () => AuthenticatedRoute,
- } as any)
const AuthenticatedDonateErrorIndexRoute =
AuthenticatedDonateErrorIndexRouteImport.update({
id: '/donate/error/',
path: '/donate/error/',
getParentRoute: () => AuthenticatedRoute,
} as any)
+const AuthenticatedDonateSuccessIndexRoute =
+ AuthenticatedDonateSuccessIndexRouteImport.update({
+ id: '/donate/success/',
+ path: '/donate/success/',
+ getParentRoute: () => AuthenticatedRoute,
+ } as any)
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
@@ -768,46 +768,32 @@ export interface RootRouteChildren {
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
- '/tos': {
- id: '/tos'
- path: '/tos'
- fullPath: '/tos'
- preLoaderRoute: typeof TosRouteImport
- parentRoute: typeof rootRouteImport
- }
- '/search': {
- id: '/search'
- path: '/search'
- fullPath: '/search'
- preLoaderRoute: typeof SearchRouteImport
- parentRoute: typeof rootRouteImport
- }
- '/reset-password': {
- id: '/reset-password'
- path: '/reset-password'
- fullPath: '/reset-password'
- preLoaderRoute: typeof ResetPasswordRouteImport
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
- '/privacy-policy': {
- id: '/privacy-policy'
- path: '/privacy-policy'
- fullPath: '/privacy-policy'
- preLoaderRoute: typeof PrivacyPolicyRouteImport
+ '/_authenticated': {
+ id: '/_authenticated'
+ path: ''
+ fullPath: '/'
+ preLoaderRoute: typeof AuthenticatedRouteImport
parentRoute: typeof rootRouteImport
}
- '/feed': {
- id: '/feed'
- path: '/feed'
- fullPath: '/feed'
- preLoaderRoute: typeof FeedRouteImport
+ '/add-data': {
+ id: '/add-data'
+ path: '/add-data'
+ fullPath: '/add-data'
+ preLoaderRoute: typeof AddDataRouteImport
parentRoute: typeof rootRouteImport
}
- '/donate': {
- id: '/donate'
- path: '/donate'
- fullPath: '/donate'
- preLoaderRoute: typeof DonateRouteImport
+ '/compare': {
+ id: '/compare'
+ path: '/compare'
+ fullPath: '/compare'
+ preLoaderRoute: typeof CompareRouteImport
parentRoute: typeof rootRouteImport
}
'/credits': {
@@ -817,68 +803,68 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof CreditsRouteImport
parentRoute: typeof rootRouteImport
}
- '/compare': {
- id: '/compare'
- path: '/compare'
- fullPath: '/compare'
- preLoaderRoute: typeof CompareRouteImport
+ '/donate': {
+ id: '/donate'
+ path: '/donate'
+ fullPath: '/donate'
+ preLoaderRoute: typeof DonateRouteImport
parentRoute: typeof rootRouteImport
}
- '/add-data': {
- id: '/add-data'
- path: '/add-data'
- fullPath: '/add-data'
- preLoaderRoute: typeof AddDataRouteImport
+ '/feed': {
+ id: '/feed'
+ path: '/feed'
+ fullPath: '/feed'
+ preLoaderRoute: typeof FeedRouteImport
parentRoute: typeof rootRouteImport
}
- '/_authenticated': {
- id: '/_authenticated'
- path: ''
- fullPath: '/'
- preLoaderRoute: typeof AuthenticatedRouteImport
+ '/privacy-policy': {
+ id: '/privacy-policy'
+ path: '/privacy-policy'
+ fullPath: '/privacy-policy'
+ preLoaderRoute: typeof PrivacyPolicyRouteImport
parentRoute: typeof rootRouteImport
}
- '/': {
- id: '/'
- path: '/'
- fullPath: '/'
- preLoaderRoute: typeof IndexRouteImport
+ '/reset-password': {
+ id: '/reset-password'
+ path: '/reset-password'
+ fullPath: '/reset-password'
+ preLoaderRoute: typeof ResetPasswordRouteImport
parentRoute: typeof rootRouteImport
}
- '/tv/': {
- id: '/tv/'
- path: '/tv'
- fullPath: '/tv/'
- preLoaderRoute: typeof TvIndexRouteImport
+ '/search': {
+ id: '/search'
+ path: '/search'
+ fullPath: '/search'
+ preLoaderRoute: typeof SearchRouteImport
parentRoute: typeof rootRouteImport
}
- '/movie/': {
- id: '/movie/'
- path: '/movie'
- fullPath: '/movie/'
- preLoaderRoute: typeof MovieIndexRouteImport
+ '/tos': {
+ id: '/tos'
+ path: '/tos'
+ fullPath: '/tos'
+ preLoaderRoute: typeof TosRouteImport
parentRoute: typeof rootRouteImport
}
- '/manga/': {
- id: '/manga/'
- path: '/manga'
- fullPath: '/manga/'
- preLoaderRoute: typeof MangaIndexRouteImport
- parentRoute: typeof rootRouteImport
+ '/_authenticated/billing': {
+ id: '/_authenticated/billing'
+ path: '/billing'
+ fullPath: '/billing'
+ preLoaderRoute: typeof AuthenticatedBillingRouteImport
+ parentRoute: typeof AuthenticatedRoute
}
- '/game/': {
- id: '/game/'
- path: '/game'
- fullPath: '/game/'
- preLoaderRoute: typeof GameIndexRouteImport
- parentRoute: typeof rootRouteImport
+ '/_authenticated/notifications': {
+ id: '/_authenticated/notifications'
+ path: '/notifications'
+ fullPath: '/notifications'
+ preLoaderRoute: typeof AuthenticatedNotificationsRouteImport
+ parentRoute: typeof AuthenticatedRoute
}
- '/book/': {
- id: '/book/'
- path: '/book'
- fullPath: '/book/'
- preLoaderRoute: typeof BookIndexRouteImport
- parentRoute: typeof rootRouteImport
+ '/_authenticated/settings': {
+ id: '/_authenticated/settings'
+ path: '/settings'
+ fullPath: '/settings'
+ preLoaderRoute: typeof AuthenticatedSettingsRouteImport
+ parentRoute: typeof AuthenticatedRoute
}
'/anime/': {
id: '/anime/'
@@ -887,46 +873,32 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AnimeIndexRouteImport
parentRoute: typeof rootRouteImport
}
- '/tv/$slug': {
- id: '/tv/$slug'
- path: '/tv/$slug'
- fullPath: '/tv/$slug'
- preLoaderRoute: typeof TvSlugRouteImport
- parentRoute: typeof rootRouteImport
- }
- '/review/$id': {
- id: '/review/$id'
- path: '/review/$id'
- fullPath: '/review/$id'
- preLoaderRoute: typeof ReviewIdRouteImport
- parentRoute: typeof rootRouteImport
- }
- '/post/$id': {
- id: '/post/$id'
- path: '/post/$id'
- fullPath: '/post/$id'
- preLoaderRoute: typeof PostIdRouteImport
+ '/anime/$slug': {
+ id: '/anime/$slug'
+ path: '/anime/$slug'
+ fullPath: '/anime/$slug'
+ preLoaderRoute: typeof AnimeSlugRouteImport
parentRoute: typeof rootRouteImport
}
- '/movie/$slug': {
- id: '/movie/$slug'
- path: '/movie/$slug'
- fullPath: '/movie/$slug'
- preLoaderRoute: typeof MovieSlugRouteImport
+ '/book/': {
+ id: '/book/'
+ path: '/book'
+ fullPath: '/book/'
+ preLoaderRoute: typeof BookIndexRouteImport
parentRoute: typeof rootRouteImport
}
- '/manga/$slug': {
- id: '/manga/$slug'
- path: '/manga/$slug'
- fullPath: '/manga/$slug'
- preLoaderRoute: typeof MangaSlugRouteImport
+ '/book/$slug': {
+ id: '/book/$slug'
+ path: '/book/$slug'
+ fullPath: '/book/$slug'
+ preLoaderRoute: typeof BookSlugRouteImport
parentRoute: typeof rootRouteImport
}
- '/list/$slug': {
- id: '/list/$slug'
- path: '/list/$slug'
- fullPath: '/list/$slug'
- preLoaderRoute: typeof ListSlugRouteImport
+ '/game/': {
+ id: '/game/'
+ path: '/game'
+ fullPath: '/game/'
+ preLoaderRoute: typeof GameIndexRouteImport
parentRoute: typeof rootRouteImport
}
'/game/$slug': {
@@ -936,144 +908,130 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof GameSlugRouteImport
parentRoute: typeof rootRouteImport
}
- '/book/$slug': {
- id: '/book/$slug'
- path: '/book/$slug'
- fullPath: '/book/$slug'
- preLoaderRoute: typeof BookSlugRouteImport
+ '/list/$slug': {
+ id: '/list/$slug'
+ path: '/list/$slug'
+ fullPath: '/list/$slug'
+ preLoaderRoute: typeof ListSlugRouteImport
parentRoute: typeof rootRouteImport
}
- '/anime/$slug': {
- id: '/anime/$slug'
- path: '/anime/$slug'
- fullPath: '/anime/$slug'
- preLoaderRoute: typeof AnimeSlugRouteImport
+ '/manga/': {
+ id: '/manga/'
+ path: '/manga'
+ fullPath: '/manga/'
+ preLoaderRoute: typeof MangaIndexRouteImport
parentRoute: typeof rootRouteImport
}
- '/_authenticated/settings': {
- id: '/_authenticated/settings'
- path: '/settings'
- fullPath: '/settings'
- preLoaderRoute: typeof AuthenticatedSettingsRouteImport
- parentRoute: typeof AuthenticatedRoute
- }
- '/_authenticated/notifications': {
- id: '/_authenticated/notifications'
- path: '/notifications'
- fullPath: '/notifications'
- preLoaderRoute: typeof AuthenticatedNotificationsRouteImport
- parentRoute: typeof AuthenticatedRoute
- }
- '/_authenticated/billing': {
- id: '/_authenticated/billing'
- path: '/billing'
- fullPath: '/billing'
- preLoaderRoute: typeof AuthenticatedBillingRouteImport
- parentRoute: typeof AuthenticatedRoute
- }
- '/user/$username/': {
- id: '/user/$username/'
- path: '/user/$username'
- fullPath: '/user/$username/'
- preLoaderRoute: typeof UserUsernameIndexRouteImport
+ '/manga/$slug': {
+ id: '/manga/$slug'
+ path: '/manga/$slug'
+ fullPath: '/manga/$slug'
+ preLoaderRoute: typeof MangaSlugRouteImport
parentRoute: typeof rootRouteImport
}
- '/tv/upcoming/': {
- id: '/tv/upcoming/'
- path: '/tv/upcoming'
- fullPath: '/tv/upcoming/'
- preLoaderRoute: typeof TvUpcomingIndexRouteImport
+ '/movie/': {
+ id: '/movie/'
+ path: '/movie'
+ fullPath: '/movie/'
+ preLoaderRoute: typeof MovieIndexRouteImport
parentRoute: typeof rootRouteImport
}
- '/tv/trending/': {
- id: '/tv/trending/'
- path: '/tv/trending'
- fullPath: '/tv/trending/'
- preLoaderRoute: typeof TvTrendingIndexRouteImport
+ '/movie/$slug': {
+ id: '/movie/$slug'
+ path: '/movie/$slug'
+ fullPath: '/movie/$slug'
+ preLoaderRoute: typeof MovieSlugRouteImport
parentRoute: typeof rootRouteImport
}
- '/tv/popular/': {
- id: '/tv/popular/'
- path: '/tv/popular'
- fullPath: '/tv/popular/'
- preLoaderRoute: typeof TvPopularIndexRouteImport
+ '/post/$id': {
+ id: '/post/$id'
+ path: '/post/$id'
+ fullPath: '/post/$id'
+ preLoaderRoute: typeof PostIdRouteImport
parentRoute: typeof rootRouteImport
}
- '/tv/airing/': {
- id: '/tv/airing/'
- path: '/tv/airing'
- fullPath: '/tv/airing/'
- preLoaderRoute: typeof TvAiringIndexRouteImport
+ '/review/$id': {
+ id: '/review/$id'
+ path: '/review/$id'
+ fullPath: '/review/$id'
+ preLoaderRoute: typeof ReviewIdRouteImport
parentRoute: typeof rootRouteImport
}
- '/movie/upcoming/': {
- id: '/movie/upcoming/'
- path: '/movie/upcoming'
- fullPath: '/movie/upcoming/'
- preLoaderRoute: typeof MovieUpcomingIndexRouteImport
+ '/tv/': {
+ id: '/tv/'
+ path: '/tv'
+ fullPath: '/tv/'
+ preLoaderRoute: typeof TvIndexRouteImport
parentRoute: typeof rootRouteImport
}
- '/movie/trending/': {
- id: '/movie/trending/'
- path: '/movie/trending'
- fullPath: '/movie/trending/'
- preLoaderRoute: typeof MovieTrendingIndexRouteImport
+ '/tv/$slug': {
+ id: '/tv/$slug'
+ path: '/tv/$slug'
+ fullPath: '/tv/$slug'
+ preLoaderRoute: typeof TvSlugRouteImport
parentRoute: typeof rootRouteImport
}
- '/movie/popular/': {
- id: '/movie/popular/'
- path: '/movie/popular'
- fullPath: '/movie/popular/'
- preLoaderRoute: typeof MoviePopularIndexRouteImport
+ '/anime/airing/': {
+ id: '/anime/airing/'
+ path: '/anime/airing'
+ fullPath: '/anime/airing/'
+ preLoaderRoute: typeof AnimeAiringIndexRouteImport
parentRoute: typeof rootRouteImport
}
- '/movie/airing/': {
- id: '/movie/airing/'
- path: '/movie/airing'
- fullPath: '/movie/airing/'
- preLoaderRoute: typeof MovieAiringIndexRouteImport
+ '/anime/recommendations/': {
+ id: '/anime/recommendations/'
+ path: '/anime/recommendations'
+ fullPath: '/anime/recommendations/'
+ preLoaderRoute: typeof AnimeRecommendationsIndexRouteImport
parentRoute: typeof rootRouteImport
}
- '/manga/upcoming/': {
- id: '/manga/upcoming/'
- path: '/manga/upcoming'
- fullPath: '/manga/upcoming/'
- preLoaderRoute: typeof MangaUpcomingIndexRouteImport
+ '/anime/top/': {
+ id: '/anime/top/'
+ path: '/anime/top'
+ fullPath: '/anime/top/'
+ preLoaderRoute: typeof AnimeTopIndexRouteImport
parentRoute: typeof rootRouteImport
}
- '/manga/top/': {
- id: '/manga/top/'
- path: '/manga/top'
- fullPath: '/manga/top/'
- preLoaderRoute: typeof MangaTopIndexRouteImport
+ '/anime/upcoming/': {
+ id: '/anime/upcoming/'
+ path: '/anime/upcoming'
+ fullPath: '/anime/upcoming/'
+ preLoaderRoute: typeof AnimeUpcomingIndexRouteImport
parentRoute: typeof rootRouteImport
}
- '/manga/recommendations/': {
- id: '/manga/recommendations/'
- path: '/manga/recommendations'
- fullPath: '/manga/recommendations/'
- preLoaderRoute: typeof MangaRecommendationsIndexRouteImport
+ '/book/franchises/$slug': {
+ id: '/book/franchises/$slug'
+ path: '/book/franchises/$slug'
+ fullPath: '/book/franchises/$slug'
+ preLoaderRoute: typeof BookFranchisesSlugRouteImport
parentRoute: typeof rootRouteImport
}
- '/manga/publishing/': {
- id: '/manga/publishing/'
- path: '/manga/publishing'
- fullPath: '/manga/publishing/'
- preLoaderRoute: typeof MangaPublishingIndexRouteImport
+ '/book/trending/': {
+ id: '/book/trending/'
+ path: '/book/trending'
+ fullPath: '/book/trending/'
+ preLoaderRoute: typeof BookTrendingIndexRouteImport
parentRoute: typeof rootRouteImport
}
- '/game/upcoming/': {
- id: '/game/upcoming/'
- path: '/game/upcoming'
- fullPath: '/game/upcoming/'
- preLoaderRoute: typeof GameUpcomingIndexRouteImport
+ '/book/upcoming/': {
+ id: '/book/upcoming/'
+ path: '/book/upcoming'
+ fullPath: '/book/upcoming/'
+ preLoaderRoute: typeof BookUpcomingIndexRouteImport
parentRoute: typeof rootRouteImport
}
- '/game/recent/': {
- id: '/game/recent/'
- path: '/game/recent'
- fullPath: '/game/recent/'
- preLoaderRoute: typeof GameRecentIndexRouteImport
+ '/game/anticipated/': {
+ id: '/game/anticipated/'
+ path: '/game/anticipated'
+ fullPath: '/game/anticipated/'
+ preLoaderRoute: typeof GameAnticipatedIndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/game/franchises/$slug': {
+ id: '/game/franchises/$slug'
+ path: '/game/franchises/$slug'
+ fullPath: '/game/franchises/$slug'
+ preLoaderRoute: typeof GameFranchisesSlugRouteImport
parentRoute: typeof rootRouteImport
}
'/game/popular/': {
@@ -1083,53 +1041,53 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof GamePopularIndexRouteImport
parentRoute: typeof rootRouteImport
}
- '/game/anticipated/': {
- id: '/game/anticipated/'
- path: '/game/anticipated'
- fullPath: '/game/anticipated/'
- preLoaderRoute: typeof GameAnticipatedIndexRouteImport
+ '/game/recent/': {
+ id: '/game/recent/'
+ path: '/game/recent'
+ fullPath: '/game/recent/'
+ preLoaderRoute: typeof GameRecentIndexRouteImport
parentRoute: typeof rootRouteImport
}
- '/book/upcoming/': {
- id: '/book/upcoming/'
- path: '/book/upcoming'
- fullPath: '/book/upcoming/'
- preLoaderRoute: typeof BookUpcomingIndexRouteImport
+ '/game/upcoming/': {
+ id: '/game/upcoming/'
+ path: '/game/upcoming'
+ fullPath: '/game/upcoming/'
+ preLoaderRoute: typeof GameUpcomingIndexRouteImport
parentRoute: typeof rootRouteImport
}
- '/book/trending/': {
- id: '/book/trending/'
- path: '/book/trending'
- fullPath: '/book/trending/'
- preLoaderRoute: typeof BookTrendingIndexRouteImport
+ '/manga/publishing/': {
+ id: '/manga/publishing/'
+ path: '/manga/publishing'
+ fullPath: '/manga/publishing/'
+ preLoaderRoute: typeof MangaPublishingIndexRouteImport
parentRoute: typeof rootRouteImport
}
- '/anime/upcoming/': {
- id: '/anime/upcoming/'
- path: '/anime/upcoming'
- fullPath: '/anime/upcoming/'
- preLoaderRoute: typeof AnimeUpcomingIndexRouteImport
+ '/manga/recommendations/': {
+ id: '/manga/recommendations/'
+ path: '/manga/recommendations'
+ fullPath: '/manga/recommendations/'
+ preLoaderRoute: typeof MangaRecommendationsIndexRouteImport
parentRoute: typeof rootRouteImport
}
- '/anime/top/': {
- id: '/anime/top/'
- path: '/anime/top'
- fullPath: '/anime/top/'
- preLoaderRoute: typeof AnimeTopIndexRouteImport
+ '/manga/top/': {
+ id: '/manga/top/'
+ path: '/manga/top'
+ fullPath: '/manga/top/'
+ preLoaderRoute: typeof MangaTopIndexRouteImport
parentRoute: typeof rootRouteImport
}
- '/anime/recommendations/': {
- id: '/anime/recommendations/'
- path: '/anime/recommendations'
- fullPath: '/anime/recommendations/'
- preLoaderRoute: typeof AnimeRecommendationsIndexRouteImport
+ '/manga/upcoming/': {
+ id: '/manga/upcoming/'
+ path: '/manga/upcoming'
+ fullPath: '/manga/upcoming/'
+ preLoaderRoute: typeof MangaUpcomingIndexRouteImport
parentRoute: typeof rootRouteImport
}
- '/anime/airing/': {
- id: '/anime/airing/'
- path: '/anime/airing'
- fullPath: '/anime/airing/'
- preLoaderRoute: typeof AnimeAiringIndexRouteImport
+ '/movie/airing/': {
+ id: '/movie/airing/'
+ path: '/movie/airing'
+ fullPath: '/movie/airing/'
+ preLoaderRoute: typeof MovieAiringIndexRouteImport
parentRoute: typeof rootRouteImport
}
'/movie/franchises/$slug': {
@@ -1139,26 +1097,61 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof MovieFranchisesSlugRouteImport
parentRoute: typeof rootRouteImport
}
- '/game/franchises/$slug': {
- id: '/game/franchises/$slug'
- path: '/game/franchises/$slug'
- fullPath: '/game/franchises/$slug'
- preLoaderRoute: typeof GameFranchisesSlugRouteImport
+ '/movie/popular/': {
+ id: '/movie/popular/'
+ path: '/movie/popular'
+ fullPath: '/movie/popular/'
+ preLoaderRoute: typeof MoviePopularIndexRouteImport
parentRoute: typeof rootRouteImport
}
- '/book/franchises/$slug': {
- id: '/book/franchises/$slug'
- path: '/book/franchises/$slug'
- fullPath: '/book/franchises/$slug'
- preLoaderRoute: typeof BookFranchisesSlugRouteImport
+ '/movie/trending/': {
+ id: '/movie/trending/'
+ path: '/movie/trending'
+ fullPath: '/movie/trending/'
+ preLoaderRoute: typeof MovieTrendingIndexRouteImport
parentRoute: typeof rootRouteImport
}
- '/_authenticated/donate/success/': {
- id: '/_authenticated/donate/success/'
- path: '/donate/success'
- fullPath: '/donate/success/'
- preLoaderRoute: typeof AuthenticatedDonateSuccessIndexRouteImport
- parentRoute: typeof AuthenticatedRoute
+ '/movie/upcoming/': {
+ id: '/movie/upcoming/'
+ path: '/movie/upcoming'
+ fullPath: '/movie/upcoming/'
+ preLoaderRoute: typeof MovieUpcomingIndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/tv/airing/': {
+ id: '/tv/airing/'
+ path: '/tv/airing'
+ fullPath: '/tv/airing/'
+ preLoaderRoute: typeof TvAiringIndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/tv/popular/': {
+ id: '/tv/popular/'
+ path: '/tv/popular'
+ fullPath: '/tv/popular/'
+ preLoaderRoute: typeof TvPopularIndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/tv/trending/': {
+ id: '/tv/trending/'
+ path: '/tv/trending'
+ fullPath: '/tv/trending/'
+ preLoaderRoute: typeof TvTrendingIndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/tv/upcoming/': {
+ id: '/tv/upcoming/'
+ path: '/tv/upcoming'
+ fullPath: '/tv/upcoming/'
+ preLoaderRoute: typeof TvUpcomingIndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/user/$username/': {
+ id: '/user/$username/'
+ path: '/user/$username'
+ fullPath: '/user/$username/'
+ preLoaderRoute: typeof UserUsernameIndexRouteImport
+ parentRoute: typeof rootRouteImport
}
'/_authenticated/donate/error/': {
id: '/_authenticated/donate/error/'
@@ -1167,6 +1160,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthenticatedDonateErrorIndexRouteImport
parentRoute: typeof AuthenticatedRoute
}
+ '/_authenticated/donate/success/': {
+ id: '/_authenticated/donate/success/'
+ path: '/donate/success'
+ fullPath: '/donate/success/'
+ preLoaderRoute: typeof AuthenticatedDonateSuccessIndexRouteImport
+ parentRoute: typeof AuthenticatedRoute
+ }
}
}
@@ -1247,3 +1247,13 @@ const rootRouteChildren: RootRouteChildren = {
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { startInstance } from './start.ts'
+declare module '@tanstack/react-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ config: Awaited>
+ }
+}
diff --git a/src/router.tsx b/src/router.tsx
new file mode 100644
index 0000000..c449297
--- /dev/null
+++ b/src/router.tsx
@@ -0,0 +1,24 @@
+import { createRouter } from "@tanstack/react-router";
+import { NotFoundComponent } from "@/components/shared/404.tsx";
+import { authClient } from "@/lib/auth";
+import { routeTree } from "./routeTree.gen";
+
+export function getRouter() {
+ return createRouter({
+ routeTree,
+ defaultPreload: "intent",
+ scrollRestoration: true,
+ context: {
+ auth: authClient,
+ },
+ defaultNotFoundComponent: () => {
+ return ;
+ },
+ });
+}
+
+declare module "@tanstack/react-router" {
+ interface Register {
+ router: ReturnType;
+ }
+}
diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx
index 94ca382..66034f7 100644
--- a/src/routes/__root.tsx
+++ b/src/routes/__root.tsx
@@ -1,9 +1,9 @@
import "@/lib/i18n/config";
-import "@/global.css";
-import { createRootRouteWithContext, HeadContent, Outlet, useRouterState } from "@tanstack/react-router";
-import { useEffect } from "react";
+import { createRootRouteWithContext, HeadContent, Outlet, Scripts, useRouterState } from "@tanstack/react-router";
+import { type ReactNode, useEffect } from "react";
import { Layout } from "@/components/layouts";
+import appCss from "@/global.css?url";
import type { authClient } from "@/lib/auth";
import { RootProvider } from "@/providers";
@@ -18,6 +18,31 @@ declare module "@tanstack/react-router" {
}
export const Route = createRootRouteWithContext()({
+ ssr: "data-only",
+ head: () => ({
+ meta: [
+ { charSet: "UTF-8" },
+ { name: "viewport", content: "width=device-width, initial-scale=1.0" },
+ { name: "theme-color", content: "#10b981" },
+ ],
+ links: [
+ { rel: "icon", type: "image/svg+xml", href: "/favicon.svg" },
+ { rel: "preconnect", href: "https://fonts.googleapis.com" },
+ { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "" },
+ {
+ rel: "stylesheet",
+ href: "https://fonts.googleapis.com/css2?family=Archivo:ital,wght@0,100..900;1,100..900&family=Noto+Color+Emoji&display=swap",
+ },
+ { rel: "stylesheet", href: appCss },
+ ],
+ scripts: [
+ {
+ src: "https://cdn.jsdelivr.net/npm/sienna-accessibility@latest/dist/sienna-accessibility.umd.js",
+ defer: true,
+ },
+ ],
+ }),
+ shellComponent: RootDocument,
component: RootLayout,
});
@@ -40,14 +65,24 @@ function RootLayout() {
}, []);
return (
- <>
-
-
-
-
-
-
-
- >
+
+
+
+
+
+ );
+}
+
+function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
+ return (
+
+
+
+
+
+ {children}
+
+
+
);
}
diff --git a/src/routes/add-data.tsx b/src/routes/add-data.tsx
index d6201c9..775fe72 100644
--- a/src/routes/add-data.tsx
+++ b/src/routes/add-data.tsx
@@ -7,6 +7,7 @@ import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/
import { seo } from "@/lib/utils/seo";
export const Route = createFileRoute("/add-data")({
+ ssr: "data-only",
head: () => ({
meta: [...seo({ title: "Add Data" })],
}),
diff --git a/src/routes/anime/$slug.tsx b/src/routes/anime/$slug.tsx
index 5c09089..0aa93dc 100644
--- a/src/routes/anime/$slug.tsx
+++ b/src/routes/anime/$slug.tsx
@@ -35,6 +35,7 @@ import { getGenreLabel } from "@/lib/utils/genre-utils";
import { seo } from "@/lib/utils/seo";
export const Route = createFileRoute("/anime/$slug")({
+ ssr: "data-only",
loader: async ({ params }) => {
const anime = await api.get(apiEndpoints.getAnimeDetails(params.slug)).then(({ data }) => data.anime);
return { anime };
diff --git a/src/routes/anime/airing/index.tsx b/src/routes/anime/airing/index.tsx
index 8d0fe67..07adb47 100644
--- a/src/routes/anime/airing/index.tsx
+++ b/src/routes/anime/airing/index.tsx
@@ -10,6 +10,7 @@ import { seo } from "@/lib/utils/seo.ts";
import { useInfiniteScroll } from "@/lib/utils/useInfiniteScroll.ts";
export const Route = createFileRoute("/anime/airing/")({
+ ssr: "data-only",
component: AiringAnimeRoute,
head: () => ({
meta: [
diff --git a/src/routes/anime/index.tsx b/src/routes/anime/index.tsx
index 121364b..883e322 100644
--- a/src/routes/anime/index.tsx
+++ b/src/routes/anime/index.tsx
@@ -12,6 +12,7 @@ import { api, apiEndpoints } from "@/lib/api";
import { seo } from "@/lib/utils/seo.ts";
export const Route = createFileRoute("/anime/")({
+ ssr: "data-only",
component: AnimeRoute,
head: () => ({
meta: [
diff --git a/src/routes/anime/recommendations/index.tsx b/src/routes/anime/recommendations/index.tsx
index 5d6932e..6191075 100644
--- a/src/routes/anime/recommendations/index.tsx
+++ b/src/routes/anime/recommendations/index.tsx
@@ -10,6 +10,7 @@ import { seo } from "@/lib/utils/seo.ts";
import { useInfiniteScroll } from "@/lib/utils/useInfiniteScroll.ts";
export const Route = createFileRoute("/anime/recommendations/")({
+ ssr: "data-only",
component: RecommendationsAnimeRoute,
head: () => ({
meta: [
diff --git a/src/routes/anime/top/index.tsx b/src/routes/anime/top/index.tsx
index ad8c16a..8baa02f 100644
--- a/src/routes/anime/top/index.tsx
+++ b/src/routes/anime/top/index.tsx
@@ -10,6 +10,7 @@ import { seo } from "@/lib/utils/seo.ts";
import { useInfiniteScroll } from "@/lib/utils/useInfiniteScroll.ts";
export const Route = createFileRoute("/anime/top/")({
+ ssr: "data-only",
component: TopAnimeRoute,
head: () => ({
meta: [
diff --git a/src/routes/anime/upcoming/index.tsx b/src/routes/anime/upcoming/index.tsx
index b654b6c..00d0e97 100644
--- a/src/routes/anime/upcoming/index.tsx
+++ b/src/routes/anime/upcoming/index.tsx
@@ -10,6 +10,7 @@ import { seo } from "@/lib/utils/seo.ts";
import { useInfiniteScroll } from "@/lib/utils/useInfiniteScroll.ts";
export const Route = createFileRoute("/anime/upcoming/")({
+ ssr: "data-only",
component: UpcomingAnimeRoute,
head: () => ({
meta: [
diff --git a/src/routes/book/$slug.tsx b/src/routes/book/$slug.tsx
index deb2b7b..d110187 100644
--- a/src/routes/book/$slug.tsx
+++ b/src/routes/book/$slug.tsx
@@ -28,6 +28,7 @@ import { useSession } from "@/lib/auth.ts";
import { seo } from "@/lib/utils/seo";
export const Route = createFileRoute("/book/$slug")({
+ ssr: "data-only",
loader: async ({ params }) => {
const book = await api.get(apiEndpoints.getBookDetails(params.slug)).then(({ data }) => data.book);
return { book };
diff --git a/src/routes/book/index.tsx b/src/routes/book/index.tsx
index 6308b17..e7a8f09 100644
--- a/src/routes/book/index.tsx
+++ b/src/routes/book/index.tsx
@@ -12,6 +12,7 @@ import { api, apiEndpoints } from "@/lib/api.ts";
import { seo } from "@/lib/utils/seo.ts";
export const Route = createFileRoute("/book/")({
+ ssr: "data-only",
component: BookRoute,
head: () => ({
meta: [
diff --git a/src/routes/book/trending/index.tsx b/src/routes/book/trending/index.tsx
index 1e29444..a1d13c6 100644
--- a/src/routes/book/trending/index.tsx
+++ b/src/routes/book/trending/index.tsx
@@ -10,6 +10,7 @@ import { seo } from "@/lib/utils/seo.ts";
import { useInfiniteScroll } from "@/lib/utils/useInfiniteScroll.ts";
export const Route = createFileRoute("/book/trending/")({
+ ssr: "data-only",
component: TrendingBookRoute,
head: () => ({
meta: [
diff --git a/src/routes/book/upcoming/index.tsx b/src/routes/book/upcoming/index.tsx
index ea6323a..eecacf1 100644
--- a/src/routes/book/upcoming/index.tsx
+++ b/src/routes/book/upcoming/index.tsx
@@ -10,6 +10,7 @@ import { seo } from "@/lib/utils/seo.ts";
import { useInfiniteScroll } from "@/lib/utils/useInfiniteScroll.ts";
export const Route = createFileRoute("/book/upcoming/")({
+ ssr: "data-only",
component: UpcomingBookRoute,
head: () => ({
meta: [
diff --git a/src/routes/compare.tsx b/src/routes/compare.tsx
index b622626..819b172 100644
--- a/src/routes/compare.tsx
+++ b/src/routes/compare.tsx
@@ -8,6 +8,7 @@ import { comparisonCriteria, comparisonEntries, comparisonPlatforms } from "@/li
import { seo } from "@/lib/utils/seo";
export const Route = createFileRoute("/compare")({
+ ssr: "data-only",
head: () => ({
meta: [
...seo({
diff --git a/src/routes/credits.tsx b/src/routes/credits.tsx
index 956feb3..95a5cd8 100644
--- a/src/routes/credits.tsx
+++ b/src/routes/credits.tsx
@@ -12,6 +12,7 @@ import { cn } from "@/lib/utils";
import { seo } from "@/lib/utils/seo";
export const Route = createFileRoute("/credits")({
+ ssr: "data-only",
head: () => ({
meta: [...seo({ title: "Credits" })],
}),
diff --git a/src/routes/donate.tsx b/src/routes/donate.tsx
index cfabdc7..db2823c 100644
--- a/src/routes/donate.tsx
+++ b/src/routes/donate.tsx
@@ -25,6 +25,7 @@ import { useSession } from "@/lib/auth";
import { seo } from "@/lib/utils/seo";
export const Route = createFileRoute("/donate")({
+ ssr: "data-only",
head: () => ({
meta: [...seo({ title: "Donate" })],
}),
diff --git a/src/routes/feed.tsx b/src/routes/feed.tsx
index 1d36484..206c955 100644
--- a/src/routes/feed.tsx
+++ b/src/routes/feed.tsx
@@ -8,6 +8,7 @@ import { useSession } from "@/lib/auth.ts";
import { seo } from "@/lib/utils/seo";
export const Route = createFileRoute("/feed")({
+ ssr: "data-only",
head: () => ({
meta: [...seo({ title: "Feed" })],
}),
diff --git a/src/routes/game/$slug.tsx b/src/routes/game/$slug.tsx
index 19d8642..e273a43 100644
--- a/src/routes/game/$slug.tsx
+++ b/src/routes/game/$slug.tsx
@@ -21,6 +21,7 @@ import { ErrorComponent } from "@/components/shared/error.tsx";
import { LoadingDetails } from "@/components/shared/loadings/details.tsx";
import { GameModal } from "@/components/shared/modals/game";
import { RefreshData } from "@/components/shared/modals/refresh-data";
+import { StarRating } from "@/components/shared/star-rating.tsx";
import { Button } from "@/components/ui/button";
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel";
import { Checkbox } from "@/components/ui/checkbox";
@@ -29,6 +30,7 @@ import { ImageZoom } from "@/components/ui/image-zoom";
import { Input } from "@/components/ui/input";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { useToggleReviewReaction } from "@/hooks/review";
import { type ApiTypes, api, apiEndpoints } from "@/lib/api.ts";
import { useSession } from "@/lib/auth.ts";
import { getGenreLabel } from "@/lib/utils/genre-utils.ts";
@@ -55,6 +57,7 @@ const websiteIconMap: Record = {
};
export const Route = createFileRoute("/game/$slug")({
+ ssr: "data-only",
loader: async ({ params }) => {
const game = await api.get(apiEndpoints.getGameDetails(params.slug)).then(({ data }) => data.game);
return { game };
@@ -82,6 +85,15 @@ const LAYOUT = {
CENTER: { x: 0, y: 0 },
};
+type ProgressStatus = "Planning" | "Playing" | "Completed";
+
+interface GameProgress {
+ id: string;
+ status: string;
+ gameId: string;
+ userId: string;
+}
+
type RelatedGame = { id: string; name: string; coverUrl: string };
type GameRelations = {
@@ -178,7 +190,6 @@ function GameDetailsRoute() {
const { slug } = Route.useParams();
const { game: loaderGame } = Route.useLoaderData();
- const rating = 4.2;
const { t } = useTranslation();
const [moreOpen, setMoreOpen] = useState(false);
@@ -189,6 +200,8 @@ function GameDetailsRoute() {
});
const game = data;
+ const rating = game?.tgReviewScore ?? 0;
+
const [reviewsQuery, screenshotsQuery] = useQueries({
queries: [
{
@@ -295,6 +308,69 @@ function GameDetailsRoute() {
onError: () => toast.error(t("api:INTERNAL_SERVER_ERROR")),
});
+ const toggleReaction = useToggleReviewReaction("game", userId ?? "");
+
+ const progressQuery = useQuery({
+ queryKey: ["gameProgress", game?.id, userId],
+ queryFn: () =>
+ api
+ .get(apiEndpoints.getGameProgress(userId as string, game?.id as string))
+ .then(({ data }) => data.gameProgresses.items[0] ?? null),
+ enabled: isAuthenticated && !!userId && !!game?.id,
+ });
+
+ const currentStatus = progressQuery.data?.status;
+
+ const setProgressMutation = useMutation({
+ mutationFn: (status: ProgressStatus) => {
+ const current = progressQuery.data;
+
+ if (current && current.status === status) {
+ return api.delete(`${apiEndpoints.gameProgress}/${current.id}`);
+ }
+
+ return api.post(apiEndpoints.gameProgress, { gameId: game?.id, status });
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["gameProgress", game?.id, userId] });
+ queryClient.invalidateQueries({ queryKey: ["game", slug] });
+ },
+ onError: () => {
+ return toast.error(t("api:INTERNAL_SERVER_ERROR"));
+ },
+ });
+
+ const favoriteQuery = useQuery({
+ queryKey: ["gameFavorite", game?.id, userId],
+ queryFn: () =>
+ api
+ .get(apiEndpoints.getFavoriteStatus, {
+ params: { type: "Game", gameId: game?.id },
+ })
+ .then(({ data }) => data.favorited),
+ enabled: isAuthenticated && !!userId && !!game?.id,
+ });
+
+ const isFavorited = !!favoriteQuery.data;
+
+ const toggleFavoriteMutation = useMutation({
+ mutationFn: () => {
+ const body = { type: "Game", gameId: game?.id };
+
+ return isFavorited
+ ? api.delete(apiEndpoints.removeFavorite, { data: body })
+ : api.post(apiEndpoints.addFavorite, body);
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["gameFavorite", game?.id, userId] });
+ queryClient.invalidateQueries({ queryKey: ["favorites"] });
+ },
+ onError: () => {
+ queryClient.invalidateQueries({ queryKey: ["gameFavorite", game?.id, userId] });
+ return toast.error(t("api:INTERNAL_SERVER_ERROR"));
+ },
+ });
+
if (isLoading || reviewsQuery.isLoading) return ;
if (isError || reviewsQuery.isError || !game) return ;
@@ -322,6 +398,10 @@ function GameDetailsRoute() {
iconBg: "bg-linear-to-r from-purple-500/20 to-violet-500/20",
iconBorder: "border-purple-500/30",
iconColor: "text-purple-400",
+ activeClass: "border-purple-400 bg-purple-400/20",
+ isActive: currentStatus === "Planning",
+ disabled: setProgressMutation.isPending,
+ onClick: () => setProgressMutation.mutate("Planning"),
},
{
label: t("feed:lists.playing"),
@@ -331,6 +411,10 @@ function GameDetailsRoute() {
iconBg: "bg-linear-to-r from-primary/20 to-secondary/20",
iconBorder: "border-primary/30",
iconColor: "text-primary",
+ activeClass: "border-primary bg-primary/20",
+ isActive: currentStatus === "Playing",
+ disabled: setProgressMutation.isPending,
+ onClick: () => setProgressMutation.mutate("Playing"),
},
{
label: t("feed:lists.played"),
@@ -340,6 +424,10 @@ function GameDetailsRoute() {
iconBg: "bg-linear-to-r from-chart-3/20 to-amber-500/20",
iconBorder: "border-chart-3/30",
iconColor: "text-chart-3",
+ activeClass: "border-chart-3 bg-chart-3/20",
+ isActive: currentStatus === "Completed",
+ disabled: setProgressMutation.isPending,
+ onClick: () => setProgressMutation.mutate("Completed"),
},
]}
/>
@@ -352,8 +440,11 @@ function GameDetailsRoute() {
triggerLabel={t("library:moreOptions")}
open={moreOpen}
onOpenChange={setMoreOpen}
+ isFavorited={isFavorited}
+ onToggleFavorite={() => toggleFavoriteMutation.mutate()}
+ favoriteDisabled={toggleFavoriteMutation.isPending || favoriteQuery.isFetching}
>
-
+ setMoreOpen(false)} />
>
)}
@@ -409,21 +500,13 @@ function GameDetailsRoute() {
)}
- {reviews?.total >= 1 && (
-
-
-
-
-
-
-
-
-
{rating}
-
- ({reviews?.total} {t("library:reviews")})
-
-
- )}
+
+
+ {rating}
+
+ ({reviews?.total ?? 0} {t("library:reviews")})
+
+
@@ -517,20 +600,25 @@ function GameDetailsRoute() {
label: t("feed:lists.wanttoplay"),
icon: "lucide:bookmark",
iconClass: "text-purple-400",
- value: "5%",
+ value: `${game.progressStats?.planToPlay?.percentage ?? 0}%`,
+ },
+ {
+ label: t("feed:lists.playing"),
+ icon: "lucide:gamepad",
+ iconClass: "text-chart-1",
+ value: `${game.progressStats?.playing?.percentage ?? 0}%`,
},
- { label: t("feed:lists.playing"), icon: "lucide:gamepad", iconClass: "text-chart-1", value: "15%" },
{
label: t("feed:lists.played"),
icon: "lucide:check-circle",
iconClass: "text-secondary",
- value: "72%",
+ value: `${game.progressStats?.completed?.percentage ?? 0}%`,
},
{
label: t("feed:lists.dropped"),
icon: "lucide:x-circle",
iconClass: "text-destructive",
- value: "8%",
+ value: `${game.progressStats?.dropped?.percentage ?? 0}%`,
},
]}
/>
@@ -674,22 +762,7 @@ function GameDetailsRoute() {
)}
- {(reviews?.total ?? 0) >= 1 ? (
-
- ) : (
+ {(reviews?.items?.length ?? 0) === 0 ? (
@@ -699,6 +772,34 @@ function GameDetailsRoute() {
{t("library:noReviewsDescription")}
+ ) : (
+
+ {reviews.items.map((review: ApiTypes.Review) => (
+
+ toggleReaction.mutate(
+ { reviewId: review.id, currentReaction, emoji },
+ { onSuccess: () => queryClient.invalidateQueries({ queryKey: ["gameReviews", game.id] }) },
+ )
+ }
+ isReacting={toggleReaction.isPending && toggleReaction.variables?.reviewId === review.id}
+ />
+ ))}
+
)}
>
diff --git a/src/routes/game/anticipated/index.tsx b/src/routes/game/anticipated/index.tsx
index 1a42cbb..6e1d182 100644
--- a/src/routes/game/anticipated/index.tsx
+++ b/src/routes/game/anticipated/index.tsx
@@ -10,6 +10,7 @@ import { seo } from "@/lib/utils/seo.ts";
import { useInfiniteScroll } from "@/lib/utils/useInfiniteScroll.ts";
export const Route = createFileRoute("/game/anticipated/")({
+ ssr: "data-only",
component: AnticipatedGameRoute,
head: () => ({
meta: [
diff --git a/src/routes/game/index.tsx b/src/routes/game/index.tsx
index 0873f85..54fa4c2 100644
--- a/src/routes/game/index.tsx
+++ b/src/routes/game/index.tsx
@@ -12,6 +12,7 @@ import { api, apiEndpoints } from "@/lib/api.ts";
import { seo } from "@/lib/utils/seo.ts";
export const Route = createFileRoute("/game/")({
+ ssr: "data-only",
component: GameRoute,
head: () => ({
meta: [
diff --git a/src/routes/game/popular/index.tsx b/src/routes/game/popular/index.tsx
index 146485b..e0f591d 100644
--- a/src/routes/game/popular/index.tsx
+++ b/src/routes/game/popular/index.tsx
@@ -10,6 +10,7 @@ import { seo } from "@/lib/utils/seo.ts";
import { useInfiniteScroll } from "@/lib/utils/useInfiniteScroll.ts";
export const Route = createFileRoute("/game/popular/")({
+ ssr: "data-only",
component: PopularGameRoute,
head: () => ({
meta: [
diff --git a/src/routes/game/recent/index.tsx b/src/routes/game/recent/index.tsx
index 9f7490f..f80999a 100644
--- a/src/routes/game/recent/index.tsx
+++ b/src/routes/game/recent/index.tsx
@@ -10,6 +10,7 @@ import { seo } from "@/lib/utils/seo.ts";
import { useInfiniteScroll } from "@/lib/utils/useInfiniteScroll.ts";
export const Route = createFileRoute("/game/recent/")({
+ ssr: "data-only",
component: RecentGameRoute,
head: () => ({
meta: [
diff --git a/src/routes/game/upcoming/index.tsx b/src/routes/game/upcoming/index.tsx
index bc4aab4..448ad83 100644
--- a/src/routes/game/upcoming/index.tsx
+++ b/src/routes/game/upcoming/index.tsx
@@ -10,6 +10,7 @@ import { seo } from "@/lib/utils/seo.ts";
import { useInfiniteScroll } from "@/lib/utils/useInfiniteScroll.ts";
export const Route = createFileRoute("/game/upcoming/")({
+ ssr: "data-only",
component: UpcomingGameRoute,
head: () => ({
meta: [
diff --git a/src/routes/index.tsx b/src/routes/index.tsx
index fdbbdb4..38edfa3 100644
--- a/src/routes/index.tsx
+++ b/src/routes/index.tsx
@@ -8,6 +8,7 @@ import { authClient } from "@/lib/auth.ts";
import { seo } from "@/lib/utils/seo";
export const Route = createFileRoute("/")({
+ ssr: "data-only",
staticData: { layout: "full" },
validateSearch: (search) => ({
landing: search.landing as string | undefined,
diff --git a/src/routes/manga/$slug.tsx b/src/routes/manga/$slug.tsx
index adf8467..17804d7 100644
--- a/src/routes/manga/$slug.tsx
+++ b/src/routes/manga/$slug.tsx
@@ -33,6 +33,7 @@ import { getGenreLabel } from "@/lib/utils/genre-utils.ts";
import { seo } from "@/lib/utils/seo";
export const Route = createFileRoute("/manga/$slug")({
+ ssr: "data-only",
loader: async ({ params }) => {
const manga = await api.get(apiEndpoints.getMangaDetails(params.slug)).then(({ data }) => data.manga);
return { manga };
diff --git a/src/routes/manga/index.tsx b/src/routes/manga/index.tsx
index cf68197..78bfdb8 100644
--- a/src/routes/manga/index.tsx
+++ b/src/routes/manga/index.tsx
@@ -12,6 +12,7 @@ import { api, apiEndpoints } from "@/lib/api.ts";
import { seo } from "@/lib/utils/seo.ts";
export const Route = createFileRoute("/manga/")({
+ ssr: "data-only",
component: MangaRoute,
head: () => ({
meta: [
diff --git a/src/routes/manga/publishing/index.tsx b/src/routes/manga/publishing/index.tsx
index 3bc6505..9565e62 100644
--- a/src/routes/manga/publishing/index.tsx
+++ b/src/routes/manga/publishing/index.tsx
@@ -10,6 +10,7 @@ import { seo } from "@/lib/utils/seo.ts";
import { useInfiniteScroll } from "@/lib/utils/useInfiniteScroll.ts";
export const Route = createFileRoute("/manga/publishing/")({
+ ssr: "data-only",
component: PublishingMangaRoute,
head: () => ({
meta: [
diff --git a/src/routes/manga/recommendations/index.tsx b/src/routes/manga/recommendations/index.tsx
index 3b9b591..d63f7e3 100644
--- a/src/routes/manga/recommendations/index.tsx
+++ b/src/routes/manga/recommendations/index.tsx
@@ -10,6 +10,7 @@ import { seo } from "@/lib/utils/seo.ts";
import { useInfiniteScroll } from "@/lib/utils/useInfiniteScroll.ts";
export const Route = createFileRoute("/manga/recommendations/")({
+ ssr: "data-only",
component: RecommendationsMangaRoute,
head: () => ({
meta: [
diff --git a/src/routes/manga/top/index.tsx b/src/routes/manga/top/index.tsx
index 582b79a..531eb84 100644
--- a/src/routes/manga/top/index.tsx
+++ b/src/routes/manga/top/index.tsx
@@ -10,6 +10,7 @@ import { seo } from "@/lib/utils/seo.ts";
import { useInfiniteScroll } from "@/lib/utils/useInfiniteScroll.ts";
export const Route = createFileRoute("/manga/top/")({
+ ssr: "data-only",
component: TopMangaRoute,
head: () => ({
meta: [
diff --git a/src/routes/manga/upcoming/index.tsx b/src/routes/manga/upcoming/index.tsx
index 3a71fd3..a47a535 100644
--- a/src/routes/manga/upcoming/index.tsx
+++ b/src/routes/manga/upcoming/index.tsx
@@ -10,6 +10,7 @@ import { seo } from "@/lib/utils/seo.ts";
import { useInfiniteScroll } from "@/lib/utils/useInfiniteScroll.ts";
export const Route = createFileRoute("/manga/upcoming/")({
+ ssr: "data-only",
component: UpcomingMangaRoute,
head: () => ({
meta: [
diff --git a/src/routes/movie/$slug.tsx b/src/routes/movie/$slug.tsx
index 78898e2..978b8f8 100644
--- a/src/routes/movie/$slug.tsx
+++ b/src/routes/movie/$slug.tsx
@@ -47,6 +47,7 @@ interface MovieProgress {
}
export const Route = createFileRoute("/movie/$slug")({
+ ssr: "data-only",
loader: async ({ params }) => {
const movie = await api.get(apiEndpoints.getMovieDetails(params.slug)).then(({ data }) => data.movie);
return { movie };
diff --git a/src/routes/movie/airing/index.tsx b/src/routes/movie/airing/index.tsx
index 944d5ab..2af5b56 100644
--- a/src/routes/movie/airing/index.tsx
+++ b/src/routes/movie/airing/index.tsx
@@ -10,6 +10,7 @@ import { seo } from "@/lib/utils/seo.ts";
import { useInfiniteScroll } from "@/lib/utils/useInfiniteScroll.ts";
export const Route = createFileRoute("/movie/airing/")({
+ ssr: "data-only",
component: AiringMovieRoute,
head: () => ({
meta: [
diff --git a/src/routes/movie/index.tsx b/src/routes/movie/index.tsx
index 963801f..4ba4988 100644
--- a/src/routes/movie/index.tsx
+++ b/src/routes/movie/index.tsx
@@ -12,6 +12,7 @@ import { api, apiEndpoints } from "@/lib/api.ts";
import { seo } from "@/lib/utils/seo.ts";
export const Route = createFileRoute("/movie/")({
+ ssr: "data-only",
component: MovieRoute,
head: () => ({
meta: [
diff --git a/src/routes/movie/popular/index.tsx b/src/routes/movie/popular/index.tsx
index ed141f7..461694f 100644
--- a/src/routes/movie/popular/index.tsx
+++ b/src/routes/movie/popular/index.tsx
@@ -10,6 +10,7 @@ import { seo } from "@/lib/utils/seo.ts";
import { useInfiniteScroll } from "@/lib/utils/useInfiniteScroll.ts";
export const Route = createFileRoute("/movie/popular/")({
+ ssr: "data-only",
component: PopularMovieRoute,
head: () => ({
meta: [
diff --git a/src/routes/movie/trending/index.tsx b/src/routes/movie/trending/index.tsx
index 89310cc..014e74b 100644
--- a/src/routes/movie/trending/index.tsx
+++ b/src/routes/movie/trending/index.tsx
@@ -10,6 +10,7 @@ import { seo } from "@/lib/utils/seo.ts";
import { useInfiniteScroll } from "@/lib/utils/useInfiniteScroll.ts";
export const Route = createFileRoute("/movie/trending/")({
+ ssr: "data-only",
component: TrendingMovieRoute,
head: () => ({
meta: [
diff --git a/src/routes/movie/upcoming/index.tsx b/src/routes/movie/upcoming/index.tsx
index f3ef259..861c811 100644
--- a/src/routes/movie/upcoming/index.tsx
+++ b/src/routes/movie/upcoming/index.tsx
@@ -10,6 +10,7 @@ import { seo } from "@/lib/utils/seo.ts";
import { useInfiniteScroll } from "@/lib/utils/useInfiniteScroll.ts";
export const Route = createFileRoute("/movie/upcoming/")({
+ ssr: "data-only",
component: UpcomingMovieRoute,
head: () => ({
meta: [
diff --git a/src/routes/privacy-policy.tsx b/src/routes/privacy-policy.tsx
index 95363f9..f95e10c 100644
--- a/src/routes/privacy-policy.tsx
+++ b/src/routes/privacy-policy.tsx
@@ -3,6 +3,7 @@ import { Trans, useTranslation } from "react-i18next";
import { seo } from "@/lib/utils/seo";
export const Route = createFileRoute("/privacy-policy")({
+ ssr: "data-only",
head: () => ({
meta: [...seo({ title: "Privacy Policy" })],
}),
diff --git a/src/routes/search.tsx b/src/routes/search.tsx
index f9a1b4d..0bcf8d4 100644
--- a/src/routes/search.tsx
+++ b/src/routes/search.tsx
@@ -28,6 +28,7 @@ const CONTENT_TYPES: { value: ContentType; labelKey: string }[] = [
];
export const Route = createFileRoute("/search")({
+ ssr: "data-only",
head: () => ({
meta: [...seo({ title: "Search" })],
}),
diff --git a/src/routes/tos.tsx b/src/routes/tos.tsx
index 462da67..fbdd3d9 100644
--- a/src/routes/tos.tsx
+++ b/src/routes/tos.tsx
@@ -4,6 +4,7 @@ import { Trans, useTranslation } from "react-i18next";
import { seo } from "@/lib/utils/seo";
export const Route = createFileRoute("/tos")({
+ ssr: "data-only",
head: () => ({
meta: [...seo({ title: "Terms of Service" })],
}),
diff --git a/src/routes/tv/$slug.tsx b/src/routes/tv/$slug.tsx
index e246068..174dd44 100644
--- a/src/routes/tv/$slug.tsx
+++ b/src/routes/tv/$slug.tsx
@@ -40,6 +40,7 @@ import { seo } from "@/lib/utils/seo";
import { getStatusLabel } from "@/lib/utils/status.ts";
export const Route = createFileRoute("/tv/$slug")({
+ ssr: "data-only",
loader: async ({ params }) => {
const item = await api.get(apiEndpoints.getTvShowDetails(params.slug)).then(({ data }) => data.tvShow);
return { item };
diff --git a/src/routes/tv/airing/index.tsx b/src/routes/tv/airing/index.tsx
index a19f103..9d47b2f 100644
--- a/src/routes/tv/airing/index.tsx
+++ b/src/routes/tv/airing/index.tsx
@@ -10,6 +10,7 @@ import { seo } from "@/lib/utils/seo.ts";
import { useInfiniteScroll } from "@/lib/utils/useInfiniteScroll.ts";
export const Route = createFileRoute("/tv/airing/")({
+ ssr: "data-only",
component: AiringTVShowRoute,
head: () => ({
meta: [
diff --git a/src/routes/tv/index.tsx b/src/routes/tv/index.tsx
index 3c2cf0e..f8b22ea 100644
--- a/src/routes/tv/index.tsx
+++ b/src/routes/tv/index.tsx
@@ -13,6 +13,7 @@ import { AVATAR_BLUR } from "@/lib/image.ts";
import { seo } from "@/lib/utils/seo.ts";
export const Route = createFileRoute("/tv/")({
+ ssr: "data-only",
component: SerieRoute,
head: () => ({
meta: [
diff --git a/src/routes/tv/popular/index.tsx b/src/routes/tv/popular/index.tsx
index 7d4bf52..9c26d9c 100644
--- a/src/routes/tv/popular/index.tsx
+++ b/src/routes/tv/popular/index.tsx
@@ -10,6 +10,7 @@ import { seo } from "@/lib/utils/seo.ts";
import { useInfiniteScroll } from "@/lib/utils/useInfiniteScroll.ts";
export const Route = createFileRoute("/tv/popular/")({
+ ssr: "data-only",
component: PopularTVShowRoute,
head: () => ({
meta: [
diff --git a/src/routes/tv/trending/index.tsx b/src/routes/tv/trending/index.tsx
index 86a9996..588bd6c 100644
--- a/src/routes/tv/trending/index.tsx
+++ b/src/routes/tv/trending/index.tsx
@@ -10,6 +10,7 @@ import { seo } from "@/lib/utils/seo.ts";
import { useInfiniteScroll } from "@/lib/utils/useInfiniteScroll.ts";
export const Route = createFileRoute("/tv/trending/")({
+ ssr: "data-only",
component: TrendingTVShowRoute,
head: () => ({
meta: [
diff --git a/src/routes/tv/upcoming/index.tsx b/src/routes/tv/upcoming/index.tsx
index f7df715..eb0a81e 100644
--- a/src/routes/tv/upcoming/index.tsx
+++ b/src/routes/tv/upcoming/index.tsx
@@ -10,6 +10,7 @@ import { seo } from "@/lib/utils/seo.ts";
import { useInfiniteScroll } from "@/lib/utils/useInfiniteScroll.ts";
export const Route = createFileRoute("/tv/upcoming/")({
+ ssr: "data-only",
component: UpcomingTVShowRoute,
head: () => ({
meta: [
diff --git a/src/routes/user/$username/index.tsx b/src/routes/user/$username/index.tsx
index 193af7c..53ec808 100644
--- a/src/routes/user/$username/index.tsx
+++ b/src/routes/user/$username/index.tsx
@@ -29,6 +29,7 @@ async function getUser(username: string) {
}
export const Route = createFileRoute("/user/$username/")({
+ ssr: "data-only",
staticData: { layout: "full" },
beforeLoad: ({ params, search }) => {
if (!(search as { tab?: string }).tab) {
diff --git a/src/start.ts b/src/start.ts
new file mode 100644
index 0000000..9af69fd
--- /dev/null
+++ b/src/start.ts
@@ -0,0 +1,5 @@
+import { createStart } from "@tanstack/react-start";
+
+export const startInstance = createStart(() => ({
+ defaultSsr: false,
+}));
diff --git a/vite.config.ts b/vite.config.ts
index ef44337..e65aadf 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,25 +1,12 @@
import tailwindcss from "@tailwindcss/vite";
import { devtools } from "@tanstack/devtools-vite";
-import { tanstackRouter } from "@tanstack/router-plugin/vite";
+import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
export default defineConfig({
- plugins: [devtools(), tanstackRouter({ target: "react", autoCodeSplitting: true }), react(), tailwindcss()],
+ plugins: [devtools(), tanstackStart(), react(), tailwindcss()],
resolve: {
tsconfigPaths: true,
},
- build: {
- rolldownOptions: {
- output: {
- manualChunks(id) {
- if (!id.includes("node_modules")) return;
- const [, pkgPath = ""] = id.split(/node_modules[\\/]/);
- const [scopeOrName, maybeName] = pkgPath.split(/[\\/]/);
- if (!scopeOrName) return;
- return scopeOrName.startsWith("@") && maybeName ? `${scopeOrName}/${maybeName}` : scopeOrName;
- },
- },
- },
- },
});