refactor(forms): update form submission methods
This commit is contained in:
3 files changed
+69
-26
No files matched your search
@@ -18,7 +18,7 @@ import { findBestIconMatch } from "./icon-matcher";
|
||||
type FormType = z.infer<typeof appManageSchema>;
|
||||
|
||||
export interface AppFormHandle {
|
||||
submit: () => void;
|
||||
submit: () => Promise<boolean>;
|
||||
isDirty: () => boolean;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ interface AppFormProps {
|
||||
submitAndCreateAnother?: string;
|
||||
};
|
||||
initialValues?: FormType;
|
||||
handleSubmit: (values: FormType, redirect: boolean, afterSuccess?: () => void) => void;
|
||||
handleSubmit: (values: FormType, redirect: boolean, afterSuccess?: () => void) => void | Promise<void>;
|
||||
isPending: boolean;
|
||||
hideButtons?: boolean;
|
||||
formRef?: React.Ref<AppFormHandle>;
|
||||
@@ -74,7 +74,20 @@ export const AppForm = ({
|
||||
useImperativeHandle(
|
||||
formRef,
|
||||
() => ({
|
||||
submit: () => form.onSubmit(handleSubmit)(),
|
||||
submit: () =>
|
||||
new Promise<boolean>((resolve) => {
|
||||
form.onSubmit(
|
||||
async (values) => {
|
||||
try {
|
||||
await Promise.resolve(handleSubmit(values));
|
||||
resolve(true);
|
||||
} catch {
|
||||
resolve(false);
|
||||
}
|
||||
},
|
||||
() => resolve(false),
|
||||
)();
|
||||
}),
|
||||
isDirty: () => form.isDirty(),
|
||||
}),
|
||||
[form, handleSubmit],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useImperativeHandle, useRef } from "react";
|
||||
import { Alert, Center, Loader, Stack } from "@mantine/core";
|
||||
import { Alert, Button, Center, Loader, Stack, Text } from "@mantine/core";
|
||||
import { IconInfoCircle } from "@tabler/icons-react";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
@@ -11,7 +11,7 @@ import { showErrorNotification, showSuccessNotification } from "@homarr/notifica
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
export interface EmbeddedAppEditFormHandle {
|
||||
submitIfDirty: () => void;
|
||||
submitIfDirty: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
interface EmbeddedAppEditFormProps {
|
||||
@@ -22,9 +22,9 @@ interface EmbeddedAppEditFormProps {
|
||||
export const EmbeddedAppEditForm = ({ appId, handleRef }: EmbeddedAppEditFormProps) => {
|
||||
const t = useI18n();
|
||||
const appFormRef = useRef<AppFormHandle>(null);
|
||||
const { data: app, isPending: isLoadingApp } = clientApi.app.byId.useQuery({ id: appId });
|
||||
const { data: app, isPending: isLoadingApp, isError, refetch } = clientApi.app.byId.useQuery({ id: appId });
|
||||
|
||||
const { mutate, isPending: isMutating } = clientApi.app.update.useMutation({
|
||||
const { mutateAsync, isPending: isMutating } = clientApi.app.update.useMutation({
|
||||
onSuccess: () => {
|
||||
showSuccessNotification({
|
||||
title: t("app.page.edit.notification.success.title"),
|
||||
@@ -42,16 +42,18 @@ export const EmbeddedAppEditForm = ({ appId, handleRef }: EmbeddedAppEditFormPro
|
||||
useImperativeHandle(
|
||||
handleRef,
|
||||
() => ({
|
||||
submitIfDirty: () => {
|
||||
if (appFormRef.current?.isDirty()) {
|
||||
appFormRef.current.submit();
|
||||
submitIfDirty: async () => {
|
||||
if (!appFormRef.current?.isDirty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return appFormRef.current.submit();
|
||||
},
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
if (isLoadingApp || !app) {
|
||||
if (isLoadingApp) {
|
||||
return (
|
||||
<Center py="xl">
|
||||
<Loader />
|
||||
@@ -59,6 +61,17 @@ export const EmbeddedAppEditForm = ({ appId, handleRef }: EmbeddedAppEditFormPro
|
||||
);
|
||||
}
|
||||
|
||||
if (isError || !app) {
|
||||
return (
|
||||
<Stack align="center" gap="md" py="xl">
|
||||
<Text c="dimmed">{t("app.action.select.notFound")}</Text>
|
||||
<Button variant="light" onClick={() => void refetch()}>
|
||||
{t("common.action.tryAgain")}
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Alert icon={<IconInfoCircle size={16} />} color="blue" variant="light">
|
||||
@@ -70,8 +83,8 @@ export const EmbeddedAppEditForm = ({ appId, handleRef }: EmbeddedAppEditFormPro
|
||||
showBackToOverview={false}
|
||||
buttonLabels={{ submit: "" }}
|
||||
initialValues={app}
|
||||
handleSubmit={(values) => {
|
||||
mutate({ id: appId, ...values });
|
||||
handleSubmit={async (values) => {
|
||||
await mutateAsync({ id: appId, ...values });
|
||||
}}
|
||||
isPending={isMutating}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useRef, useState } from "react";
|
||||
import { useRef, useState, type FormEvent } from "react";
|
||||
import { Button, Group, Stack, Tabs } from "@mantine/core";
|
||||
import { schemaResolver } from "@mantine/form";
|
||||
import { z } from "zod/v4";
|
||||
@@ -45,6 +45,7 @@ export const WidgetEditModal = createModal<ModalProps<WidgetKind>>(({ actions, i
|
||||
const { data: session } = useSession();
|
||||
const [advancedOptions, setAdvancedOptions] = useState<BoardItemAdvancedOptions>(innerProps.value.advancedOptions);
|
||||
const appEditRef = useRef<EmbeddedAppEditFormHandle>(null);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
z.config({
|
||||
customError: zodErrorMap(t),
|
||||
@@ -86,15 +87,29 @@ export const WidgetEditModal = createModal<ModalProps<WidgetKind>>(({ actions, i
|
||||
const appId = innerProps.appId;
|
||||
const showAppTab = innerProps.kind === "app" && canModifyApps && Boolean(appId);
|
||||
|
||||
const handleSubmit = form.onSubmit((values) => {
|
||||
innerProps.onSuccessfulEdit({
|
||||
...values,
|
||||
advancedOptions,
|
||||
});
|
||||
appEditRef.current?.submitIfDirty();
|
||||
actions.closeModal();
|
||||
const handleSubmit = form.onSubmit(async (values) => {
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
const appSaved = showAppTab ? await (appEditRef.current?.submitIfDirty() ?? true) : true;
|
||||
|
||||
if (!appSaved) {
|
||||
return;
|
||||
}
|
||||
|
||||
innerProps.onSuccessfulEdit({
|
||||
...values,
|
||||
advancedOptions,
|
||||
});
|
||||
actions.closeModal();
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
});
|
||||
|
||||
const onFormSubmit = (event: FormEvent<HTMLFormElement>) => {
|
||||
handleSubmit(event);
|
||||
};
|
||||
|
||||
const widgetFormContent = (
|
||||
<Stack>
|
||||
{innerProps.integrationSupport && (
|
||||
@@ -108,9 +123,7 @@ export const WidgetEditModal = createModal<ModalProps<WidgetKind>>(({ actions, i
|
||||
const Input = getInputForType(value.type);
|
||||
|
||||
if (
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
!Input ||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
value.shouldHide?.(
|
||||
form.values.options as never,
|
||||
innerProps.integrationData
|
||||
@@ -154,7 +167,9 @@ export const WidgetEditModal = createModal<ModalProps<WidgetKind>>(({ actions, i
|
||||
<Button onClick={actions.closeModal} variant="subtle" color="gray">
|
||||
{t("common.action.cancel")}
|
||||
</Button>
|
||||
<Button type="submit">{t("common.action.saveChanges")}</Button>
|
||||
<Button type="submit" loading={isSubmitting}>
|
||||
{t("common.action.saveChanges")}
|
||||
</Button>
|
||||
</Group>
|
||||
)}
|
||||
</Group>
|
||||
@@ -162,7 +177,7 @@ export const WidgetEditModal = createModal<ModalProps<WidgetKind>>(({ actions, i
|
||||
);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<form onSubmit={onFormSubmit}>
|
||||
<FormProvider form={form}>
|
||||
{showAppTab ? (
|
||||
<Stack>
|
||||
@@ -182,7 +197,9 @@ export const WidgetEditModal = createModal<ModalProps<WidgetKind>>(({ actions, i
|
||||
<Button onClick={actions.closeModal} variant="subtle" color="gray">
|
||||
{t("common.action.cancel")}
|
||||
</Button>
|
||||
<Button type="submit">{t("common.action.saveChanges")}</Button>
|
||||
<Button type="submit" loading={isSubmitting}>
|
||||
{t("common.action.saveChanges")}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
) : (
|
||||
|
||||
Reference in new issue
Block a user