import { useState } from 'react' import StyledButton from '~/components/StyledButton' import StyledTable from '~/components/StyledTable' import StyledSectionHeader from '~/components/StyledSectionHeader' import ActiveDownloads from '~/components/ActiveDownloads' import Alert from '~/components/Alert' import type { ContentUpdateCheckResult, ResourceUpdateInfo } from '../../../types/collections' import api from '~/lib/api' import { useQueryClient } from '@tanstack/react-query' import { useNotifications } from '~/context/NotificationContext' import { formatBytes } from '~/lib/util' export default function ContentUpdatesSection() { const { addNotification } = useNotifications() const queryClient = useQueryClient() const [checkResult, setCheckResult] = useState(null) const [isChecking, setIsChecking] = useState(false) const [applyingIds, setApplyingIds] = useState>(new Set()) const [isApplyingAll, setIsApplyingAll] = useState(false) const handleCheck = async () => { setIsChecking(true) try { const result = await api.checkForContentUpdates() if (result) { setCheckResult(result) } } catch { setCheckResult({ updates: [], checked_at: new Date().toISOString(), error: 'Failed to check for content updates', }) } finally { setIsChecking(false) } } const handleApply = async (update: ResourceUpdateInfo) => { setApplyingIds((prev) => new Set(prev).add(update.resource_id)) try { const result = await api.applyContentUpdate(update) if (result?.success) { addNotification({ type: 'success', message: `Update started for ${update.resource_id}` }) // Remove from the updates list setCheckResult((prev) => prev ? { ...prev, updates: prev.updates.filter((u) => u.resource_id !== update.resource_id) } : prev ) // Force Active Downloads to refetch now — small updates finish before the next // idle poll fires, so without this the user wouldn't see them. queryClient.invalidateQueries({ queryKey: ['download-jobs'] }) } else { addNotification({ type: 'error', message: result?.error || 'Failed to start update' }) } } catch { addNotification({ type: 'error', message: `Failed to start update for ${update.resource_id}` }) } finally { setApplyingIds((prev) => { const next = new Set(prev) next.delete(update.resource_id) return next }) } } const handleApplyAll = async () => { if (!checkResult?.updates.length) return setIsApplyingAll(true) try { const result = await api.applyAllContentUpdates(checkResult.updates) if (result?.results) { const succeeded = result.results.filter((r) => r.success).length const failed = result.results.filter((r) => !r.success).length if (succeeded > 0) { addNotification({ type: 'success', message: `Started ${succeeded} update(s)` }) } if (failed > 0) { addNotification({ type: 'error', message: `${failed} update(s) could not be started` }) } // Remove successful updates from the list const successIds = new Set(result.results.filter((r) => r.success).map((r) => r.resource_id)) setCheckResult((prev) => prev ? { ...prev, updates: prev.updates.filter((u) => !successIds.has(u.resource_id)) } : prev ) if (successIds.size > 0) { queryClient.invalidateQueries({ queryKey: ['download-jobs'] }) } } } catch { addNotification({ type: 'error', message: 'Failed to apply updates' }) } finally { setIsApplyingAll(false) } } return (

Check if newer versions of your installed ZIM files and maps are available.

Check for Content Updates
{checkResult?.error && ( )} {checkResult && !checkResult.error && checkResult.updates.length === 0 && ( )} {checkResult && checkResult.updates.length > 0 && (

{checkResult.updates.length} update(s) available

Update All ({checkResult.updates.length})
( {record.resource_id} ), }, { accessor: 'resource_type', title: 'Type', render: (record) => ( {record.resource_type === 'zim' ? 'ZIM' : 'Map'} ), }, { accessor: 'size_bytes', title: 'Size', render: (record) => ( {record.size_bytes ? formatBytes(record.size_bytes, 1) : '—'} ), }, { accessor: 'installed_version', title: 'Version', render: (record) => ( {record.installed_version} → {record.latest_version} ), }, { accessor: 'resource_id', title: '', render: (record) => ( handleApply(record)} loading={applyingIds.has(record.resource_id)} > Update ), }, ]} />
)} {checkResult?.checked_at && (

Last checked: {new Date(checkResult.checked_at).toLocaleString()}

)}
) }