"use client"; import { useRouter } from "next/navigation"; import { useEffect, useMemo, useState } from "react"; import { clientApi } from "@homarr/api/client"; import { revalidatePathActionAsync } from "@homarr/common/client"; import { applyCustomWidgetSourceSetup, getCustomWidgetSourceSetups, getImportReview, } from "@homarr/custom-widgets/core"; import type { CustomWidgetSecretKind, HomarrCustomWidgetV2 } from "@homarr/custom-widgets/core"; import { createCustomWidgetSourceSetupValues, CustomWidgetSourceSetupPanel, ImportReviewDialog, isCustomWidgetSourceSetupReady, } from "@homarr/custom-widgets/workbench"; import type { CustomWidgetSourceSetupValue } from "@homarr/custom-widgets/workbench"; import { showErrorNotification, showSuccessNotification } from "@homarr/notifications"; import { useScopedI18n } from "@homarr/translation/client"; interface CustomWidgetImportDialogProps { opened: boolean; widget: HomarrCustomWidgetV2 | null; stackId?: string; zIndex?: number; onClose(): void; onImported?(result: { id: string }): void; } export function CustomWidgetImportDialog({ opened, widget, stackId, zIndex, onClose, onImported, }: CustomWidgetImportDialogProps) { const t = useScopedI18n("customWidget"); const router = useRouter(); const utils = clientApi.useUtils(); const setups = useMemo(() => getCustomWidgetSourceSetups(widget?.sources ?? {}), [widget]); const [values, setValues] = useState>({}); const configuredWidget = useMemo(() => { if (!widget) return null; return { ...widget, sources: applyCustomWidgetSourceSetup( widget.sources, Object.fromEntries( Object.entries(values).map(([sourceId, value]) => [ sourceId, { baseUrl: value.baseUrl, networkScope: value.networkScope }, ]), ), ), }; }, [values, widget]); const review = useMemo(() => getImportReview(configuredWidget), [configuredWidget]); const mutation = clientApi.customWidget.import.useMutation({ onSuccess: (result) => { showSuccessNotification({ title: t("action.import"), message: t("notification.imported") }); void utils.customWidget.list.invalidate(); void utils.customWidget.available.invalidate(); void revalidatePathActionAsync("/manage/custom-widgets").then(() => router.refresh()); onImported?.(result); onClose(); }, onError: (error) => { showErrorNotification({ title: t("action.import"), message: error.message || t("notification.importError") }); }, }); useEffect(() => { if (opened) setValues(createCustomWidgetSourceSetupValues(setups)); }, [opened, setups]); const importWidget = () => { if (!configuredWidget) return; const secrets = Object.entries(values).flatMap(([sourceId, value]) => Object.entries(value.secrets).flatMap(([kind, secret]) => secret?.trim() ? [{ sourceId, kind: kind as CustomWidgetSecretKind, value: secret }] : [], ), ); mutation.mutate({ widget: configuredWidget, secrets }); }; return ( t(`preview.request.permission.${permission}` as never), }} > setValues((current) => ({ ...current, [sourceId]: value }))} messages={{ title: t("importReview.sourceSetup.title"), description: t("importReview.sourceSetup.description"), suggestedUrl: t("importReview.sourceSetup.suggestedUrl"), baseUrl: t("workbench.sources.baseUrl"), networkScope: t("workbench.sources.networkScope"), authentication: t("workbench.sources.authentication"), confirmUrl: t("importReview.sourceSetup.confirmUrl"), ready: t("importReview.sourceSetup.ready"), needsUrl: t("importReview.sourceSetup.needsUrl"), credentialsMissing: t("importReview.sourceSetup.credentialsMissing"), credentialsOptional: t("importReview.sourceSetup.credentialsOptional"), configured: t("workbench.sources.configured"), secret: (kind) => t(`importReview.credentials.field.${kind}`), urlError: (issue) => t(`workbench.sources.baseUrlError.${issue}`), }} /> ); }