From 7adc18b1ce64dc0e3656d07bb6e202ceb7fbc300 Mon Sep 17 00:00:00 2001 From: ajnart Date: Sat, 8 Aug 2026 16:02:40 +0200 Subject: [PATCH] fix: avoid nested board settings forms --- .../boards/[name]/settings/_settings-form.tsx | 63 +++++++++++-------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/apps/nextjs/src/app/[locale]/boards/[name]/settings/_settings-form.tsx b/apps/nextjs/src/app/[locale]/boards/[name]/settings/_settings-form.tsx index c27f77c71..e7bd4c553 100644 --- a/apps/nextjs/src/app/[locale]/boards/[name]/settings/_settings-form.tsx +++ b/apps/nextjs/src/app/[locale]/boards/[name]/settings/_settings-form.tsx @@ -36,6 +36,11 @@ const boardSettingsFormSchema = boardSavePartialSettingsSchema export type FormValues = z.infer; +const normalizeMobileLayoutGutters = (layouts: readonly FormValues["layouts"][number][]) => + layouts.map((layout) => + layout.role === "mobile" ? { ...layout, leftGutterColumnCount: 0, rightGutterColumnCount: 0 } : layout, + ); + const PARTIAL_FORM_KEYS = [ "pageTitle", "metaTitle", @@ -70,7 +75,7 @@ const buildInitialValues = (board: Board): FormValues => ({ itemRadius: board.itemRadius, customCss: board.customCss ?? "", disableStatus: board.disableStatus, - layouts: board.layouts, + layouts: normalizeMobileLayoutGutters(board.layouts), }); interface BoardSettingsFormProps { @@ -122,46 +127,45 @@ export const BoardSettingsForm = ({ board, permissions, hasFullAccess, hideVisib return () => window.removeEventListener("beforeunload", handler); }, []); - const handleSubmitAsync = async (values: FormValues) => { + const saveSettingsAsync = async (values: FormValues): Promise => { const defaults = initialValuesRef.current; const changed = (...fields: TKey[]) => fields.some((field) => values[field] !== defaults[field]); - const { layouts, ...partialSettings } = values; - - const saveActions: { when: boolean; action: () => Promise }[] = [ - { - when: changed(...PARTIAL_FORM_KEYS), - action: () => - savePartialSettings.mutateAsync({ id: board.id, ...partialSettings }).then(() => { - updateFavicon(values.faviconImageUrl ?? homarrLogoPath); - }), - }, - { - when: changed("layouts"), - action: () => saveLayouts.mutateAsync({ id: board.id, layouts }), - }, - ]; - - const promises = saveActions.filter((saveAction) => saveAction.when).map((saveAction) => saveAction.action()); - if (promises.length === 0) return; + const { layouts: submittedLayouts, ...partialSettings } = values; + const layouts = normalizeMobileLayoutGutters(submittedLayouts); + const partialSettingsChanged = changed(...PARTIAL_FORM_KEYS); + const layoutsChanged = changed("layouts"); + if (!partialSettingsChanged && !layoutsChanged) return values; try { - await Promise.all(promises); - lastSavedRef.current = { pageTitle: values.pageTitle, logoImageUrl: values.logoImageUrl }; - initialValuesRef.current = values; - form.setInitialValues(values); - form.resetDirty(); + const [, canonicalLayouts] = await Promise.all([ + partialSettingsChanged + ? savePartialSettings.mutateAsync({ id: board.id, ...partialSettings }).then(() => { + updateFavicon(values.faviconImageUrl ?? homarrLogoPath); + }) + : Promise.resolve(), + layoutsChanged ? saveLayouts.mutateAsync({ id: board.id, layouts }) : Promise.resolve(null), + ]); + const canonicalValues = canonicalLayouts ? { ...values, layouts: canonicalLayouts } : values; + + lastSavedRef.current = { pageTitle: canonicalValues.pageTitle, logoImageUrl: canonicalValues.logoImageUrl }; + initialValuesRef.current = canonicalValues; + form.setValues(canonicalValues); + form.setInitialValues(canonicalValues); + form.resetDirty(canonicalValues); await revalidatePathActionAsync(`/boards/${board.name}/settings`); showSuccessNotification({ title: t("common.notification.update.success"), message: t("common.notification.update.success"), }); + return canonicalValues; } catch { showErrorNotification({ title: t("common.notification.update.error"), message: t("common.notification.update.error"), }); + return null; } }; @@ -179,10 +183,15 @@ export const BoardSettingsForm = ({ board, permissions, hasFullAccess, hideVisib return ( -
void handleSubmitAsync(values))}> + void saveSettingsAsync(values))}> - + saveSettingsAsync(form.values)} + />