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:
1 parent
f2b4303628
commit
ac63066a2a
20 files changed
+1266
-6
No files matched your search
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
Reference in new issue
Block a user