import React, { useState } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { FiDownload, FiRefreshCw, FiCheckCircle, FiAlertCircle, FiX, FiExternalLink } from 'react-icons/fi'; import { Button } from './Button'; import { Card } from './Card'; import Typography from './Typography'; import { cn } from '../../lib/utils'; export interface UpdateInfo { version: string; releaseDate: string; releaseNotes?: string; } export interface UpdateProgress { percent: number; bytesPerSecond: number; total: number; transferred: number; } interface UpdateNotificationProps { updateState: 'checking' | 'available' | 'downloading' | 'downloaded' | 'error' | 'not-available' | null; updateInfo?: UpdateInfo; progress?: UpdateProgress; error?: { message: string; stack?: string }; onDownload?: () => void; onInstall?: () => void; onDismiss?: () => void; onViewReleases?: () => void; className?: string; } /** * Update notification component that displays the current update status * and provides actions for downloading and installing updates */ export const UpdateNotification: React.FC = ({ updateState, updateInfo, progress, error, onDownload, onInstall, onDismiss, onViewReleases, className }) => { const [isExpanded, setIsExpanded] = useState(false); if (!updateState || updateState === 'not-available') { return null; } const formatBytes = (bytes: number): string => { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; const formatSpeed = (bytesPerSecond: number): string => { return formatBytes(bytesPerSecond) + '/s'; }; const getStateIcon = () => { switch (updateState) { case 'checking': return ; case 'available': return ; case 'downloading': return ; case 'downloaded': return ; case 'error': return ; default: return null; } }; const getStateTitle = () => { switch (updateState) { case 'checking': return 'Checking for updates...'; case 'available': return `Update available: v${updateInfo?.version}`; case 'downloading': return 'Downloading update...'; case 'downloaded': return 'Update ready to install'; case 'error': return 'Update failed'; default: return ''; } }; const getStateMessage = () => { switch (updateState) { case 'checking': return 'Please wait while we check for the latest version.'; case 'available': return 'A new version is available. Would you like to download it?'; case 'downloading': return progress ? `${progress.percent}% • ${formatSpeed(progress.bytesPerSecond)} • ${formatBytes(progress.transferred)} of ${formatBytes(progress.total)}` : 'Preparing download...'; case 'downloaded': return 'The update has been downloaded and is ready to install. The app will restart to apply the update.'; case 'error': return error?.message || 'An error occurred while checking for updates.'; default: return ''; } }; return (
{getStateIcon()}
{getStateTitle()} {getStateMessage()}
{onDismiss && ( )}
{updateState === 'downloading' && progress && (
)} {updateInfo?.releaseNotes && updateState === 'available' && (
{isExpanded && ( {updateInfo.releaseNotes} )}
)}
{onViewReleases && ( )} {updateState === 'available' && onDownload && ( )} {updateState === 'downloaded' && onInstall && ( )}
); };