05f8e2e3b6
- Create reusable components: auth, api-client, shared-types - ModeTemplate with Hono backend and SvelteKit frontend - Auth store refactored to class-based Svelte 5 pattern - SSR-safe hydrate() pattern for localStorage access - Frontend builds successfully with Svelte 5 runes
98 lines
2.3 KiB
TypeScript
98 lines
2.3 KiB
TypeScript
/**
|
|
* API Client Component
|
|
*
|
|
* Frontend helper for making authenticated requests to the backend.
|
|
* Uses PocketBase token from auth session.
|
|
*/
|
|
|
|
// Header constants (inline to avoid cross-component dependency)
|
|
const HEADERS = {
|
|
AUTH: 'Authorization',
|
|
CONTENT_TYPE: 'Content-Type',
|
|
} as const;
|
|
|
|
const CONTENT_TYPES = {
|
|
JSON: 'application/json',
|
|
} as const;
|
|
|
|
function authHeader(token: string): string {
|
|
return `Bearer ${token}`;
|
|
}
|
|
|
|
export interface ApiResponse<T = unknown> {
|
|
ok: boolean;
|
|
status: number;
|
|
data?: T;
|
|
error?: string;
|
|
}
|
|
|
|
export interface ApiClientConfig {
|
|
baseUrl: string;
|
|
getToken: () => string | null;
|
|
}
|
|
|
|
/**
|
|
* Create an API client instance
|
|
*/
|
|
export function createApiClient(config: ApiClientConfig) {
|
|
const { baseUrl, getToken } = config;
|
|
|
|
async function request<T>(
|
|
method: string,
|
|
path: string,
|
|
body?: unknown
|
|
): Promise<ApiResponse<T>> {
|
|
const token = getToken();
|
|
|
|
const headers: Record<string, string> = {
|
|
[HEADERS.CONTENT_TYPE]: CONTENT_TYPES.JSON,
|
|
};
|
|
|
|
if (token) {
|
|
headers[HEADERS.AUTH] = authHeader(token);
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${baseUrl}${path}`, {
|
|
method,
|
|
headers,
|
|
body: body ? JSON.stringify(body) : undefined,
|
|
});
|
|
|
|
const data = response.headers.get('content-type')?.includes('application/json')
|
|
? await response.json()
|
|
: await response.text();
|
|
|
|
if (!response.ok) {
|
|
return {
|
|
ok: false,
|
|
status: response.status,
|
|
error: typeof data === 'string' ? data : data.message || 'Request failed',
|
|
};
|
|
}
|
|
|
|
return {
|
|
ok: true,
|
|
status: response.status,
|
|
data: data as T,
|
|
};
|
|
} catch (err) {
|
|
return {
|
|
ok: false,
|
|
status: 0,
|
|
error: err instanceof Error ? err.message : 'Network error',
|
|
};
|
|
}
|
|
}
|
|
|
|
return {
|
|
get: <T>(path: string) => request<T>('GET', path),
|
|
post: <T>(path: string, body?: unknown) => request<T>('POST', path, body),
|
|
put: <T>(path: string, body?: unknown) => request<T>('PUT', path, body),
|
|
patch: <T>(path: string, body?: unknown) => request<T>('PATCH', path, body),
|
|
delete: <T>(path: string) => request<T>('DELETE', path),
|
|
};
|
|
}
|
|
|
|
export type ApiClient = ReturnType<typeof createApiClient>;
|