'use client' import { useLocale, useTranslations } from 'next-intl' import { formatDateLong } from '@/lib/utils' import { useState, useEffect } from 'react' import { Switch } from '@/components/ui/switch' import { Button } from '@/components/ui/button' import { useToast } from '@/components/ui/use-toast' import { Calendar, Copy, RefreshCw, Loader2, Check } from 'lucide-react' import { DestructiveConfirmDialog, useDestructiveConfirm } from '@/components/ui/destructive-confirm-dialog' import { SettingsGroup, SettingsInput, SettingsRow, SettingsRowEnd, SettingsRowNote, } from '@/components/settings/SettingsRows' import type { CalendarFeed } from '@/types' interface CalendarFeedWithUrls extends CalendarFeed { webcalUrl: string httpsUrl: string } export function CalendarFeedSettings() { const t = useTranslations('settings_calendar_feed') const locale = useLocale() const { toast } = useToast() const [isLoading, setIsLoading] = useState(true) const [isSaving, setIsSaving] = useState(false) const [isRegenerating, setIsRegenerating] = useState(false) const [feed, setFeed] = useState(null) const [copied, setCopied] = useState(false) const { dialogProps: confirmDialogProps, confirm: confirmAction } = useDestructiveConfirm() useEffect(() => { fetchFeed() }, []) const fetchFeed = async () => { setIsLoading(true) const response = await fetch('/api/calendar/feed') const { data } = await response.json() setFeed(data) setIsLoading(false) } const createFeed = async () => { setIsSaving(true) try { const response = await fetch('/api/calendar/feed', { method: 'POST', }) if (!response.ok) { throw new Error('Failed to create feed') } const { data } = await response.json() setFeed(data) toast({ title: t('toast_feed_created_title'), description: t('toast_feed_created_description'), }) } catch { toast({ title: t('toast_create_failed'), variant: 'destructive', }) } finally { setIsSaving(false) } } const updateFeed = async (key: keyof CalendarFeed, value: boolean) => { if (!feed) return setIsSaving(true) try { const response = await fetch('/api/calendar/feed', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ [key]: value }), }) if (!response.ok) { throw new Error('Failed to update feed') } const { data } = await response.json() setFeed(data) } catch { toast({ title: t('toast_update_failed'), variant: 'destructive', }) } finally { setIsSaving(false) } } const regenerateToken = async () => { const ok = await confirmAction({ title: t('regen_dialog_title'), description: t('regen_dialog_description'), confirmLabel: t('regen_confirm'), variant: 'warning', }) if (!ok) return setIsRegenerating(true) try { const response = await fetch('/api/calendar/feed', { method: 'DELETE', }) if (!response.ok) { throw new Error('Failed to regenerate token') } const { data } = await response.json() setFeed(data) toast({ title: t('toast_new_link_title'), description: t('toast_new_link_description'), }) } catch { toast({ title: t('toast_regen_failed'), variant: 'destructive', }) } finally { setIsRegenerating(false) } } const copyToClipboard = async (text: string) => { try { await navigator.clipboard.writeText(text) setCopied(true) setTimeout(() => setCopied(false), 2000) toast({ title: t('toast_copied_title'), description: t('toast_copied_description'), }) } catch { toast({ title: t('toast_copy_failed'), variant: 'destructive', }) } } const openWebcal = () => { if (feed?.webcalUrl) { window.location.href = feed.webcalUrl } } if (isLoading) { return (
) } if (!feed) { return ( ) } return ( <> {/* Feed URL */} {/* Live feed stats stay visible: they are state, not instructions */} {feed.last_accessed_at && ( {t('last_fetched')}{' '} {formatDateLong(feed.last_accessed_at, locale)} {' ยท '} {t('times_count', { count: feed.access_count })} )} {/* Content settings */} updateFeed('include_tax_deadlines', checked) } disabled={isSaving} /> updateFeed('include_invoices', checked) } disabled={isSaving} /> ) }