From e398d53315b503d292473158c52ecc75adaadb00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20F=C3=B6rtsch?= Date: Wed, 11 Mar 2026 11:32:16 +0100 Subject: [PATCH] add dark mode toggle, uberspace deploy script Co-Authored-By: Claude Opus 4.6 --- bun.lock | 3 ++ package.json | 3 +- scripts/deploy.sh | 19 ++++++++ src/routes/__root.tsx | 82 +++++++++++++++++++++++------------ src/shared/hooks/use-theme.ts | 32 ++++++++++++++ 5 files changed, 110 insertions(+), 29 deletions(-) create mode 100755 scripts/deploy.sh create mode 100644 src/shared/hooks/use-theme.ts diff --git a/bun.lock b/bun.lock index 970efc9..59d9238 100644 --- a/bun.lock +++ b/bun.lock @@ -21,6 +21,7 @@ "tailwind-merge": "^3.5.0", "tailwindcss": "^4.2.1", "zod": "^4.3.6", + "zustand": "^5.0.11", }, "devDependencies": { "@biomejs/biome": "^2.4.6", @@ -1425,6 +1426,8 @@ "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], + "zustand": ["zustand@5.0.11", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg=="], + "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], "@rollup/plugin-babel/rollup": ["rollup@2.80.0", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ=="], diff --git a/package.json b/package.json index 7091ec1..7d6080a 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,8 @@ "react-dom": "^19.2.4", "tailwind-merge": "^3.5.0", "tailwindcss": "^4.2.1", - "zod": "^4.3.6" + "zod": "^4.3.6", + "zustand": "^5.0.11" }, "devDependencies": { "@biomejs/biome": "^2.4.6", diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..b11572c --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Description: Build and deploy TherapyFinder to Uberspace static hosting +# Usage: ./scripts/deploy.sh +# Example: ./scripts/deploy.sh felixfoertsch@andromeda.uberspace.de + +HOST="${1:?Usage: ./scripts/deploy.sh }" +USER="$(ssh "$HOST" whoami)" +REMOTE_DIR="/var/www/virtual/${USER}/html/tpf" + +echo "Building..." +bun run build + +echo "Deploying to ${HOST}:${REMOTE_DIR}..." +ssh "$HOST" "mkdir -p ${REMOTE_DIR}" +rsync -avz --delete dist/ "${HOST}:${REMOTE_DIR}/" + +echo "Done." diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index 154db95..11b387b 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -1,33 +1,59 @@ import { createRootRoute, Link, Outlet } from "@tanstack/react-router"; +import { useThemeStore } from "../shared/hooks/use-theme"; + +const themeIcon = { + light: "\u2600", + dark: "\u263D", + system: "\u2699", +} as const; +const themeNext = { + light: "dark", + dark: "system", + system: "light", +} as const; export const Route = createRootRoute({ - component: () => ( -
-
- -
- -
- ), + {themeIcon[theme]} + + +
+ +
+ + + ); + }, }); diff --git a/src/shared/hooks/use-theme.ts b/src/shared/hooks/use-theme.ts new file mode 100644 index 0000000..7f4cca9 --- /dev/null +++ b/src/shared/hooks/use-theme.ts @@ -0,0 +1,32 @@ +import { create } from "zustand"; + +type Theme = "light" | "dark" | "system"; + +interface ThemeStore { + theme: Theme; + setTheme: (theme: Theme) => void; +} + +export const useThemeStore = create((set) => ({ + theme: (localStorage.getItem("theme") as Theme) ?? "system", + setTheme: (theme) => { + localStorage.setItem("theme", theme); + set({ theme }); + applyTheme(theme); + }, +})); + +function applyTheme(theme: Theme) { + const root = document.documentElement; + if (theme === "system") { + const prefersDark = window.matchMedia( + "(prefers-color-scheme: dark)", + ).matches; + root.classList.toggle("dark", prefersDark); + } else { + root.classList.toggle("dark", theme === "dark"); + } +} + +// Apply on load +applyTheme(useThemeStore.getState().theme);