.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>
25 lines
1.1 KiB
TypeScript
25 lines
1.1 KiB
TypeScript
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' }),
|
|
}
|
|
}
|