// ===================================================================== // Bandeau « une mise à jour est disponible ». // // Discret : une bande fine en haut de l'application, jamais une modale // qui interrompt le travail. On peut reporter — la proposition revient // au prochain lancement. // ===================================================================== import { useCallback, useEffect, useState } from 'react' import { AnimatePresence, motion } from 'framer-motion' import { Download, RefreshCw, Sparkles, X } from 'lucide-react' import { AvailableUpdate, checkForUpdate } from '../lib/updater' import { useFeedback } from '../lib/feedback' import { Button, IconButton } from './ui' /** Nouvelle vérification toutes les 2 heures pour les sessions qui durent. */ const RECHECK_MS = 2 * 60 * 60 * 1000 export default function UpdateBanner(): JSX.Element | null { const { toast } = useFeedback() const [update, setUpdate] = useState(null) const [dismissed, setDismissed] = useState(false) const [installing, setInstalling] = useState(false) const [progress, setProgress] = useState(null) const look = useCallback(async () => { const found = await checkForUpdate() if (found) setUpdate(found) }, []) useEffect(() => { // Au démarrage, mais sans retarder l'affichage de l'application. const first = setTimeout(look, 4000) const timer = setInterval(look, RECHECK_MS) return () => { clearTimeout(first) clearInterval(timer) } }, [look]) if (!update || dismissed) return null const install = async (): Promise => { setInstalling(true) setProgress(0) try { // Au succès l'application redémarre : rien à faire de plus ici. await update.install((pct) => setProgress(pct)) } catch (e) { setInstalling(false) setProgress(null) toast( `La mise à jour a échoué : ${(e as Error).message}. Réessaie plus tard ou préviens l'administrateur.`, 'error' ) } } return (
Version {update.version} disponible.{' '} {installing ? ( {progress === null || progress === 0 ? 'Téléchargement…' : progress < 100 ? `Téléchargement ${progress} %…` : 'Installation, l’application va redémarrer…'} ) : ( {update.notes?.split('\n')[0] || "L'application se relancera toute seule."} )} {installing ? (
) : ( <> } onClick={() => setDismissed(true)} /> )}
) } /** Ligne « Version X · Rechercher une mise à jour », pour l'écran d'aide. */ export function UpdateCheckRow({ version }: { version: string | null }): JSX.Element { const { toast } = useFeedback() const [busy, setBusy] = useState(false) const look = async (): Promise => { setBusy(true) const found = await checkForUpdate() setBusy(false) toast( found ? `Version ${found.version} disponible — le bandeau apparaît en haut de l'écran.` : 'Tu es déjà à jour.', found ? 'info' : 'success' ) } return (
Version installée {version ?? '—'}
) }