"use client"; import { LoadingButton } from "@/components/loading-button"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { usePermissions } from "@/hooks/use-permissions"; import type { PermissionType } from "@/hooks/use-permissions"; import { showSuccessToast } from "@/lib/toast-utils"; import { invoke } from "@tauri-apps/api/core"; import { useTheme } from "next-themes"; import { useCallback, useEffect, useState } from "react"; import { BsCamera, BsMic } from "react-icons/bs"; interface AppSettings { set_as_default_browser: boolean; show_settings_on_startup: boolean; theme: string; auto_updates_enabled: boolean; auto_delete_unused_binaries: boolean; } interface PermissionInfo { permission_type: PermissionType; isGranted: boolean; description: string; } interface SettingsDialogProps { isOpen: boolean; onClose: () => void; } export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) { const [settings, setSettings] = useState({ set_as_default_browser: false, show_settings_on_startup: true, theme: "system", auto_updates_enabled: true, auto_delete_unused_binaries: true, }); const [originalSettings, setOriginalSettings] = useState({ set_as_default_browser: false, show_settings_on_startup: true, theme: "system", auto_updates_enabled: true, auto_delete_unused_binaries: true, }); const [isDefaultBrowser, setIsDefaultBrowser] = useState(false); const [isLoading, setIsLoading] = useState(false); const [isSaving, setIsSaving] = useState(false); const [isSettingDefault, setIsSettingDefault] = useState(false); const [isClearingCache, setIsClearingCache] = useState(false); const [permissions, setPermissions] = useState([]); const [isLoadingPermissions, setIsLoadingPermissions] = useState(false); const [requestingPermission, setRequestingPermission] = useState(null); const [isMacOS, setIsMacOS] = useState(false); const { setTheme } = useTheme(); const { requestPermission, isMicrophoneAccessGranted, isCameraAccessGranted, } = usePermissions(); const getPermissionDescription = useCallback((type: PermissionType) => { switch (type) { case "microphone": return "Access to microphone for browser applications"; case "camera": return "Access to camera for browser applications"; } }, []); useEffect(() => { if (isOpen) { loadSettings().catch(console.error); checkDefaultBrowserStatus().catch(console.error); // Check if we're on macOS const userAgent = navigator.userAgent; const isMac = userAgent.includes("Mac"); setIsMacOS(isMac); if (isMac) { loadPermissions().catch(console.error); } // Set up interval to check default browser status const intervalId = setInterval(() => { checkDefaultBrowserStatus().catch(console.error); }, 500); // Check every 500ms // Cleanup interval on component unmount or dialog close return () => { clearInterval(intervalId); }; } }, [isOpen]); // Update permissions when the permission states change useEffect(() => { if (isMacOS) { const permissionList: PermissionInfo[] = [ { permission_type: "microphone", isGranted: isMicrophoneAccessGranted, description: getPermissionDescription("microphone"), }, { permission_type: "camera", isGranted: isCameraAccessGranted, description: getPermissionDescription("camera"), }, ]; setPermissions(permissionList); } else { setPermissions([]); } }, [ isMacOS, isMicrophoneAccessGranted, isCameraAccessGranted, getPermissionDescription, ]); const loadSettings = async () => { setIsLoading(true); try { const appSettings = await invoke("get_app_settings"); setSettings(appSettings); setOriginalSettings(appSettings); } catch (error) { console.error("Failed to load settings:", error); } finally { setIsLoading(false); } }; const loadPermissions = async () => { setIsLoadingPermissions(true); try { if (!isMacOS) { // On non-macOS platforms, don't show permissions setPermissions([]); return; } const permissionList: PermissionInfo[] = [ { permission_type: "microphone", isGranted: isMicrophoneAccessGranted, description: getPermissionDescription("microphone"), }, { permission_type: "camera", isGranted: isCameraAccessGranted, description: getPermissionDescription("camera"), }, ]; setPermissions(permissionList); } catch (error) { console.error("Failed to load permissions:", error); } finally { setIsLoadingPermissions(false); } }; const checkDefaultBrowserStatus = async () => { try { const isDefault = await invoke("is_default_browser"); setIsDefaultBrowser(isDefault); } catch (error) { console.error("Failed to check default browser status:", error); } }; const handleSetDefaultBrowser = async () => { setIsSettingDefault(true); try { await invoke("set_as_default_browser"); await checkDefaultBrowserStatus(); } catch (error) { console.error("Failed to set as default browser:", error); } finally { setIsSettingDefault(false); } }; const handleClearCache = async () => { setIsClearingCache(true); try { await invoke("clear_all_version_cache_and_refetch"); showSuccessToast("Cache cleared successfully", { description: "All browser version cache has been cleared and browsers are being refreshed", duration: 4000, }); } catch (error) { console.error("Failed to clear cache:", error); } finally { setIsClearingCache(false); } }; const handleRequestPermission = async (permissionType: PermissionType) => { setRequestingPermission(permissionType); try { await requestPermission(permissionType); showSuccessToast( `${getPermissionDisplayName(permissionType)} access requested`, ); } catch (error) { console.error("Failed to request permission:", error); } finally { setRequestingPermission(null); } }; const getPermissionIcon = (type: PermissionType) => { switch (type) { case "microphone": return ; case "camera": return ; } }; const getPermissionDisplayName = (type: PermissionType) => { switch (type) { case "microphone": return "Microphone"; case "camera": return "Camera"; } }; const getStatusBadge = (isGranted: boolean) => { if (isGranted) { return ( Granted ); } return Not Granted; }; const handleSave = async () => { setIsSaving(true); try { await invoke("save_app_settings", { settings }); setTheme(settings.theme); setOriginalSettings(settings); onClose(); } catch (error) { console.error("Failed to save settings:", error); } finally { setIsSaving(false); } }; const updateSetting = (key: keyof AppSettings, value: boolean | string) => { setSettings((prev) => ({ ...prev, [key]: value })); }; // Check if settings have changed (excluding default browser setting) const hasChanges = settings.show_settings_on_startup !== originalSettings.show_settings_on_startup || settings.theme !== originalSettings.theme || settings.auto_updates_enabled !== originalSettings.auto_updates_enabled || settings.auto_delete_unused_binaries !== originalSettings.auto_delete_unused_binaries; return ( Settings
{/* Appearance Section */}

Choose your preferred theme or follow your system settings.

{/* Default Browser Section */}
{isDefaultBrowser ? "Active" : "Inactive"}
{ handleSetDefaultBrowser().catch(console.error); }} disabled={isDefaultBrowser} variant={isDefaultBrowser ? "outline" : "default"} className="w-full" > {isDefaultBrowser ? "Already Default Browser" : "Set as Default Browser"}

When set as default, Donut Browser will handle web links and allow you to choose which profile to use.

{/* Auto-Update Section */}
{ updateSetting("auto_updates_enabled", checked as boolean); }} />
{ updateSetting( "auto_delete_unused_binaries", checked as boolean, ); }} />

When enabled, Donut Browser will check for browser updates and notify you when updates are available for your profiles. Unused binaries will be automatically deleted to save disk space.

{/* Startup Behavior Section */}
{ updateSetting("show_settings_on_startup", checked as boolean); }} />

When enabled, the settings dialog will be shown when the app starts.

{/* Permissions Section - Only show on macOS */} {isMacOS && (
{isLoadingPermissions ? (
Loading permissions...
) : (
{permissions.map((permission) => (
{getPermissionIcon(permission.permission_type)}
{getPermissionDisplayName( permission.permission_type, )}
{permission.description}
{getStatusBadge(permission.isGranted)} {!permission.isGranted && ( { handleRequestPermission( permission.permission_type, ).catch(console.error); }} > Grant )}
))}
)}

These permissions allow browsers launched from Donut Browser to access system resources. Each website will still ask for your permission individually.

)} {/* Advanced Section */}
{ handleClearCache().catch(console.error); }} variant="outline" className="w-full" > Clear All Version Cache

Clear all cached browser version data and refresh all browser versions from their sources. This will force a fresh download of version information for all browsers.

{ handleSave().catch(console.error); }} disabled={isLoading || !hasChanges} > Save Settings
); }