feat(01-03): add data hooks, utilities, UI store, and foundational components
- API fetch wrapper with error handling and multipart upload - Weight/price formatters for display - TanStack Query hooks for items, categories, and totals with cache invalidation - Zustand UI store for panel and confirm dialog state - TotalsBar, CategoryHeader, ItemCard, ConfirmDialog, ImageUpload components Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
61
src/client/lib/api.ts
Normal file
61
src/client/lib/api.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
class ApiError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public status: number,
|
||||
) {
|
||||
super(message);
|
||||
this.name = "ApiError";
|
||||
}
|
||||
}
|
||||
|
||||
async function handleResponse<T>(res: Response): Promise<T> {
|
||||
if (!res.ok) {
|
||||
let message = `Request failed with status ${res.status}`;
|
||||
try {
|
||||
const body = await res.json();
|
||||
if (body.error) message = body.error;
|
||||
} catch {
|
||||
// Use default message
|
||||
}
|
||||
throw new ApiError(message, res.status);
|
||||
}
|
||||
return res.json() as Promise<T>;
|
||||
}
|
||||
|
||||
export async function apiGet<T>(url: string): Promise<T> {
|
||||
const res = await fetch(url);
|
||||
return handleResponse<T>(res);
|
||||
}
|
||||
|
||||
export async function apiPost<T>(url: string, body: unknown): Promise<T> {
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
return handleResponse<T>(res);
|
||||
}
|
||||
|
||||
export async function apiPut<T>(url: string, body: unknown): Promise<T> {
|
||||
const res = await fetch(url, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
return handleResponse<T>(res);
|
||||
}
|
||||
|
||||
export async function apiDelete<T>(url: string): Promise<T> {
|
||||
const res = await fetch(url, { method: "DELETE" });
|
||||
return handleResponse<T>(res);
|
||||
}
|
||||
|
||||
export async function apiUpload<T>(url: string, file: File): Promise<T> {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
return handleResponse<T>(res);
|
||||
}
|
||||
Reference in New Issue
Block a user