feat: integrate automatic update checks and enhance settings UI

- Added functionality for automatic update checks in the settings page, allowing users to enable or disable this feature.
- Integrated app version display and update availability notifications.
- Updated settings store to manage auto-check updates state and handle version migration.
- Enhanced layout with a new card for update settings, improving user experience and accessibility.
This commit is contained in:
Evandro Fonseca Junior committed 2025-11-27 22:47:48 -03:00
1 parent f2b4303628
commit ac63066a2a
20 files changed
+1266 -6

No files matched your search

+40
View File
@@ -0,0 +1,40 @@
'use client';
import { useEffect, useState } from 'react';
import { UpdateDialog } from '@/components/modals/update-dialog';
import { useAppUpdater } from '@/hooks/use-app-updater';
import { useSettingsStore } from '@/store/settings-store';
export function AppUpdaterProvider() {
const { updateAvailable, checkUpdate } = useAppUpdater();
const [isOpen, setIsOpen] = useState(false);
const [hasChecked, setHasChecked] = useState(false);
const autoCheckEnabled = useSettingsStore((state) => state.autoCheckUpdates ?? true);
useEffect(() => {
if (!autoCheckEnabled || hasChecked) return;
// Verificar após um delay para não interferir no carregamento inicial
const timer = setTimeout(() => {
checkUpdate().then(() => {
setHasChecked(true);
});
}, 5000); // 5 segundos após o app carregar
return () => clearTimeout(timer);
}, [autoCheckEnabled, hasChecked, checkUpdate]);
useEffect(() => {
if (updateAvailable && !isOpen) {
setIsOpen(true);
}
}, [updateAvailable, isOpen]);
return (
<UpdateDialog
open={isOpen}
onOpenChange={setIsOpen}
/>
);
}
+153
View File
@@ -0,0 +1,153 @@
'use client';
import { useState } from 'react';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Progress } from '@/components/ui/progress';
import { Checkbox } from '@/components/ui/checkbox';
import { Label } from '@/components/ui/label';
import { Download, Loader2, X, Sparkles } from 'lucide-react';
import { useAppUpdater } from '@/hooks/use-app-updater';
import { useSettingsStore } from '@/store/settings-store';
interface UpdateDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
}
export function UpdateDialog({ open, onOpenChange }: UpdateDialogProps) {
const {
currentVersion,
updateAvailable,
updateInfo,
isChecking,
isDownloading,
downloadProgress,
error,
installUpdate,
} = useAppUpdater();
const [dontShowAgain, setDontShowAgain] = useState(false);
const setAutoCheckUpdates = useSettingsStore((state) => state.setAutoCheckUpdates);
const handleInstall = async () => {
if (dontShowAgain) {
setAutoCheckUpdates(false);
}
await installUpdate();
};
const handleClose = () => {
if (dontShowAgain) {
setAutoCheckUpdates(false);
}
onOpenChange(false);
};
if (!updateAvailable && !isChecking) {
return null;
}
return (
<Dialog open={open && updateAvailable} onOpenChange={handleClose}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Sparkles className="h-5 w-5 text-primary" />
Atualização Disponível
</DialogTitle>
<DialogDescription>
Uma nova versão do OllaHub está disponível para download.
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">Versão Atual</span>
<span className="font-mono font-medium">{currentVersion}</span>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">Nova Versão</span>
<span className="font-mono font-medium text-primary">
{updateInfo?.version}
</span>
</div>
</div>
{updateInfo?.body && (
<div className="p-3 bg-muted rounded-lg">
<p className="text-sm text-muted-foreground line-clamp-3">
{updateInfo.body}
</p>
</div>
)}
{isDownloading && (
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">Baixando atualização...</span>
<span className="font-medium">{downloadProgress}%</span>
</div>
<Progress value={downloadProgress} className="h-2" />
</div>
)}
{error && (
<div className="p-3 bg-destructive/10 border border-destructive/20 rounded-lg">
<p className="text-sm text-destructive">{error}</p>
</div>
)}
<div className="flex items-center gap-2">
<Checkbox
id="dont-show-updates"
checked={dontShowAgain}
onCheckedChange={(checked) => setDontShowAgain(checked === true)}
/>
<Label
htmlFor="dont-show-updates"
className="text-sm font-normal cursor-pointer"
>
Não verificar atualizações automaticamente
</Label>
</div>
</div>
<div className="flex gap-2 justify-end">
<Button
variant="outline"
onClick={handleClose}
disabled={isDownloading}
>
<X className="h-4 w-4 mr-2" />
Depois
</Button>
<Button
onClick={handleInstall}
disabled={isDownloading || isChecking}
>
{isDownloading ? (
<>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
Baixando...
</>
) : (
<>
<Download className="h-4 w-4 mr-2" />
Atualizar Agora
</>
)}
</Button>
</div>
</DialogContent>
</Dialog>
);
}
+30
View File
@@ -0,0 +1,30 @@
"use client"
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Check } from "lucide-react"
import { cn } from "@/lib/utils"
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")}
>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName
export { Checkbox }