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:
24
frontend/src/composables/useApi.ts
Normal file
24
frontend/src/composables/useApi.ts
Normal 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' }),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user