feat(desktop): add manual download fallback to UpdateToast

Extends UpdateToast to handle update-error events from the preload bridge,
showing a "Download" link to the GitHub release URL when auto-update fails.
This commit is contained in:
Jannis Braun
2026-03-21 02:31:20 +01:00
parent 6ebb124d25
commit a9fcdb72a8
+52 -8
View File
@@ -1,27 +1,39 @@
import React, { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { isElectron } from '../../platform/platform'; import { isElectron } from '../../platform/platform';
interface UpdateError {
message: string;
releaseUrl: string;
}
/** /**
* Persistent toast shown when an Electron auto-update has been downloaded. * Persistent toast shown when an Electron auto-update has been downloaded
* or when auto-update fails (offers manual download link).
* Renders nothing in browser environments. * Renders nothing in browser environments.
*/ */
export function UpdateToast() { export function UpdateToast() {
const [downloadedVersion, setDownloadedVersion] = useState<string | null>(null); const [downloadedVersion, setDownloadedVersion] = useState<string | null>(null);
const [failedUpdate, setFailedUpdate] = useState<UpdateError | null>(null);
useEffect(() => { useEffect(() => {
if (!isElectron() || !window.backspace) return; if (!isElectron() || !window.backspace) return;
window.backspace.onUpdateDownloaded((info) => { window.backspace.onUpdateDownloaded((info) => {
setDownloadedVersion(info.version); setDownloadedVersion(info.version);
// Auto-download succeeded — clear any previous error state
setFailedUpdate(null);
});
window.backspace.onUpdateError((error) => {
setFailedUpdate(error);
}); });
}, []); }, []);
if (!downloadedVersion) return null; // Nothing to show
if (!downloadedVersion && !failedUpdate) return null;
const handleRestart = () => {
window.backspace?.installUpdate();
};
// Auto-download succeeded — show restart toast
if (downloadedVersion) {
return ( return (
<div className="fixed bottom-6 left-6 z-[300] animate-slide-up"> <div className="fixed bottom-6 left-6 z-[300] animate-slide-up">
<div className="glass-pill rounded-xl px-4 py-3 flex items-center gap-3 max-w-[340px]"> <div className="glass-pill rounded-xl px-4 py-3 flex items-center gap-3 max-w-[340px]">
@@ -32,7 +44,7 @@ export function UpdateToast() {
</p> </p>
</div> </div>
<button <button
onClick={handleRestart} onClick={() => window.backspace?.installUpdate()}
className="shrink-0 px-3 py-1.5 text-xs font-medium rounded-lg bg-accent-primary hover:bg-accent-primary/80 text-white transition-colors" className="shrink-0 px-3 py-1.5 text-xs font-medium rounded-lg bg-accent-primary hover:bg-accent-primary/80 text-white transition-colors"
> >
Restart Restart
@@ -49,4 +61,36 @@ export function UpdateToast() {
</div> </div>
</div> </div>
); );
}
// Auto-download failed — show manual download toast
return (
<div className="fixed bottom-6 left-6 z-[300] animate-slide-up">
<div className="glass-pill rounded-xl px-4 py-3 flex items-center gap-3 max-w-[380px]">
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-txt-primary">Update available</p>
<p className="text-xs text-txt-secondary truncate">
Auto-install failed download manually
</p>
</div>
<a
href={failedUpdate!.releaseUrl}
target="_blank"
rel="noopener noreferrer"
className="shrink-0 px-3 py-1.5 text-xs font-medium rounded-lg bg-accent-primary hover:bg-accent-primary/80 text-white transition-colors"
>
Download
</a>
<button
onClick={() => setFailedUpdate(null)}
className="shrink-0 p-1 text-txt-tertiary hover:text-txt-secondary transition-colors"
aria-label="Dismiss"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
);
} }