Replace Next.js app with Vite React app (gerbil-manager-web)

- Remove gerbil-manager-nextjs (unused scaffold; recoverable from history)
- Scaffold gerbil-manager-web: Vite + React 19 + TypeScript
- German-only UI (lang=de, central strings in src/strings/de.ts)
- Mobile-first shell: bottom tab bar on phones, sidebar on >=768px
- react-router with skeleton routes (Start, Rennmaeuse, Wuerfe, Zuechter)
- API client (src/api/client.ts) pointing at GerbilManagerWebAPI,
  configurable via VITE_API_BASE_URL (default http://localhost:5179)
- nginx-based Dockerfile with SPA fallback; docker-compose frontend
  service now builds gerbil-manager-web

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-05 23:30:34 +02:00
parent 42a43ae24e
commit 6ee85bc909
41 changed files with 3436 additions and 4758 deletions

View File

@@ -0,0 +1,66 @@
import { de } from '../strings/de'
/**
* Basis-URL der GerbilManagerWebAPI.
* Per VITE_API_BASE_URL konfigurierbar (z. B. in Docker / Aspire);
* Standard ist das lokale Dev-Profil der API (launchSettings.json, Profil "http").
*/
export const API_BASE_URL: string =
import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:5179'
export class ApiError extends Error {
readonly status: number | null
constructor(message: string, status: number | null) {
super(message)
this.name = 'ApiError'
this.status = status
}
}
function errorMessageFor(status: number): string {
if (status === 404) return de.api.errors.notFound
if (status >= 500) return de.api.errors.server
return de.api.errors.unknown
}
async function request<T>(path: string, init?: RequestInit): Promise<T> {
let response: Response
try {
response = await fetch(`${API_BASE_URL}${path}`, {
headers: { 'Content-Type': 'application/json', ...init?.headers },
...init,
})
} catch {
throw new ApiError(de.api.errors.network, null)
}
if (!response.ok) {
throw new ApiError(errorMessageFor(response.status), response.status)
}
if (response.status === 204) {
return undefined as T
}
return (await response.json()) as T
}
export const api = {
get: <T>(path: string) => request<T>(path),
post: <T>(path: string, body: unknown) =>
request<T>(path, { method: 'POST', body: JSON.stringify(body) }),
put: <T>(path: string, body: unknown) =>
request<T>(path, { method: 'PUT', body: JSON.stringify(body) }),
delete: (path: string) => request<void>(path, { method: 'DELETE' }),
}
/**
* Ressourcen-Pfade der API (siehe GerbilManagerWebAPI/Controllers).
* Alle drei Ressourcen bieten volle CRUD-Endpunkte:
* GET /{resource}, GET /{resource}/{id}, POST, PUT /{id}, DELETE /{id}
*/
export const resources = {
gerbils: '/gerbils',
litters: '/litters',
breeders: '/breeders',
} as const