diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx index 1d7f8aed8..ce6a24c28 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx @@ -19,6 +19,7 @@ import { } from "@tabler/icons-react"; import { clientApi } from "@homarr/api/client"; +import { useSession } from "@homarr/auth/client"; import { useRequiredBoard } from "@homarr/boards/context"; import { useEditMode } from "@homarr/boards/edit-mode"; import { revalidatePathActionAsync } from "@homarr/common/client"; @@ -62,6 +63,7 @@ export const BoardContentHeaderActions = () => { }; const AddMenu = () => { + const { data: session } = useSession(); const { openModal: openCategoryEditModal } = useModalAction(CategoryEditModal); const { openModal: openItemSelectModal } = useModalAction(ItemSelectModal); const { openModal: openAppSelectModal } = useModalAction(AppSelectModal); @@ -96,12 +98,13 @@ const AddMenu = () => { const handleSelectApp = useCallback(() => { openAppSelectModal({ - onSelect: (appId) => { + onSelect: (app) => { createItem({ kind: "app", - options: { appId }, + options: { appId: app.id }, }); }, + withCreate: session?.user.permissions.includes("app-create") ?? false, }); }, [openAppSelectModal, createItem]); diff --git a/apps/nextjs/src/app/[locale]/manage/integrations/edit/[id]/_integration-edit-form.tsx b/apps/nextjs/src/app/[locale]/manage/integrations/edit/[id]/_integration-edit-form.tsx index 8f1b576c9..9dd9dbceb 100644 --- a/apps/nextjs/src/app/[locale]/manage/integrations/edit/[id]/_integration-edit-form.tsx +++ b/apps/nextjs/src/app/[locale]/manage/integrations/edit/[id]/_integration-edit-form.tsx @@ -3,16 +3,18 @@ import { useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; -import { Alert, Button, Fieldset, Group, Stack, Text, TextInput } from "@mantine/core"; -import { IconInfoCircle } from "@tabler/icons-react"; -import type { z } from "zod/v4"; +import { Alert, Anchor, Button, ButtonGroup, Fieldset, Group, Stack, Text, TextInput } from "@mantine/core"; +import { IconInfoCircle, IconPencil, IconPlus, IconUnlink } from "@tabler/icons-react"; +import { z } from "zod/v4"; import type { RouterOutputs } from "@homarr/api"; import { clientApi } from "@homarr/api/client"; +import { useSession } from "@homarr/auth/client"; import { revalidatePathActionAsync } from "@homarr/common/client"; import { getAllSecretKindOptions, getDefaultSecretKinds } from "@homarr/definitions"; import { useZodForm } from "@homarr/form"; -import { useConfirmModal } from "@homarr/modals"; +import { useConfirmModal, useModalAction } from "@homarr/modals"; +import { AppSelectModal } from "@homarr/modals-collection"; import { showErrorNotification, showSuccessNotification } from "@homarr/notifications"; import { useI18n } from "@homarr/translation/client"; import { integrationUpdateSchema } from "@homarr/validation/integration"; @@ -27,6 +29,19 @@ interface EditIntegrationForm { integration: RouterOutputs["integration"]["byId"]; } +const formSchema = integrationUpdateSchema.omit({ id: true, appId: true }).and( + z.object({ + app: z + .object({ + id: z.string(), + name: z.string(), + iconUrl: z.string(), + href: z.string().nullable(), + }) + .nullable(), + }), +); + export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => { const t = useI18n(); const { openConfirmModal } = useConfirmModal(); @@ -40,7 +55,7 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => { const hasUrlSecret = initialSecretsKinds.includes("url"); const router = useRouter(); - const form = useZodForm(integrationUpdateSchema.omit({ id: true }), { + const form = useZodForm(formSchema, { initialValues: { name: integration.name, url: integration.url, @@ -48,6 +63,7 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => { kind, value: integration.secrets.find((secret) => secret.kind === kind)?.value ?? "", })), + app: integration.app ?? null, }, }); const { mutateAsync, isPending } = clientApi.integration.update.useMutation(); @@ -55,7 +71,7 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => { const secretsMap = new Map(integration.secrets.map((secret) => [secret.kind, secret])); - const handleSubmitAsync = async (values: FormType) => { + const handleSubmitAsync = async ({ app, ...values }: FormType) => { const url = hasUrlSecret ? new URL(values.secrets.find((secret) => secret.kind === "url")?.value ?? values.url).origin : values.url; @@ -68,6 +84,7 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => { kind: secret.kind, value: secret.value === "" ? null : secret.value, })), + appId: app?.id ?? null, }, { onSuccess: (data) => { @@ -102,7 +119,7 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => { form.values.secrets.length === initialSecretsKinds.length; return ( -