import React, { useState, useEffect } from 'react'; import { Box, IconButton, Spinner, Text, Menu, PopOut, Button, config } from 'folds'; import * as css from './UpdateNotification.css'; interface UpdateInfo { version: string; releaseNotes?: string; releaseDate?: string; } interface DownloadProgress { percent: number; transferred: number; total: number; } export function UpdateNotification() { const [updateAvailable, setUpdateAvailable] = useState(false); const [updateInfo, setUpdateInfo] = useState(null); const [downloading, setDownloading] = useState(false); const [downloadProgress, setDownloadProgress] = useState(0); const [updateReady, setUpdateReady] = useState(false); const [checking, setChecking] = useState(false); const [menuAnchor, setMenuAnchor] = useState<{ x: number; y: number; width: number; height: number } | undefined>(undefined); useEffect(() => { // Check if we're in Electron environment if (typeof window === 'undefined' || !(window as any).electron?.updater) { return; } const { updater } = (window as any).electron; // Listen for update available updater.onUpdateAvailable((info: UpdateInfo) => { console.log('Update available:', info.version); setUpdateAvailable(true); setUpdateInfo(info); setDownloading(false); setUpdateReady(false); setChecking(false); }); // Listen for download progress updater.onUpdateDownloadProgress((progress: DownloadProgress) => { setDownloadProgress(Math.round(progress.percent)); }); // Listen for update downloaded updater.onUpdateDownloaded((info: UpdateInfo) => { console.log('Update downloaded:', info.version); setDownloading(false); setUpdateReady(true); }); // Cleanup - IPC listeners don't need manual cleanup in this case }, []); const handleCheckForUpdates = async () => { setChecking(true); try { const result = await (window as any).electron.updater.checkForUpdates(); console.log('Update check result:', result); // If no update found, show feedback setTimeout(() => { if (!updateAvailable) { setChecking(false); } }, 2000); } catch (error) { console.error('Failed to check for updates:', error); setChecking(false); } }; const handleDownload = async () => { setDownloading(true); setDownloadProgress(0); try { await (window as any).electron.updater.downloadUpdate(); } catch (error) { console.error('Failed to download update:', error); setDownloading(false); } setMenuAnchor(undefined); }; const handleInstall = async () => { try { await (window as any).electron.updater.installUpdate(); } catch (error) { console.error('Failed to install update:', error); } }; const handleMenuToggle = (event: React.MouseEvent) => { if (menuAnchor) { setMenuAnchor(undefined); } else { const rect = event.currentTarget.getBoundingClientRect(); setMenuAnchor({ x: rect.x, y: rect.y, width: rect.width, height: rect.height }); } }; // Don't render anything if not in Electron if (typeof window === 'undefined' || !(window as any).electron?.updater) { return null; } // Show check button if no update status if (!updateAvailable && !updateReady && !checking) { return ( ); } // Show checking state if (checking) { return ( ); } // Show update available/ready state if (!updateAvailable && !updateReady) { return null; } return ( {updateReady ? 'Update Ready' : 'Update Available'} {updateInfo && ( Version {updateInfo.version} )} {updateReady ? ( ) : ( )} } > {null} ); }