import { useEffect, useState } from 'react'
import StyledButton from '~/components/StyledButton'
import StyledSectionHeader from '~/components/StyledSectionHeader'
import Alert from '~/components/Alert'
import api from '~/lib/api'
import Input from '~/components/inputs/Input'
import Switch from '~/components/inputs/Switch'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { useNotifications } from '~/context/NotificationContext'
import { useContentAutoUpdateStatus } from '~/hooks/useContentAutoUpdateStatus'
import { formatBytes } from '~/lib/util'
const COOLOFF_OPTIONS = [
{ value: 24, label: '24 hours (1 day)' },
{ value: 48, label: '48 hours (2 days)' },
{ value: 72, label: '72 hours (3 days)' },
{ value: 168, label: '7 days' },
]
const BYTES_PER_GB = 1024 * 1024 * 1024
export default function ContentAutoUpdateSection() {
const { addNotification } = useNotifications()
const queryClient = useQueryClient()
const { data: status, isLoading } = useContentAutoUpdateStatus()
const [windowStart, setWindowStart] = useState('02:00')
const [windowEnd, setWindowEnd] = useState('05:00')
const [cooloff, setCooloff] = useState(72)
// Data cap is stored in bytes but edited in GB (0 = unlimited).
const [capGb, setCapGb] = useState('0')
// Seed editable fields once the persisted status loads.
useEffect(() => {
if (status) {
setWindowStart(status.windowStart)
setWindowEnd(status.windowEnd)
setCooloff(status.cooloffHours)
setCapGb(
status.maxBytesPerWindow > 0
? String(Math.round((status.maxBytesPerWindow / BYTES_PER_GB) * 100) / 100)
: '0'
)
}
}, [status?.windowStart, status?.windowEnd, status?.cooloffHours, status?.maxBytesPerWindow])
const enabled = status?.enabled ?? false
const autoDisabled = !!status?.autoDisabledReason
const toggleMutation = useMutation({
mutationFn: (value: boolean) => api.updateSetting('contentAutoUpdate.enabled', value),
onSuccess: (_data, value) => {
queryClient.invalidateQueries({ queryKey: ['content-auto-update-status'] })
addNotification({
type: 'success',
message: value
? 'Automatic content updates enabled.'
: 'Automatic content updates disabled.',
})
},
onError: () => {
addNotification({ type: 'error', message: 'Failed to update content auto-update setting.' })
},
})
const handleSaveSchedule = async () => {
const parsedGb = Number(capGb)
if (!Number.isFinite(parsedGb) || parsedGb < 0) {
addNotification({ type: 'error', message: 'Data cap must be 0 or a positive number of GB.' })
return
}
const capBytes = Math.round(parsedGb * BYTES_PER_GB)
try {
await api.updateSetting('contentAutoUpdate.windowStart', windowStart)
await api.updateSetting('contentAutoUpdate.windowEnd', windowEnd)
await api.updateSetting('contentAutoUpdate.cooloffHours', String(cooloff))
await api.updateSetting('contentAutoUpdate.maxBytesPerWindow', String(capBytes))
queryClient.invalidateQueries({ queryKey: ['content-auto-update-status'] })
addNotification({ type: 'success', message: 'Content update schedule saved.' })
} catch {
addNotification({ type: 'error', message: 'Failed to save content update schedule.' })
}
}
return (
<>
Delay after a new version appears
Update window: {status.windowStart}โ{status.windowEnd} ( {status.withinWindow ? 'currently inside' : 'currently outside'}); cool-off{' '} {status.cooloffHours}h; data cap{' '} {status.maxBytesPerWindow > 0 ? formatBytes(status.maxBytesPerWindow) : 'unlimited'} {status.maxBytesPerWindow > 0 && ( <> ({formatBytes(status.windowBytesUsed)} used this window)> )} . {status.lastResult && ( <> {' '} Last run: {status.lastResult} {status.lastAttemptAt ? ` (${new Date(status.lastAttemptAt).toLocaleString()})` : ''} > )}
{status.lastError && (Last error: {status.lastError}
)} {status.resources.length === 0 ? (All installed content is up to date. New versions will appear here when detected.
) : ({resource.resource_id}{' '} {resource.resource_type}
{resource.current_version} {resource.available_update_version ? ` โ ${resource.available_update_version}` : ' (up to date)'} {resource.size_bytes ? ` ยท ${formatBytes(resource.size_bytes)}` : ''}
{resource.auto_disabled_reason && ({resource.auto_disabled_reason}
)}