"use client"; import { useState, useRef, useEffect, useCallback } from "react"; import { useTranslations } from "next-intl"; import { Button } from "@/components/ui/button"; import { X, Loader2, Globe } from "lucide-react"; import type { IJMAPClient } from '@/lib/jmap/client-interface'; import { useCalendarStore, type ICalSubscription } from "@/stores/calendar-store"; import { CalendarColorPicker } from "@/components/settings/calendar-management-settings"; import { toast } from "@/stores/toast-store"; interface ICalSubscriptionModalProps { client: IJMAPClient; onClose: () => void; editSubscription?: ICalSubscription; initialUrl?: string; initialName?: string; } export function ICalSubscriptionModal({ client, onClose, editSubscription, initialUrl, initialName }: ICalSubscriptionModalProps) { const t = useTranslations("calendar.subscription"); const tCommon = useTranslations("common"); const addICalSubscription = useCalendarStore((s) => s.addICalSubscription); const updateICalSubscription = useCalendarStore((s) => s.updateICalSubscription); const isEdit = !!editSubscription; const [url, setUrl] = useState(editSubscription?.url || initialUrl || ""); const [name, setName] = useState(editSubscription?.name || initialName || ""); const [color, setColor] = useState(editSubscription?.color || "#3b82f6"); const [refreshInterval, setRefreshInterval] = useState(editSubscription?.refreshInterval || 60); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(null); const modalRef = useRef(null); const isValid = url.trim().length > 0 && name.trim().length > 0; const handleSubmit = useCallback(async () => { let trimmedUrl = url.trim(); if (!trimmedUrl || !name.trim()) return; // Convert webcal:// to https:// if (trimmedUrl.startsWith("webcal://")) { trimmedUrl = trimmedUrl.replace(/^webcal:\/\//, "https://"); } try { new URL(trimmedUrl); } catch { setError(t("invalid_url")); return; } setError(null); setIsSubmitting(true); try { if (isEdit && editSubscription) { const updates: { url?: string; name?: string; color?: string; refreshInterval?: number } = {}; if (trimmedUrl !== editSubscription.url) updates.url = trimmedUrl; if (name.trim() !== editSubscription.name) updates.name = name.trim(); if (color !== editSubscription.color) updates.color = color; if (refreshInterval !== editSubscription.refreshInterval) updates.refreshInterval = refreshInterval; await updateICalSubscription(client, editSubscription.id, updates); toast.success(t("updated", { name: name.trim() })); onClose(); } else { const subscription = await addICalSubscription(client, trimmedUrl, name.trim(), color, refreshInterval); if (subscription) { toast.success(t("success", { name: name.trim() })); onClose(); } else { setError(t("error")); } } } catch { setError(isEdit ? t("update_error") : t("error")); } finally { setIsSubmitting(false); } }, [url, name, color, refreshInterval, client, isEdit, editSubscription, addICalSubscription, updateICalSubscription, onClose, t]); useEffect(() => { const handleKey = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", handleKey); return () => window.removeEventListener("keydown", handleKey); }, [onClose]); useEffect(() => { const modal = modalRef.current; if (!modal) return; const focusableEls = modal.querySelectorAll( 'input, select, textarea, button, [tabindex]:not([tabindex="-1"])' ); const firstEl = focusableEls[0]; const lastEl = focusableEls[focusableEls.length - 1]; const handler = (e: KeyboardEvent) => { if (e.key !== "Tab") return; if (e.shiftKey && document.activeElement === firstEl) { e.preventDefault(); lastEl?.focus(); } else if (!e.shiftKey && document.activeElement === lastEl) { e.preventDefault(); firstEl?.focus(); } }; modal.addEventListener("keydown", handler); firstEl?.focus(); return () => modal.removeEventListener("keydown", handler); }, []); return (
); }