Component props.
The rendered update management section.
export function UpdateManagementSection({
updateChannel,
isCheckingUpdate,
updateInfo,
updateError,
isDownloading,
downloadProgress,
isDownloaded,
highlightedSectionId,
onUpdateChannelChange,
onCheckForUpdates,
onDownloadUpdate,
onInstallUpdate,
onOpenExternal,
collapsedSections,
onToggleSection,
}: Readonly<UpdateManagementSectionProps>) {
return (
<div
id="data-updates"
className={cn(
highlightedSectionId === "data-updates" &&
"rounded-2xl ring-2 ring-blue-500 ring-offset-2 ring-offset-white dark:ring-blue-400 dark:ring-offset-slate-950",
)}
>
<SettingsSectionShell
id="update-management"
isCollapsible={true}
isCollapsed={collapsedSections["update-management"] ?? false}
onCollapsedChange={() => onToggleSection("update-management")}
icon={RefreshCw}
title="Check for updates"
description="Stay current with the latest Kenmei → AniList improvements."
accent="from-sky-500/15 via-blue-500/10 to-transparent"
className="mt-6"
contentClassName="space-y-6"
>
<div className="grid gap-6">
{/* Update Channel Card */}
<Card>
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<div className="space-y-1">
<CardTitle className="text-base">Update Channel</CardTitle>
<CardDescription>
Select which version of the app you want to receive updates
for.
</CardDescription>
</div>
<Badge variant="outline" className="capitalize">
{updateChannel}
</Badge>
</div>
</CardHeader>
<CardContent>
<RadioGroup
value={updateChannel}
onValueChange={(v) =>
onUpdateChannelChange(v as "stable" | "beta")
}
className="grid gap-4 sm:grid-cols-2"
>
<div
className={cn(
"flex items-start space-x-3 rounded-lg border p-4 transition-colors hover:bg-slate-50 dark:hover:bg-slate-900/50",
updateChannel === "stable" &&
"border-blue-500 bg-blue-50/50 dark:border-blue-500/50 dark:bg-blue-950/20",
)}
>
<RadioGroupItem
value="stable"
id="update-stable"
className="mt-1"
/>
<Label
htmlFor="update-stable"
className="cursor-pointer space-y-1"
>
<div className="flex items-center gap-2 font-medium">
<Shield className="h-4 w-4 text-blue-500" />
Stable
</div>
<div className="text-muted-foreground text-xs">
Tested and reliable versions. Recommended for most users.
</div>
</Label>
</div>
<div
className={cn(
"flex items-start space-x-3 rounded-lg border p-4 transition-colors hover:bg-slate-50 dark:hover:bg-slate-900/50",
updateChannel === "beta" &&
"border-amber-500 bg-amber-50/50 dark:border-amber-500/50 dark:bg-amber-950/20",
)}
>
<RadioGroupItem
value="beta"
id="update-beta"
className="mt-1"
/>
<Label
htmlFor="update-beta"
className="cursor-pointer space-y-1"
>
<div className="flex items-center gap-2 font-medium">
<Zap className="h-4 w-4 text-amber-500" />
Beta
</div>
<div className="text-muted-foreground text-xs">
Early access to new features. May contain bugs.
</div>
</Label>
</div>
</RadioGroup>
</CardContent>
</Card>
{/* Update Status Card */}
<Card>
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<div className="space-y-1">
<CardTitle className="text-base">Update Status</CardTitle>
<CardDescription>
Current version:{" "}
<span className="text-foreground font-mono font-medium">
{getAppVersion()}
</span>
</CardDescription>
</div>
<Button
onClick={onCheckForUpdates}
disabled={isCheckingUpdate}
aria-label="Check for updates"
size="sm"
>
{isCheckingUpdate ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Checking...
</>
) : (
<>
<RefreshCw className="mr-2 h-4 w-4" />
Check for Updates
</>
)}
</Button>
</div>
</CardHeader>
<CardContent>
{updateError && (
<Alert variant="destructive" className="mb-4">
<AlertCircle className="h-4 w-4" />
<AlertTitle>Update Check Failed</AlertTitle>
<AlertDescription>{updateError}</AlertDescription>
</Alert>
)}
{updateInfo ? (
<div className="space-y-4">
<div className="bg-muted/50 rounded-lg border p-4">
<div className="mb-3 flex flex-wrap items-center gap-3">
<Badge
className={
updateInfo.isBeta
? "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300"
: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300"
}
>
{updateInfo.isBeta ? "Beta Release" : "Stable Release"}
</Badge>
<span className="font-mono text-sm font-medium">
v{updateInfo.version.replace(/^v/, "")}
</span>
<button
type="button"
className="text-xs text-blue-500 underline hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300"
onClick={onOpenExternal(updateInfo.url)}
>
Release Notes
</button>
</div>
<div className="flex items-center gap-2 text-sm">
{(() => {
const current = getAppVersion().replace(/^v/, "");
const latest = updateInfo.version.replace(/^v/, "");
if (current === latest) {
return (
<div className="flex items-center gap-2 text-emerald-600 dark:text-emerald-400">
<Check className="h-4 w-4" />
<span>You are on the latest version.</span>
</div>
);
}
if (compareVersions(current, latest) < 0) {
return (
<div className="flex items-center gap-2 text-amber-600 dark:text-amber-400">
<Download className="h-4 w-4" />
<span>A new version is available!</span>
</div>
);
}
return (
<div className="flex items-center gap-2 text-blue-600 dark:text-blue-400">
<Zap className="h-4 w-4" />
<span>You are on a development build.</span>
</div>
);
})()}
</div>
</div>
{/* Download/Install Actions */}
{(isDownloading || downloadProgress > 0) && (
<div className="space-y-2">
<div className="text-muted-foreground flex items-center justify-between text-xs">
<span>Downloading update...</span>
<span>{Math.round(downloadProgress * 100)}%</span>
</div>
<div className="h-2 w-full overflow-hidden rounded-full bg-slate-100 dark:bg-slate-800">
<div
className="h-full bg-blue-500 transition-all duration-300"
style={{ width: `${downloadProgress * 100}%` }}
/>
</div>
</div>
)}
<div className="flex gap-3">
{!isDownloading && !isDownloaded && (
<Button
onClick={onDownloadUpdate}
disabled={isCheckingUpdate}
className="flex-1"
>
<Download className="mr-2 h-4 w-4" />
Download Update
</Button>
)}
{isDownloaded && !isDownloading && (
<Button
onClick={onInstallUpdate}
className="flex-1 bg-emerald-600 hover:bg-emerald-700"
>
<Check className="mr-2 h-4 w-4" />
Install & Restart
</Button>
)}
</div>
</div>
) : (
<div className="text-muted-foreground flex flex-col items-center justify-center py-6 text-center">
<RefreshCw className="mb-2 h-8 w-8 opacity-20" />
<p className="text-sm">
{isCheckingUpdate
? "Checking for updates..."
: "Check for updates to see what's new."}
</p>
</div>
)}
</CardContent>
</Card>
</div>
</SettingsSectionShell>
</div>
);
}
Update management section component. Handles checking for updates, downloading, and installing updates.