"use client"; import { useState, useRef } from "react"; import type { Service, ServiceUrlInput, ServiceRequest } 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 { Plus, Trash2, Upload, Star } from "lucide-react"; import { useCreateService, useUpdateService, useUploadIcon } from "@/lib/api/hooks"; interface ServiceFormProps { service?: Service | null; groups: { id: string; name: string }[]; open: boolean; onOpenChange: (open: boolean) => void; } const EMPTY_URL: ServiceUrlInput = { label: "", kind: "local", url: "", isPrimary: false }; export function ServiceForm({ service, groups, open, onOpenChange }: ServiceFormProps) { const isEdit = !!service; const createMut = useCreateService(); const updateMut = useUpdateService(); const uploadMut = useUploadIcon(); const [name, setName] = useState(service?.name || ""); const [groupId, setGroupId] = useState(service?.groupId || null); const [iconUrl, setIconUrl] = useState(service?.iconUrl || ""); const [iconAssetId, setIconAssetId] = useState(service?.iconAssetId || null); const [iconMode, setIconMode] = useState<"url" | "upload">("url"); const [urls, setUrls] = useState( service?.urls?.map((u) => ({ id: u.id, label: u.label, kind: u.kind, url: u.url, isPrimary: u.isPrimary })) || [{ ...EMPTY_URL, isPrimary: true }], ); const [errors, setErrors] = useState>({}); const fileRef = useRef(null); const addUrl = () => setUrls((prev) => [...prev, { ...EMPTY_URL }]); const removeUrl = (idx: number) => setUrls((prev) => prev.filter((_, i) => i !== idx)); const updateUrl = (idx: number, field: keyof ServiceUrlInput, value: string | boolean) => { setUrls((prev) => { const next = [...prev]; next[idx] = { ...next[idx], [field]: value }; if (field === "isPrimary" && value === true) { next.forEach((u, i) => { if (i !== idx) u.isPrimary = false; }); } return next; }); }; const handleFileUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; try { const asset = await uploadMut.mutateAsync(file); setIconAssetId(asset.id); setIconUrl(""); } catch { setErrors((prev) => ({ ...prev, icon: "Upload failed" })); } }; const validate = (): boolean => { const e: Record = {}; if (!name.trim()) e.name = "Name is required"; if (urls.length === 0) e.urls = "At least one URL is required"; urls.forEach((u, i) => { if (!u.label.trim()) e[`url-label-${i}`] = "Label required"; if (!u.url.trim()) e[`url-${i}`] = "URL required"; else if (!/^https?:\/\//.test(u.url)) e[`url-${i}`] = "Must be http(s)"; }); setErrors(e); return Object.keys(e).length === 0; }; const handleSubmit = async () => { if (!validate()) return; const body: ServiceRequest = { name: name.trim(), groupId, iconUrl: iconMode === "url" && iconUrl ? iconUrl : null, iconAssetId: iconMode === "upload" && iconAssetId ? iconAssetId : null, urls: urls.map((u) => ({ label: u.label.trim(), kind: u.kind, url: u.url.trim(), isPrimary: u.isPrimary })), }; try { if (isEdit && service) { await updateMut.mutateAsync({ id: service.id, ...body }); } else { await createMut.mutateAsync(body); } onOpenChange(false); } catch (err) { setErrors({ submit: err instanceof Error ? err.message : "Failed" }); } }; return ( {isEdit ? "Edit App" : "Add App"} {isEdit ? "Update app details" : "Add a new app to your dashboard"}
setName(e.target.value)} placeholder="Jellyfin" /> {errors.name && {errors.name}}
{iconMode === "url" ? ( setIconUrl(e.target.value)} placeholder="https://example.com/icon.png" /> ) : (
{iconAssetId && Uploaded}
)} {errors.icon && {errors.icon}}
{urls.map((u, i) => (
updateUrl(i, "label", e.target.value)} placeholder="Label" />
updateUrl(i, "url", e.target.value)} placeholder="https://" />
{errors[`url-label-${i}`] && {errors[`url-label-${i}`]}} {errors[`url-${i}`] && {errors[`url-${i}`]}}
))}
{errors.submit && {errors.submit}}
); }