From 7337f38710f1333d5057d5ab8ed0ccdec1afe084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20F=C3=B6rtsch?= Date: Mon, 2 Mar 2026 21:31:14 +0100 Subject: [PATCH] add app versioning, state-aware update button, disable pinch zoom Co-Authored-By: Claude Opus 4.6 --- index.html | 6 +- package.json | 2 +- public/manifest.webmanifest | 21 --- server/package.json | 2 +- .../settings/components/settings-list.tsx | 143 ++++++++++++++---- src/shared/i18n/locales/de.ts | 14 ++ src/shared/i18n/locales/en.ts | 14 ++ src/vite-env.d.ts | 2 + vite.config.ts | 26 +++- 9 files changed, 173 insertions(+), 57 deletions(-) delete mode 100644 public/manifest.webmanifest diff --git a/index.html b/index.html index 821bc32..16d964e 100644 --- a/index.html +++ b/index.html @@ -2,12 +2,12 @@ - + WhatToPlay - - + +
diff --git a/package.json b/package.json index e33bcb9..20a96d0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "whattoplay", "private": true, - "version": "2026.03.01", + "version": "2026.03.02", "type": "module", "scripts": { "dev": "vite", diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest deleted file mode 100644 index 1e22fe3..0000000 --- a/public/manifest.webmanifest +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "WhatToPlay", - "short_name": "WhatToPlay", - "description": "Manage your game library across platforms", - "start_url": "/", - "display": "standalone", - "theme_color": "#0f172a", - "background_color": "#0f172a", - "icons": [ - { - "src": "/icons/icon-192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/icons/icon-512.png", - "sizes": "512x512", - "type": "image/png" - } - ] -} diff --git a/server/package.json b/server/package.json index 77b322b..8c4cf79 100644 --- a/server/package.json +++ b/server/package.json @@ -1,7 +1,7 @@ { "name": "whattoplay-server", "private": true, - "version": "2026.03.01", + "version": "2026.03.02", "type": "module", "scripts": { "dev": "bun --watch src/index.ts", diff --git a/src/features/settings/components/settings-list.tsx b/src/features/settings/components/settings-list.tsx index dac867f..2cb6c31 100644 --- a/src/features/settings/components/settings-list.tsx +++ b/src/features/settings/components/settings-list.tsx @@ -1,8 +1,13 @@ +import { useRegisterSW } from "virtual:pwa-register/react" import { Badge } from "@/shared/components/ui/badge" -import { Card } from "@/shared/components/ui/card" +import { Button } from "@/shared/components/ui/button" +import { ListItem } from "@/shared/components/ui/list-item" import { useConfig } from "@/shared/db/hooks" import { t } from "@/shared/i18n" -import { Link } from "@tanstack/react-router" +import { api } from "@/shared/lib/api" +import { useNavigate } from "@tanstack/react-router" +import { Loader2 } from "lucide-react" +import { useState } from "react" const providers = [ { id: "steam", label: "Steam" }, @@ -12,6 +17,14 @@ const providers = [ export function SettingsList() { const steamConfig = useConfig<{ apiKey: string; steamId: string }>("steam") const gogConfig = useConfig<{ accessToken: string }>("gog") + const navigate = useNavigate() + + const [testState, setTestState] = useState<"idle" | "testing" | "ok" | "failed">("idle") + + const { + needRefresh: [needRefresh], + updateServiceWorker, + } = useRegisterSW() const isConnected = (id: string) => { if (id === "steam") return Boolean(steamConfig?.apiKey) @@ -19,34 +32,106 @@ export function SettingsList() { return false } - return ( -
-
-

- {t("settings.providers")} -

-
- {providers.map((p) => ( - - - {p.label} - - {isConnected(p.id) ? "Connected" : "Not configured"} - - - - ))} -
-
+ const handleTestConnection = async () => { + setTestState("testing") + try { + const res = await api.health.$get() + if (res.ok) { + setTestState("ok") + } else { + setTestState("failed") + } + } catch { + setTestState("failed") + } + } -
-

{t("settings.data")}

- - - {t("settings.data")} - - -
+ return ( +
+

+ {t("settings.app")} +

+
+ updateServiceWorker()}> + {t("settings.updateApp")} + + ) : ( + {t("settings.upToDate")} + ) + } + /> +
+

v{__APP_VERSION__}

+ +

+ {t("settings.server")} +

+
+ + {testState === "testing" && } + {testState === "ok" && ( + + {t("settings.connectionOk")} + + )} + {testState === "failed" && ( + + {t("settings.connectionFailed")} + + )} + +
+ } + /> +
+ +

+ {t("settings.providers")} +

+
+ {providers.map((p) => ( + + {isConnected(p.id) ? "Connected" : "Not configured"} + + } + onClick={() => navigate({ to: "/settings/$provider", params: { provider: p.id } })} + /> + ))} +
+ +

+ {t("settings.data")} +

+
+ navigate({ to: "/settings/$provider", params: { provider: "data" } })} + /> +
) } diff --git a/src/shared/i18n/locales/de.ts b/src/shared/i18n/locales/de.ts index f231d01..6497d0f 100644 --- a/src/shared/i18n/locales/de.ts +++ b/src/shared/i18n/locales/de.ts @@ -33,6 +33,8 @@ export const de: Record = { "settings.data": "Daten", "settings.steam": "Steam", "settings.gog": "GOG", + "settings.steam.instructions": + "Du brauchst einen Steam Web API Key und deine Steam-ID. Registriere dir kostenlos einen API Key im Steam-Entwicklerportal und füge ihn zusammen mit deiner Steam-ID oder Profil-URL unten ein.", "settings.steam.apiKey": "API-Schlüssel", "settings.steam.steamId": "Steam-ID", "settings.steam.sync": "Spiele synchronisieren", @@ -43,8 +45,20 @@ export const de: Record = { "settings.data.import": "Daten importieren", "settings.data.clear": "Alle Daten löschen", "settings.data.clearConfirm": "Bist du sicher? Alle Spiele und Playlisten werden gelöscht.", + "settings.app": "App", + "settings.updateAvailable": "Update verfügbar", + "settings.appUpToDate": "App ist aktuell", + "settings.updateApp": "Aktualisieren", + "settings.upToDate": "✓ Aktuell", + "settings.server": "Server", + "settings.connection": "Verbindung", + "settings.testConnection": "Testen", + "settings.connectionOk": "OK", + "settings.connectionFailed": "Fehlgeschlagen", "settings.lastSync": "Letzte Synchronisierung", "settings.syncing": "Synchronisiere...", + "settings.syncFetching": "Spiele werden vom Server geladen...", + "settings.syncSaving": "Speichere {current} / {total} Spiele...", "settings.syncSuccess": "{count} Spiele synchronisiert", "state.not_set": "Nicht gesetzt", diff --git a/src/shared/i18n/locales/en.ts b/src/shared/i18n/locales/en.ts index aba8c6e..4a34671 100644 --- a/src/shared/i18n/locales/en.ts +++ b/src/shared/i18n/locales/en.ts @@ -36,6 +36,8 @@ export const en = { "settings.data": "Data", "settings.steam": "Steam", "settings.gog": "GOG", + "settings.steam.instructions": + "You need a Steam Web API key and your Steam ID. Register for a free API key on the Steam developer portal, then paste it below along with your Steam ID or profile URL.", "settings.steam.apiKey": "API Key", "settings.steam.steamId": "Steam ID", "settings.steam.sync": "Sync Games", @@ -46,8 +48,20 @@ export const en = { "settings.data.import": "Import Data", "settings.data.clear": "Clear All Data", "settings.data.clearConfirm": "Are you sure? This will delete all games and playlists.", + "settings.app": "App", + "settings.updateAvailable": "Update available", + "settings.appUpToDate": "App is up to date", + "settings.updateApp": "Update", + "settings.upToDate": "✓ Up to date", + "settings.server": "Server", + "settings.connection": "Connection", + "settings.testConnection": "Test", + "settings.connectionOk": "OK", + "settings.connectionFailed": "Failed", "settings.lastSync": "Last sync", "settings.syncing": "Syncing...", + "settings.syncFetching": "Fetching games from server...", + "settings.syncSaving": "Saving {current} / {total} games...", "settings.syncSuccess": "Synced {count} games", // Game states diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 97abc3a..e4874e4 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1,5 +1,7 @@ /// +declare const __APP_VERSION__: string + declare module "*.sql?raw" { const content: string export default content diff --git a/vite.config.ts b/vite.config.ts index 0183c37..e558302 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,16 +1,37 @@ +import { execSync } from "node:child_process" +import { readFileSync } from "node:fs" import tailwindcss from "@tailwindcss/vite" import { TanStackRouterVite } from "@tanstack/router-plugin/vite" import react from "@vitejs/plugin-react" import { defineConfig } from "vite" import { VitePWA } from "vite-plugin-pwa" +const pkg = JSON.parse(readFileSync("./package.json", "utf-8")) +const gitCount = execSync("git rev-list --count HEAD", { encoding: "utf-8" }).trim() + export default defineConfig({ + base: "/whattoplay/", + define: { + __APP_VERSION__: JSON.stringify(`${pkg.version}+${gitCount}`), + }, plugins: [ TanStackRouterVite(), react(), tailwindcss(), VitePWA({ - registerType: "autoUpdate", + registerType: "prompt", + manifest: { + name: "WhatToPlay", + short_name: "WhatToPlay", + description: "Manage your game library across platforms", + theme_color: "#0f172a", + background_color: "#0f172a", + display: "standalone", + icons: [ + { src: "icons/icon-192.png", sizes: "192x192", type: "image/png" }, + { src: "icons/icon-512.png", sizes: "512x512", type: "image/png" }, + ], + }, workbox: { globPatterns: ["**/*.{js,css,html,ico,png,svg,wasm,data}"], maximumFileSizeToCacheInBytes: 10 * 1024 * 1024, @@ -24,9 +45,10 @@ export default defineConfig({ }, server: { proxy: { - "/api": { + "/whattoplay/api": { target: "http://localhost:3001", changeOrigin: true, + rewrite: (path) => path.replace(/^\/whattoplay\/api/, ""), }, }, },