feat(client): PhotoUploadStore mit idle/loading/success/error

This commit is contained in:
hsiegeln
2026-04-21 10:45:36 +02:00
parent 8c23875ba2
commit bc42f35f8c
2 changed files with 163 additions and 0 deletions

View File

@@ -0,0 +1,76 @@
import type { Recipe } from '$lib/types';
export type UploadStatus = 'idle' | 'loading' | 'success' | 'error';
export class PhotoUploadStore {
status = $state<UploadStatus>('idle');
recipe = $state<Recipe | null>(null);
errorCode = $state<string | null>(null);
errorMessage = $state<string | null>(null);
lastFile = $state<File | null>(null);
private controller: AbortController | null = null;
private readonly fetchImpl: typeof fetch;
constructor(opts: { fetchImpl?: typeof fetch } = {}) {
this.fetchImpl = opts.fetchImpl ?? fetch;
}
async upload(file: File): Promise<void> {
this.lastFile = file;
await this.doUpload(file);
}
async retry(): Promise<void> {
if (this.lastFile) await this.doUpload(this.lastFile);
}
reset(): void {
this.status = 'idle';
this.recipe = null;
this.errorCode = null;
this.errorMessage = null;
this.lastFile = null;
this.controller?.abort();
this.controller = null;
}
abort(): void {
this.controller?.abort();
}
private async doUpload(file: File): Promise<void> {
this.status = 'loading';
this.recipe = null;
this.errorCode = null;
this.errorMessage = null;
this.controller = new AbortController();
const fd = new FormData();
fd.append('photo', file);
try {
const res = await this.fetchImpl('/api/recipes/extract-from-photo', {
method: 'POST',
body: fd,
signal: this.controller.signal
});
const body = await res.json().catch(() => ({}));
if (!res.ok) {
this.status = 'error';
this.errorCode = typeof body.code === 'string' ? body.code : 'UNKNOWN';
this.errorMessage =
typeof body.message === 'string' ? body.message : `HTTP ${res.status}`;
return;
}
this.recipe = body.recipe as Recipe;
this.status = 'success';
} catch (e) {
if ((e as Error).name === 'AbortError') {
this.status = 'idle';
return;
}
this.status = 'error';
this.errorCode = 'NETWORK';
this.errorMessage = (e as Error).message;
}
}
}