feat: complete mealplanner app (backend + frontend + deployment)

.NET 8 backend with Zitadel JWT auth, TheMealDB integration,
weekly meal plan generation, shopping list aggregation.
Vue 3 + Tailwind 4 frontend with dark emerald theme,
manual OIDC PKCE auth, all views implemented.
Multi-stage Dockerfile with nginx reverse proxy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-14 19:10:10 +00:00
parent 660bcd1953
commit f58782774b
51 changed files with 4061 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
import { getAccessToken, login } from '../auth'
const BASE = '/api'
async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
const token = getAccessToken()
if (!token) { login(window.location.pathname); throw new Error('Not authenticated') }
const res = await fetch(`${BASE}${path}`, {
...options,
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, ...options.headers },
})
if (res.status === 401) { login(window.location.pathname); throw new Error('Unauthorized') }
if (!res.ok) throw new Error(`HTTP ${res.status}`)
return res.json() as Promise<T>
}
export function useApi() {
return {
get: <T>(path: string) => request<T>(path),
post: <T>(path: string, body?: unknown) => request<T>(path, { method: 'POST', ...(body !== undefined ? { body: JSON.stringify(body) } : {}) }),
put: <T>(path: string, body?: unknown) => request<T>(path, { method: 'PUT', ...(body !== undefined ? { body: JSON.stringify(body) } : {}) }),
del: <T>(path: string) => request<T>(path, { method: 'DELETE' }),
}
}