"use client"; import type { ChangeEventHandler } from "react"; import { useEffect, useRef } from "react"; import Link from "next/link"; import { Button, Checkbox, Collapse, Group, Stack, Textarea, TextInput } from "@mantine/core"; import { useDebouncedValue, useDisclosure } from "@mantine/hooks"; import type { z } from "zod"; import { clientApi } from "@homarr/api/client"; import { useZodForm } from "@homarr/form"; import { useI18n } from "@homarr/translation/client"; import { appManageSchema } from "@homarr/validation/app"; import { IconPicker } from "../icon-picker/icon-picker"; import { findBestIconMatch } from "./icon-matcher"; type FormType = z.infer; interface AppFormProps { showBackToOverview: boolean; buttonLabels: { submit: string; submitAndCreateAnother?: string; }; initialValues?: FormType; handleSubmit: (values: FormType, redirect: boolean, afterSuccess?: () => void) => void; isPending: boolean; } export const AppForm = ({ buttonLabels, showBackToOverview, handleSubmit: originalHandleSubmit, initialValues, isPending, }: AppFormProps) => { const t = useI18n(); const form = useZodForm(appManageSchema, { initialValues: { name: initialValues?.name ?? "", description: initialValues?.description ?? "", iconUrl: initialValues?.iconUrl ?? "", href: initialValues?.href ?? "", pingUrl: initialValues?.pingUrl ?? "", }, }); // Debounce the name value with 200ms delay const [debouncedName] = useDebouncedValue(form.values.name, 200); const shouldCreateAnother = useRef(false); const handleSubmit = (values: FormType) => { const redirect = !shouldCreateAnother.current; const afterSuccess = shouldCreateAnother.current ? () => { form.reset(); shouldCreateAnother.current = false; } : undefined; originalHandleSubmit(values, redirect, afterSuccess); }; const [opened, { open, close }] = useDisclosure((initialValues?.pingUrl?.length ?? 0) > 0); const handleClickDifferentUrlPing: ChangeEventHandler = () => { if (!opened) { open(); } else { close(); form.setFieldValue("pingUrl", ""); } }; // Auto-select icon based on app name with debounced search const { data: iconsData } = clientApi.icon.findIcons.useQuery( { searchText: debouncedName, }, { enabled: debouncedName.length > 3, }, ); useEffect(() => { if (debouncedName && !form.values.iconUrl && iconsData?.icons) { const bestMatch = findBestIconMatch(debouncedName, iconsData.icons); if (bestMatch) { form.setFieldValue("iconUrl", bestMatch); } } }, [debouncedName, iconsData]); return (