All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m16s
Svelte-5-Runes-Store mit uncheckedCount, recipeIds und loaded. refresh() holt Snapshot via GET /api/shopping-list, addRecipe/ removeRecipe posten bzw. loeschen und refreshen anschliessend. Bei Netzwerkfehler bleibt der letzte bekannte State erhalten.
53 lines
1.5 KiB
TypeScript
53 lines
1.5 KiB
TypeScript
type Snapshot = {
|
|
recipes: { recipe_id: number }[];
|
|
uncheckedCount: number;
|
|
};
|
|
|
|
export class ShoppingCartStore {
|
|
uncheckedCount = $state(0);
|
|
recipeIds = $state<Set<number>>(new Set());
|
|
loaded = $state(false);
|
|
|
|
private readonly fetchImpl: typeof fetch;
|
|
|
|
constructor(fetchImpl?: typeof fetch) {
|
|
this.fetchImpl = fetchImpl ?? ((...a) => fetch(...a));
|
|
}
|
|
|
|
async refresh(): Promise<void> {
|
|
try {
|
|
const res = await this.fetchImpl('/api/shopping-list');
|
|
if (!res.ok) return;
|
|
const body = (await res.json()) as Snapshot;
|
|
this.recipeIds = new Set(body.recipes.map((r) => r.recipe_id));
|
|
this.uncheckedCount = body.uncheckedCount;
|
|
this.loaded = true;
|
|
} catch {
|
|
// keep last known state on network error
|
|
}
|
|
}
|
|
|
|
async addRecipe(recipeId: number): Promise<void> {
|
|
const res = await this.fetchImpl('/api/shopping-list/recipe', {
|
|
method: 'POST',
|
|
headers: { 'content-type': 'application/json' },
|
|
body: JSON.stringify({ recipe_id: recipeId })
|
|
});
|
|
// Consume body to avoid leaking response, even if we ignore the payload.
|
|
await res.json().catch(() => null);
|
|
await this.refresh();
|
|
}
|
|
|
|
async removeRecipe(recipeId: number): Promise<void> {
|
|
const res = await this.fetchImpl(`/api/shopping-list/recipe/${recipeId}`, { method: 'DELETE' });
|
|
await res.json().catch(() => null);
|
|
await this.refresh();
|
|
}
|
|
|
|
isInCart(recipeId: number): boolean {
|
|
return this.recipeIds.has(recipeId);
|
|
}
|
|
}
|
|
|
|
export const shoppingCartStore = new ShoppingCartStore();
|