import React, { useState, useEffect, useCallback } from "react"; import { api } from "../services/api"; import { VersionInfo, CommitInfo } from "../types"; interface VersionCheckerProps { onUpdateAvailable?: (hasUpdate: boolean) => void; } interface VersionModalProps { isOpen: boolean; onClose: () => void; versionInfo: VersionInfo | null; isLoading: boolean; } function VersionModal({ isOpen, onClose, versionInfo, isLoading }: VersionModalProps) { const [commits, setCommits] = useState([]); const [loadingCommits, setLoadingCommits] = useState(false); const [upgrading, setUpgrading] = useState(false); const [restarting, setRestarting] = useState(false); const [upgradeMessage, setUpgradeMessage] = useState(null); const [upgradeError, setUpgradeError] = useState(null); useEffect(() => { if (isOpen && versionInfo?.has_update && versionInfo.current_tag && versionInfo.latest_tag) { loadCommits(versionInfo.current_tag, versionInfo.latest_tag); } }, [isOpen, versionInfo]); const loadCommits = async (currentTag: string, latestTag: string) => { setLoadingCommits(true); try { const result = await api.getChangelog(currentTag, latestTag); setCommits(result || []); } catch (err) { console.error("Failed to load changelog:", err); setCommits([]); } finally { setLoadingCommits(false); } }; const handleUpgrade = async () => { setUpgrading(true); setUpgradeError(null); setUpgradeMessage(null); try { const result = await api.upgrade(); setUpgradeMessage(result.message); } catch (err) { const message = err instanceof Error ? err.message : "Unknown error"; setUpgradeError(message); } finally { setUpgrading(false); } }; const handleExit = async () => { setRestarting(true); try { await api.exit(); setTimeout(() => { window.location.reload(); }, 2000); } catch { setTimeout(() => { window.location.reload(); }, 2000); } }; if (!isOpen) return null; const formatDateTime = (dateStr: string) => { const date = new Date(dateStr); return date.toLocaleString(undefined, { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", timeZoneName: "short", }); }; const getCommitUrl = (sha: string) => { return `https://github.com/boldsoftware/shelley/commit/${sha}`; }; return (
e.stopPropagation()}>

Version

{isLoading ? (
Checking for updates...
) : versionInfo ? ( <>
Current: {versionInfo.current_tag || versionInfo.current_version || "dev"} {versionInfo.current_commit_time && ( ({formatDateTime(versionInfo.current_commit_time)}) )}
{versionInfo.latest_tag && (
Latest: {versionInfo.latest_tag} {versionInfo.published_at && ( ({formatDateTime(versionInfo.published_at)}) )}
)} {versionInfo.error && (
Error: {versionInfo.error}
)} {/* Changelog */} {versionInfo.has_update && (

Changelog

{loadingCommits ? (
Loading...
) : commits.length > 0 ? ( ) : (
No commits found
)}
)} {/* Upgrade/Restart buttons */} {versionInfo.has_update && versionInfo.download_url && (
{upgradeMessage && (
Upgraded {versionInfo.executable_path || "shelley"}
)} {upgradeError &&
{upgradeError}
} {!upgradeMessage ? ( ) : ( )}
)} ) : (
Loading...
)}
); } export function useVersionChecker({ onUpdateAvailable }: VersionCheckerProps = {}) { const [versionInfo, setVersionInfo] = useState(null); const [showModal, setShowModal] = useState(false); const [isLoading, setIsLoading] = useState(false); const [shouldNotify, setShouldNotify] = useState(false); const checkVersion = useCallback(async () => { setIsLoading(true); try { // Always force refresh when checking const info = await api.checkVersion(true); setVersionInfo(info); setShouldNotify(info.should_notify); onUpdateAvailable?.(info.should_notify); } catch (err) { console.error("Failed to check version:", err); } finally { setIsLoading(false); } }, [onUpdateAvailable]); // Check version on mount (uses cache) useEffect(() => { const checkInitial = async () => { try { const info = await api.checkVersion(false); setVersionInfo(info); setShouldNotify(info.should_notify); onUpdateAvailable?.(info.should_notify); } catch (err) { console.error("Failed to check version:", err); } }; checkInitial(); }, [onUpdateAvailable]); const openModal = useCallback(() => { setShowModal(true); // Always check for new version when opening modal checkVersion(); }, [checkVersion]); const closeModal = useCallback(() => { setShowModal(false); }, []); const VersionModalComponent = ( ); return { hasUpdate: shouldNotify, // For red dot indicator (5+ days apart) versionInfo, openModal, closeModal, isLoading, VersionModal: VersionModalComponent, }; } export default useVersionChecker;