'use client'; import Link from 'next/link'; import { useEffect, useMemo, useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Expand, Loader2, Pencil, Plus, Save, X } from 'lucide-react'; import { toast } from 'sonner'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card'; import { Label } from '@/components/ui/label'; import { Switch } from '@/components/ui/switch'; import { effectiveOfflinePageHTML } from '@/lib/openflare/offline-page-templates'; import { OptionService, ZoneService, zoneQueryKey, } from '@/lib/services/openflare'; import { useTranslations } from 'next-intl'; import { ScopeDomainDialog } from './scope-domain-dialog'; import { defaultOfflinePageFields, invalidateResponseQueries, KEY_SW_DOMAINS, KEY_SW_ENABLED, KEY_SW_HTML, mapOptionsToOfflineFields, type OfflinePageFields, } from './shared'; export function OfflinePageTab({ optionMap, }: { optionMap: Record; }) { const t = useTranslations('responses'); const tc = useTranslations('common'); const queryClient = useQueryClient(); const [fields, setFields] = useState( defaultOfflinePageFields, ); const [scopeOpen, setScopeOpen] = useState(false); useEffect(() => { setFields(mapOptionsToOfflineFields(optionMap)); }, [optionMap]); const previewSrcDoc = useMemo( () => effectiveOfflinePageHTML(fields.html), [fields.html], ); const zonesQuery = useQuery({ queryKey: [...zoneQueryKey, 'sw-scope'], queryFn: async () => { const zones = await ZoneService.list(); const overviews = await Promise.all( zones.map((zone) => ZoneService.getOverview(zone.id)), ); return overviews.map((ov) => ({ zoneDomain: ov.zone.domain, domains: [ov.zone.domain, ...ov.domains.map((d) => d.domain)], })); }, }); const saveMutation = useMutation({ mutationFn: async () => { await OptionService.updateBatch([ { key: KEY_SW_ENABLED, value: String(fields.enabled) }, { key: KEY_SW_HTML, value: fields.html }, { key: KEY_SW_DOMAINS, value: JSON.stringify(fields.domains) }, ]); }, onSuccess: async () => { toast.success(t('offlineSaved')); await invalidateResponseQueries(queryClient); }, onError: (error) => { toast.error(error instanceof Error ? error.message : t('saveFailed')); }, }); return (
{t('offlinePage')} {t('offlinePageDesc')}

{t('enableOfflineDesc')}

setFields((prev) => ({ ...prev, enabled })) } aria-label={t('enableOfflineAria')} className='mt-0.5 shrink-0' />

{t('scopeDesc')}

{fields.domains.length === 0 ? (

{fields.enabled ? t('noScopeSelected') : t('enableThenSelectScope')}

) : (
{fields.domains.map((domain) => ( {domain} ))}
)}
{t('pagePreview')}