Files
Jungfraujoch/frontend/src/components/useUpload.ts
T
leonarski_fandClaude Opus 4.8 59da040e30 frontend: shared SettingsPanel + typed upload with error display
Generalize the expert-panel boilerplate (container + title + widgets +
upload button) and surface real upload errors via hey-api.

- SettingsPanel: consistent padded Paper + Typography title (one place to
  size it) + optional Upload button + result snackbar; PanelTitle and
  UploadSnackbarView exported for panels with bespoke layouts (DetectorSettings
  grid, ROI)
- useUpload(mutation): wraps a generated hey-api mutation, shows the server's
  error message on failure (errorMessage handles 400 string / 500 error_message)
- "unsaved changes" dot on panel titles, derived from the existing
  last-downloaded snapshot (dirty = !isEqual(value, lastDownloaded))
- fix PixelMask: TIFF upload was swallowing errors; now shows them
- config panels (FileWriter, ImageFormat, Detector, AzInt, DarkMask, Indexing,
  Instrument, ZeroMQ, ROI, DetectorSelection) upload through the typed SDK
  instead of ButtonWithSnackbar's raw fetch; status/display panels share the
  same shell for consistent spacing
- ButtonWithSnackbar now only powers bodyless action buttons (pedestal/cancel/
  initialize, start/trigger, deactivate)

Build (tsc + vite) passes; dev server transforms all modules.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 22:04:55 +02:00

42 lines
1.4 KiB
TypeScript

import { useState } from "react";
import { useMutation, UseMutationOptions } from "@tanstack/react-query";
/** Pull a human-readable message out of a hey-api error (400 = string, 500 = error_message). */
export function errorMessage(error: unknown): string {
if (typeof error === "string") return error;
if (error && typeof error === "object" && "msg" in error)
return String((error as { msg?: unknown }).msg ?? "Unknown error");
return "Upload failed";
}
export type UploadSnackbar = {
open: boolean;
success: boolean;
message: string;
onClose: () => void;
};
/**
* Wraps a generated hey-api mutation (e.g. putConfigFileWriterMutation()) and
* surfaces the result as a snackbar. submit(body) sends the typed body; on
* failure the server's error message is shown.
*/
export function useUpload<TError, TVars extends { body?: unknown }>(
mutationOptions: UseMutationOptions<unknown, TError, TVars>,
) {
const mutation = useMutation(mutationOptions);
const [open, setOpen] = useState(false);
const submit = (body: TVars["body"]) =>
mutation.mutate({ body } as TVars, { onSettled: () => setOpen(true) });
const snackbar: UploadSnackbar = {
open,
success: mutation.isSuccess,
message: mutation.error ? errorMessage(mutation.error) : "",
onClose: () => setOpen(false),
};
return { submit, pending: mutation.isPending, snackbar };
}