import { Button } from "@/components/ui/button"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { useTranslation } from "react-i18next"; import { useState, useCallback, useMemo } from "react"; import { LuEye, LuEyeOff } from "react-icons/lu"; import useSWR from "swr"; import { FrigateConfig } from "@/types/frigateConfig"; import { WizardFormData, CameraBrand, CAMERA_BRANDS, CAMERA_BRAND_VALUES, } from "@/types/cameraWizard"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { LuInfo } from "react-icons/lu"; type Step1NameCameraProps = { wizardData: Partial; onUpdate: (data: Partial) => void; onNext: (data?: Partial) => void; onCancel: () => void; canProceed?: boolean; }; export default function Step1NameCamera({ wizardData, onUpdate, onNext, onCancel, }: Step1NameCameraProps) { const { t } = useTranslation(["views/settings"]); const { data: config } = useSWR("config"); const [showPassword, setShowPassword] = useState(false); const [probeMode, setProbeMode] = useState( wizardData.probeMode ?? true, ); const existingCameraNames = useMemo(() => { if (!config?.cameras) { return []; } return Object.keys(config.cameras); }, [config]); const step1FormData = z .object({ cameraName: z .string() .min(1, t("cameraWizard.step1.errors.nameRequired")) .max(64, t("cameraWizard.step1.errors.nameLength")) .refine( (value) => !existingCameraNames.includes(value), t("cameraWizard.step1.errors.nameExists"), ), host: z.string().optional(), username: z.string().optional(), password: z.string().optional(), brandTemplate: z.enum(CAMERA_BRAND_VALUES).optional(), onvifPort: z.coerce.number().int().min(1).max(65535).optional(), customUrl: z .string() .optional() .refine( (val) => !val || val.startsWith("rtsp://"), t("cameraWizard.step1.errors.customUrlRtspRequired"), ), }) .refine( (data) => { // If brand is "other", customUrl is required if (data.brandTemplate === "other") { return data.customUrl && data.customUrl.trim().length > 0; } // If brand is not "other", host is required return data.host && data.host.trim().length > 0; }, { message: t("cameraWizard.step1.errors.brandOrCustomUrlRequired"), path: ["customUrl"], }, ); const form = useForm>({ resolver: zodResolver(step1FormData), defaultValues: { cameraName: wizardData.cameraName || "", host: wizardData.host || "", username: wizardData.username || "", password: wizardData.password || "", brandTemplate: wizardData.brandTemplate && CAMERA_BRAND_VALUES.includes(wizardData.brandTemplate as CameraBrand) ? (wizardData.brandTemplate as CameraBrand) : "dahua", customUrl: wizardData.customUrl || "", onvifPort: wizardData.onvifPort ?? 80, }, mode: "onChange", }); const watchedBrand = form.watch("brandTemplate"); const watchedHost = form.watch("host"); const watchedCustomUrl = form.watch("customUrl"); const hostPresent = !!(watchedHost && watchedHost.trim()); const customPresent = !!(watchedCustomUrl && watchedCustomUrl.trim()); const cameraNamePresent = !!(form.getValues().cameraName || "").trim(); const isContinueButtonEnabled = cameraNamePresent && (probeMode ? hostPresent : watchedBrand === "other" ? customPresent : hostPresent); const onSubmit = (data: z.infer) => { onUpdate({ ...data, probeMode }); }; const handleContinue = useCallback(async () => { const isValid = await form.trigger(); if (isValid) { const data = form.getValues(); onNext({ ...data, probeMode }); } }, [form, probeMode, onNext]); return (
{t("cameraWizard.step1.description")}
( {t("cameraWizard.step1.cameraName")} )} />
( {t("cameraWizard.step1.host")} )} /> ( {t("cameraWizard.step1.username")} )} /> ( {t("cameraWizard.step1.password")}
)} />
{t("cameraWizard.step1.detectionMethod")} { setProbeMode(value === "probe"); }} >
{t("cameraWizard.step1.detectionMethodDescription")}
{probeMode && ( ( {t("cameraWizard.step1.onvifPort")} {t("cameraWizard.step1.onvifPortDescription")} {fieldState.error ? fieldState.error.message : null} )} /> )} {!probeMode && (
(
{t("cameraWizard.step1.cameraBrand")} {field.value && (() => { const selectedBrand = CAMERA_BRANDS.find( (brand) => brand.value === field.value, ); return selectedBrand && selectedBrand.value != "other" ? (

{selectedBrand.label}

{t("cameraWizard.step1.brandUrlFormat", { exampleUrl: selectedBrand.exampleUrl, })}

) : null; })()}
)} /> {watchedBrand == "other" && ( ( {t("cameraWizard.step1.customUrl")} )} /> )}
)}
); }