From d5085294df19819e580d32f1e0fcde1e49b2b1c4 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sat, 28 Mar 2026 18:43:49 +0700 Subject: [PATCH] fix(desktop): update banner dismiss button and install feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the X dismiss button to a floating circle in the top-left corner so it no longer overlaps the banner text. Show the banner during "installing" and "error" states so the user gets feedback after clicking "Install & restart" — previously the banner vanished because those statuses were not in the render condition. Add a retry button for the error state. --- .../app/src/desktop/updates/update-banner.tsx | 70 ++++++++++++++----- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/packages/app/src/desktop/updates/update-banner.tsx b/packages/app/src/desktop/updates/update-banner.tsx index 860abad21..4fe0db78d 100644 --- a/packages/app/src/desktop/updates/update-banner.tsx +++ b/packages/app/src/desktop/updates/update-banner.tsx @@ -10,8 +10,15 @@ const CHANGELOG_URL = "https://paseo.sh/changelog"; export function UpdateBanner() { const { theme } = useUnistyles(); - const { isDesktop, status, availableUpdate, checkForUpdates, installUpdate, isInstalling } = - useDesktopAppUpdater(); + const { + isDesktop, + status, + availableUpdate, + errorMessage, + checkForUpdates, + installUpdate, + isInstalling, + } = useDesktopAppUpdater(); const [dismissed, setDismissed] = useState(false); const intervalRef = useRef | null>(null); @@ -33,24 +40,36 @@ export function UpdateBanner() { if (!isDesktop) return null; if (dismissed) return null; - if (status !== "available" && status !== "installed") return null; + if (status !== "available" && status !== "installed" && status !== "installing" && status !== "error") + return null; const isInstalled = status === "installed"; + const isError = status === "error"; + + function getTitle(): string { + if (isInstalled) return "Update installed"; + if (isInstalling) return "Installing update"; + if (isError) return "Update failed"; + return "Update available"; + } + + function getSubtitle(): string { + if (isInstalled) return "Restart to use the new version."; + if (isInstalling) return "Downloading and installing..."; + if (isError) return errorMessage ?? "Something went wrong."; + return `${availableUpdate?.latestVersion ? `v${availableUpdate.latestVersion.replace(/^v/i, "")} is ready` : "A new version is ready"} to install.`; + } return ( - - setDismissed(true)} hitSlop={8} style={styles.closeButton}> - - + setDismissed(true)} hitSlop={8} style={styles.closeButton}> + + + - {isInstalled ? "Update installed" : "Update available"} - - {isInstalled - ? "Restart to use the new version." - : `${availableUpdate?.latestVersion ? `v${availableUpdate.latestVersion.replace(/^v/i, "")} is ready` : "A new version is ready"} to install.`} - + {getTitle()} + {getSubtitle()} @@ -61,7 +80,7 @@ export function UpdateBanner() { What's new - {!isInstalled && ( + {!isInstalled && !isError && ( void installUpdate()} disabled={isInstalling} @@ -76,6 +95,15 @@ export function UpdateBanner() { )} + + {isError && ( + void checkForUpdates()} + style={({ pressed }) => [styles.primaryButton, pressed && styles.buttonPressed]} + > + Retry + + )} @@ -109,15 +137,21 @@ const styles = StyleSheet.create((theme) => ({ }, closeButton: { position: "absolute", - top: theme.spacing[2], - left: theme.spacing[2], - padding: theme.spacing[1], + top: -8, + left: -8, + width: 24, + height: 24, + borderRadius: 12, + backgroundColor: theme.colors.surface2, + borderWidth: theme.borderWidth[1], + borderColor: theme.colors.border, + alignItems: "center", + justifyContent: "center", zIndex: 1, }, textSection: { flex: 1, gap: 2, - paddingTop: theme.spacing[1], }, title: { color: theme.colors.foreground,