From 8550cbfa2e9cc27e5721d9e1ff69e8b6c03dcaf0 Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Sat, 1 Mar 2025 19:12:20 +0100 Subject: [PATCH] feat(integrations): add checkbox to add app --- .../new/_integration-new-form.tsx | 86 ++++++++++++++++--- packages/translation/src/lang/en.json | 7 ++ 2 files changed, 83 insertions(+), 10 deletions(-) diff --git a/apps/nextjs/src/app/[locale]/manage/integrations/new/_integration-new-form.tsx b/apps/nextjs/src/app/[locale]/manage/integrations/new/_integration-new-form.tsx index cb286217e..ed93ba822 100644 --- a/apps/nextjs/src/app/[locale]/manage/integrations/new/_integration-new-form.tsx +++ b/apps/nextjs/src/app/[locale]/manage/integrations/new/_integration-new-form.tsx @@ -1,16 +1,27 @@ "use client"; -import { useCallback } from "react"; +import { useCallback, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; -import { Alert, Button, Checkbox, Fieldset, Group, SegmentedControl, Stack, Text, TextInput } from "@mantine/core"; +import { + Alert, + Button, + Checkbox, + Collapse, + Fieldset, + Group, + SegmentedControl, + Stack, + Text, + TextInput, +} from "@mantine/core"; import { IconInfoCircle } from "@tabler/icons-react"; -import type { z } from "zod"; +import { z } from "zod"; import { clientApi } from "@homarr/api/client"; import { revalidatePathActionAsync } from "@homarr/common/client"; import type { IntegrationKind, IntegrationSecretKind } from "@homarr/definitions"; -import { getAllSecretKindOptions, getIntegrationName, integrationDefs } from "@homarr/definitions"; +import { getAllSecretKindOptions, getIconUrl, getIntegrationName, integrationDefs } from "@homarr/definitions"; import type { UseFormReturnType } from "@homarr/form"; import { useZodForm } from "@homarr/form"; import { convertIntegrationTestConnectionError } from "@homarr/integrations/client"; @@ -26,11 +37,19 @@ interface NewIntegrationFormProps { }; } +const formSchema = validation.integration.create.omit({ kind: true }).and( + z.object({ + createApp: z.boolean(), + appHref: validation.app.manage.shape.href, + }), +); + export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) => { const t = useI18n(); const secretKinds = getAllSecretKindOptions(searchParams.kind); const router = useRouter(); - const form = useZodForm(validation.integration.create.omit({ kind: true }), { + const [opened, setOpened] = useState(false); + const form = useZodForm(formSchema, { initialValues: { name: searchParams.name ?? getIntegrationName(searchParams.kind), url: searchParams.url ?? "", @@ -39,23 +58,60 @@ export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) => value: "", })), attemptSearchEngineCreation: true, + createApp: false, + appHref: "", + }, + onValuesChange(values, previous) { + if (values.createApp !== previous.createApp) { + setOpened(values.createApp); + } }, }); - const { mutateAsync, isPending } = clientApi.integration.create.useMutation(); + + const { mutateAsync: createIntegrationAsync, isPending: isPendingIntegration } = + clientApi.integration.create.useMutation(); + const { mutateAsync: createAppAsync, isPending: isPendingApp } = clientApi.app.create.useMutation(); + const isPending = isPendingIntegration || isPendingApp; const handleSubmitAsync = async (values: FormType) => { - await mutateAsync( + await createIntegrationAsync( { kind: searchParams.kind, ...values, }, { - onSuccess: () => { + async onSuccess() { showSuccessNotification({ title: t("integration.page.create.notification.success.title"), message: t("integration.page.create.notification.success.message"), }); - void revalidatePathActionAsync("/manage/integrations").then(() => router.push("/manage/integrations")); + + if (!values.createApp) { + await revalidatePathActionAsync("/manage/integrations").then(() => router.push("/manage/integrations")); + return; + } + + const hasCustomHref = values.appHref !== null && values.appHref.trim().length >= 1; + await createAppAsync( + { + name: values.name, + href: hasCustomHref ? values.appHref : values.url, + iconUrl: getIconUrl(searchParams.kind), + description: null, + pingUrl: values.url, + }, + { + async onSettled() { + await revalidatePathActionAsync("/manage/integrations").then(() => router.push("/manage/integrations")); + }, + onError() { + showErrorNotification({ + title: t("app.page.create.notification.error.title"), + message: t("app.page.create.notification.error.message"), + }); + }, + }, + ); }, onError: (error) => { const testConnectionError = convertIntegrationTestConnectionError(error.data?.error); @@ -117,6 +173,16 @@ export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) => /> )} + + + + + +