"use client"; import { invoke } from "@tauri-apps/api/core"; import { listen } from "@tauri-apps/api/event"; import { useCallback, useEffect, useState } from "react"; import { toast } from "sonner"; import { AppUpdateToast } from "@/components/app-update-toast"; import { showToast } from "@/lib/toast-utils"; import type { AppUpdateInfo, AppUpdateProgress } from "@/types"; export function useAppUpdateNotifications() { const [updateInfo, setUpdateInfo] = useState(null); const [isUpdating, setIsUpdating] = useState(false); const [updateProgress, setUpdateProgress] = useState(null); const [isClient, setIsClient] = useState(false); const [dismissedVersion, setDismissedVersion] = useState(null); // Ensure we're on the client side to prevent hydration mismatches useEffect(() => { setIsClient(true); }, []); const checkForAppUpdates = useCallback(async () => { if (!isClient) return; try { const update = await invoke( "check_for_app_updates", ); // Don't show update if this version was already dismissed if (update && update.new_version !== dismissedVersion) { setUpdateInfo(update); } else if (update) { console.log("Update available but dismissed:", update.new_version); } } catch (error) { console.error("Failed to check for app updates:", error); } }, [isClient, dismissedVersion]); const checkForAppUpdatesManual = useCallback(async () => { if (!isClient) return; try { console.log("Triggering manual app update check..."); const update = await invoke( "check_for_app_updates_manual", ); console.log("Manual check result:", update); // Always show manual check results, even if previously dismissed setUpdateInfo(update); } catch (error) { console.error("Failed to manually check for app updates:", error); } }, [isClient]); const handleAppUpdate = useCallback(async (appUpdateInfo: AppUpdateInfo) => { try { setIsUpdating(true); setUpdateProgress({ stage: "downloading", percentage: 0, speed: undefined, eta: undefined, message: "Starting update...", }); await invoke("download_and_install_app_update", { updateInfo: appUpdateInfo, }); } catch (error) { console.error("Failed to update app:", error); showToast({ type: "error", title: "Failed to update Donut Browser", description: String(error), duration: 6000, }); setIsUpdating(false); setUpdateProgress(null); } }, []); const dismissAppUpdate = useCallback(() => { if (!isClient) return; // Remember the dismissed version so we don't show it again if (updateInfo) { setDismissedVersion(updateInfo.new_version); console.log("Dismissed app update version:", updateInfo.new_version); } setUpdateInfo(null); toast.dismiss("app-update"); }, [isClient, updateInfo]); // Listen for app update availability useEffect(() => { if (!isClient) return; const unlistenUpdate = listen( "app-update-available", (event) => { console.log("App update available:", event.payload); setUpdateInfo(event.payload); }, ); const unlistenProgress = listen( "app-update-progress", (event) => { console.log("App update progress:", event.payload); setUpdateProgress(event.payload); // If update is completed, mark as no longer updating after a delay if (event.payload.stage === "completed") { setTimeout(() => { setIsUpdating(false); setUpdateProgress(null); }, 5000); // Show completion message for 5 seconds instead of 2 } }, ); return () => { void unlistenUpdate.then((unlisten) => { unlisten(); }); void unlistenProgress.then((unlisten) => { unlisten(); }); }; }, [isClient]); // Show toast when update is available useEffect(() => { if (!isClient || !updateInfo) return; toast.custom( () => ( ), { id: "app-update", duration: Number.POSITIVE_INFINITY, // Persistent until user action position: "top-left", style: { zIndex: 99999, // Ensure app updates appear above dialogs pointerEvents: "auto", // Ensure app updates remain interactive marginTop: "16px", // slightly lower on macOS-like top controls }, }, ); }, [ updateInfo, handleAppUpdate, dismissAppUpdate, isUpdating, updateProgress, isClient, ]); // Check for app updates on startup useEffect(() => { if (!isClient) return; // Check for updates immediately on startup void checkForAppUpdates(); }, [isClient, checkForAppUpdates]); return { updateInfo, isUpdating, updateProgress, checkForAppUpdates, checkForAppUpdatesManual, dismissAppUpdate, }; }