"use client"; import { useState } from "react"; import type { WidgetInstance, WidgetRequest } from "@/lib/api/schema"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { useCreateWidget, useUpdateWidget } from "@/lib/api/hooks"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Badge } from "@/components/ui/badge"; import { Check, ChevronsUpDown, X } from "lucide-react"; import { cn } from "@/lib/utils"; const POPULAR_TIMEZONES = [ "America/New_York", "America/Chicago", "America/Denver", "America/Los_Angeles", "America/Anchorage", "Pacific/Honolulu", "America/Sao_Paulo", "America/Argentina/Buenos_Aires", "Europe/London", "Europe/Paris", "Europe/Berlin", "Europe/Prague", "Europe/Moscow", "Asia/Dubai", "Asia/Kolkata", "Asia/Bangkok", "Asia/Shanghai", "Asia/Tokyo", "Asia/Seoul", "Australia/Sydney", "Australia/Melbourne", "Pacific/Auckland", "UTC", ]; const WIDGET_TYPES = ["clock", "image", "pihole", "memos", "immich"] as const; interface WidgetFormProps { widget?: WidgetInstance | null; open: boolean; onOpenChange: (open: boolean) => void; } export function WidgetForm({ widget, open, onOpenChange }: WidgetFormProps) { const isEdit = !!widget; const createMut = useCreateWidget(); const updateMut = useUpdateWidget(); const [type, setType] = useState(widget?.type || "clock"); const [title, setTitle] = useState(widget?.title || ""); const [enabled, setEnabled] = useState(widget?.enabled ?? true); const [selectedTzs, setSelectedTzs] = useState( (widget?.config?.timezones as string[]) || [], ); const [tzPopoverOpen, setTzPopoverOpen] = useState(false); const [imageUrl, setImageUrl] = useState((widget?.config?.imageUrl as string) || ""); const [linkUrl, setLinkUrl] = useState((widget?.config?.linkUrl as string) || ""); const [piholeBaseUrl, setPiholeBaseUrl] = useState((widget?.config?.baseUrl as string) || ""); const [piholeApiToken, setPiholeApiToken] = useState((widget?.config?.apiToken as string) || ""); const [memosBaseUrl, setMemosBaseUrl] = useState((widget?.config?.baseUrl as string) || ""); const [memosApiToken, setMemosApiToken] = useState((widget?.config?.apiToken as string) || ""); const [memosPageSize, setMemosPageSize] = useState(String((widget?.config?.pageSize as number) || 5)); const [immichBaseUrl, setImmichBaseUrl] = useState((widget?.config?.baseUrl as string) || ""); const [immichApiKey, setImmichApiKey] = useState((widget?.config?.apiKey as string) || ""); const [error, setError] = useState(""); const buildConfig = (): Record => { switch (type) { case "clock": return { timezones: selectedTzs }; case "image": return { imageUrl, linkUrl: linkUrl || null }; case "pihole": return { baseUrl: piholeBaseUrl, apiToken: piholeApiToken }; case "memos": return { baseUrl: memosBaseUrl, apiToken: memosApiToken, pageSize: parseInt(memosPageSize) || 5 }; case "immich": return { baseUrl: immichBaseUrl, apiKey: immichApiKey }; default: return {}; } }; const handleSubmit = async () => { if (!title.trim()) { setError("Title is required"); return; } if ((type === "pihole" || type === "memos") && !piholeBaseUrl && !memosBaseUrl) { setError("Base URL is required"); return; } if (type === "immich" && !immichBaseUrl) { setError("Base URL is required"); return; } if (type === "image" && !imageUrl) { setError("Image URL is required"); return; } const body: WidgetRequest = { type: type as WidgetRequest["type"], title: title.trim(), enabled, config: buildConfig() as WidgetRequest["config"], }; try { if (isEdit && widget) { await updateMut.mutateAsync({ id: widget.id, ...body }); } else { await createMut.mutateAsync(body); } onOpenChange(false); setError(""); } catch (err) { setError(err instanceof Error ? err.message : "Failed"); } }; return ( {isEdit ? "Edit Widget" : "Add Widget"} {isEdit ? "Update widget settings" : "Add a new widget to your dashboard"}
setTitle(e.target.value)} placeholder="My Widget" />
{type === "clock" && (
{selectedTzs.map((tz) => ( {tz.split("/").pop()?.replace("_", " ")} ))}
No timezone found. {POPULAR_TIMEZONES.filter((tz) => !selectedTzs.includes(tz)).map((tz) => ( { setSelectedTzs((prev) => [...prev, tz]); setTzPopoverOpen(false); }} > {tz} ))}
)} {type === "image" && ( <>
setImageUrl(e.target.value)} placeholder="https://example.com/image.jpg" />
setLinkUrl(e.target.value)} placeholder="https://example.com" />
)} {type === "pihole" && ( <>
setPiholeBaseUrl(e.target.value)} placeholder="http://pihole.local" />
setPiholeApiToken(e.target.value)} />
)} {type === "memos" && ( <>
setMemosBaseUrl(e.target.value)} placeholder="http://memos.local:5230" />
setMemosApiToken(e.target.value)} />
setMemosPageSize(e.target.value)} min={1} max={20} />
)} {type === "immich" && ( <>
setImmichBaseUrl(e.target.value)} placeholder="http://immich.local:2283" />
setImmichApiKey(e.target.value)} />
)} {error && {error}}
); }