diff --git a/src-tauri/src/theme_detector.rs b/src-tauri/src/theme_detector.rs index 7f6055e..0900bc4 100644 --- a/src-tauri/src/theme_detector.rs +++ b/src-tauri/src/theme_detector.rs @@ -414,7 +414,7 @@ mod linux { Err("Could not detect theme from system files".into()) } - fn is_command_available(command: &str) -> bool { + pub fn is_command_available(command: &str) -> bool { Command::new("which") .arg(command) .output() diff --git a/src/app/page.tsx b/src/app/page.tsx index ede8b6e..790bb0a 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -23,7 +23,7 @@ import { useAppUpdateNotifications } from "@/hooks/use-app-update-notifications" import type { PermissionType } from "@/hooks/use-permissions"; import { usePermissions } from "@/hooks/use-permissions"; import { useUpdateNotifications } from "@/hooks/use-update-notifications"; -import { showErrorToast } from "@/lib/toast-utils"; +import { showErrorToast, showToast } from "@/lib/toast-utils"; import type { BrowserProfile, CamoufoxConfig, GroupWithCount } from "@/types"; type BrowserTypeString = @@ -713,6 +713,29 @@ export default function Home() { loadGroups, ]); + // Show deprecation warning for unsupported profiles + useEffect(() => { + if (profiles.length === 0) return; + + const hasDeprecated = profiles.some( + (p) => + ["tor-browser", "mullvad-browser"].includes(p.browser) || + (p.release_type === "nightly" && p.browser !== "firefox-developer"), + ); + + if (hasDeprecated) { + // Use a stable id to avoid duplicate toasts on re-renders + showToast({ + id: "deprecated-profiles-warning", + type: "error", + title: "Some profiles will be deprecated soon", + description: + "Tor, Mullvad Browser and nightly profiles (except Firefox Developers Edition) will be removed in upcoming versions. Please check GitHub for migration instructions which will be available soon.", + duration: 15000, + }); + } + }, [profiles]); + useEffect(() => { if (profiles.length === 0) return; diff --git a/src/components/change-version-dialog.tsx b/src/components/change-version-dialog.tsx index 5a598b7..bf99923 100644 --- a/src/components/change-version-dialog.tsx +++ b/src/components/change-version-dialog.tsx @@ -2,11 +2,9 @@ import { invoke } from "@tauri-apps/api/core"; import { useCallback, useEffect, useState } from "react"; -import { LuTriangleAlert } from "react-icons/lu"; import { LoadingButton } from "@/components/loading-button"; import { ReleaseTypeSelector } from "@/components/release-type-selector"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; -import { Checkbox } from "@/components/ui/checkbox"; +import { Alert, AlertDescription } from "@/components/ui/alert"; import { Dialog, DialogContent, @@ -39,8 +37,8 @@ export function ChangeVersionDialog({ const [releaseTypes, setReleaseTypes] = useState({}); const [isLoadingReleaseTypes, setIsLoadingReleaseTypes] = useState(false); const [isUpdating, setIsUpdating] = useState(false); - const [showDowngradeWarning, setShowDowngradeWarning] = useState(false); - const [acknowledgeDowngrade, setAcknowledgeDowngrade] = useState(false); + // Nightly switching is disabled for non-nightly profiles (except Firefox Developer), + // so downgrade warnings are no longer applicable. const { downloadedVersions, @@ -50,38 +48,36 @@ export function ChangeVersionDialog({ isVersionDownloaded, } = useBrowserDownload(); - const loadReleaseTypes = useCallback(async (browser: string) => { - setIsLoadingReleaseTypes(true); - try { - const releaseTypes = await invoke( - "get_browser_release_types", - { browserStr: browser }, - ); - setReleaseTypes(releaseTypes); - } catch (error) { - console.error("Failed to load release types:", error); - } finally { - setIsLoadingReleaseTypes(false); - } - }, []); - - useEffect(() => { - if ( - profile && - selectedReleaseType && - selectedReleaseType !== profile.release_type - ) { - // For simplicity, we'll show downgrade warning when switching from stable to nightly - // since nightly versions might be considered "downgrades" in terms of stability - const isDowngrade = - profile.release_type === "stable" && selectedReleaseType === "nightly"; - setShowDowngradeWarning(isDowngrade); - - if (!isDowngrade) { - setAcknowledgeDowngrade(false); + const loadReleaseTypes = useCallback( + async (browser: string) => { + setIsLoadingReleaseTypes(true); + try { + const releaseTypes = await invoke( + "get_browser_release_types", + { browserStr: browser }, + ); + // Filter nightly visibility based on rules: + // - Firefox Developer Edition: allow nightly only + // - If profile is currently nightly: allow both stable and nightly + // - Otherwise: allow stable only + const filtered: BrowserReleaseTypes = {}; + if (profile?.browser === "firefox-developer") { + if (releaseTypes.nightly) filtered.nightly = releaseTypes.nightly; + } else if (profile?.release_type === "nightly") { + if (releaseTypes.stable) filtered.stable = releaseTypes.stable; + if (releaseTypes.nightly) filtered.nightly = releaseTypes.nightly; + } else { + if (releaseTypes.stable) filtered.stable = releaseTypes.stable; + } + setReleaseTypes(filtered); + } catch (error) { + console.error("Failed to load release types:", error); + } finally { + setIsLoadingReleaseTypes(false); } - } - }, [selectedReleaseType, profile]); + }, + [profile?.browser, profile?.release_type], + ); const handleDownload = useCallback(async () => { if (!profile || !selectedReleaseType) return; @@ -129,14 +125,12 @@ export function ChangeVersionDialog({ selectedReleaseType && selectedReleaseType !== profile.release_type && selectedVersion && - isVersionDownloaded(selectedVersion) && - (!showDowngradeWarning || acknowledgeDowngrade); + isVersionDownloaded(selectedVersion); useEffect(() => { if (isOpen && profile) { // Set current release type based on profile setSelectedReleaseType(profile.release_type as "stable" | "nightly"); - setAcknowledgeDowngrade(false); void loadReleaseTypes(profile.browser); void loadDownloadedVersions(profile.browser); } @@ -206,7 +200,6 @@ export function ChangeVersionDialog({ selectedReleaseType={selectedReleaseType} onReleaseTypeSelect={setSelectedReleaseType} availableReleaseTypes={releaseTypes} - browser={profile.browser} isDownloading={isBrowserDownloading(profile.browser)} onDownload={() => { void handleDownload(); @@ -246,7 +239,6 @@ export function ChangeVersionDialog({ selectedReleaseType={selectedReleaseType} onReleaseTypeSelect={setSelectedReleaseType} availableReleaseTypes={releaseTypes} - browser={profile.browser} isDownloading={isBrowserDownloading(profile.browser)} onDownload={() => { void handleDownload(); @@ -259,32 +251,7 @@ export function ChangeVersionDialog({ )} - {/* Downgrade Warning */} - {showDowngradeWarning && ( - - - - Stability Warning - - - You are about to switch from stable to nightly releases. Nightly - versions may be less stable and could contain bugs or incomplete - features. -
- { - setAcknowledgeDowngrade(checked as boolean); - }} - /> - -
-
-
- )} + {/* Nightly switching disabled in UI; no downgrade warning needed. */} diff --git a/src/components/create-profile-dialog.tsx b/src/components/create-profile-dialog.tsx index ace563a..127923a 100644 --- a/src/components/create-profile-dialog.tsx +++ b/src/components/create-profile-dialog.tsx @@ -191,10 +191,19 @@ export function CreateProfileDialog({ // Only update state if this browser is still the one we're loading if (loadingBrowserRef.current === browser) { + // Filter to enforce stable-only creation, except Firefox Developer (nightly-only) if (browser === "camoufox") { - setCamoufoxReleaseTypes(releaseTypes); + const filtered: BrowserReleaseTypes = {}; + if (releaseTypes.stable) filtered.stable = releaseTypes.stable; + setCamoufoxReleaseTypes(filtered); + } else if (browser === "firefox-developer") { + const filtered: BrowserReleaseTypes = {}; + if (releaseTypes.nightly) filtered.nightly = releaseTypes.nightly; + setAvailableReleaseTypes(filtered); } else { - setAvailableReleaseTypes(releaseTypes); + const filtered: BrowserReleaseTypes = {}; + if (releaseTypes.stable) filtered.stable = releaseTypes.stable; + setAvailableReleaseTypes(filtered); } // Load downloaded versions for this browser @@ -241,26 +250,20 @@ export function CreateProfileDialog({ } }, [selectedBrowser, loadReleaseTypes]); - // Helper function to get the best available version and release type + // Helper function to get the best available version respecting rules const getBestAvailableVersion = useCallback( (releaseTypes: BrowserReleaseTypes, browserType?: string) => { - // For Firefox Developer Edition, prefer nightly over stable + // Firefox Developer Edition: nightly-only if (browserType === "firefox-developer" && releaseTypes.nightly) { return { version: releaseTypes.nightly, releaseType: "nightly" as const, }; } - + // All others: stable-only if (releaseTypes.stable) { return { version: releaseTypes.stable, releaseType: "stable" as const }; } - if (releaseTypes.nightly) { - return { - version: releaseTypes.nightly, - releaseType: "nightly" as const, - }; - } return null; }, [], @@ -438,8 +441,11 @@ export function CreateProfileDialog({ - supportedBrowsers.includes(browser.value), + .filter( + (browser) => + supportedBrowsers.includes(browser.value) && + browser.value !== "mullvad-browser" && + browser.value !== "tor-browser", ) .map((browser) => { const IconComponent = getBrowserIcon(browser.value); @@ -473,7 +479,7 @@ export function CreateProfileDialog({ availableReleaseTypes, selectedBrowser, ); - return `${bestVersion?.releaseType === "stable" ? "Latest stable" : "Latest nightly"} version (${bestVersion?.version}) needs to be downloaded`; + return `Latest version (${bestVersion?.version}) needs to be downloaded`; })()}

)} @@ -509,7 +515,7 @@ export function CreateProfileDialog({ availableReleaseTypes, selectedBrowser, ); - return `Downloading ${bestVersion?.releaseType === "stable" ? "stable" : "nightly"} version (${bestVersion?.version})...`; + return `Downloading version (${bestVersion?.version})...`; })()} )} @@ -534,7 +540,7 @@ export function CreateProfileDialog({ camoufoxReleaseTypes, "camoufox", ); - return `Camoufox ${bestVersion?.releaseType} version (${bestVersion?.version}) needs to be downloaded`; + return `Camoufox version (${bestVersion?.version}) needs to be downloaded`; })()}

)} @@ -570,7 +576,7 @@ export function CreateProfileDialog({ camoufoxReleaseTypes, "camoufox", ); - return `Downloading Camoufox ${bestVersion?.releaseType} version (${bestVersion?.version})...`; + return `Downloading Camoufox version (${bestVersion?.version})...`; })()} )} diff --git a/src/components/profile-data-table.tsx b/src/components/profile-data-table.tsx index 24f588c..62069f6 100644 --- a/src/components/profile-data-table.tsx +++ b/src/components/profile-data-table.tsx @@ -666,36 +666,6 @@ export function ProfilesDataTable({ ); }, }, - { - accessorKey: "release_type", - header: "Release", - cell: ({ row }) => { - const releaseType: string = row.getValue("release_type"); - const isNightly = releaseType === "nightly"; - return ( -
- - {isNightly ? "Nightly" : "Stable"} - -
- ); - }, - enableSorting: true, - sortingFn: (rowA, rowB, columnId) => { - const releaseA: string = rowA.getValue(columnId); - const releaseB: string = rowB.getValue(columnId); - // Sort with "stable" before "nightly" - if (releaseA === "stable" && releaseB === "nightly") return -1; - if (releaseA === "nightly" && releaseB === "stable") return 1; - return 0; - }, - }, { id: "proxy", header: "Proxy", diff --git a/src/components/release-type-selector.tsx b/src/components/release-type-selector.tsx index 82202a6..3e811ac 100644 --- a/src/components/release-type-selector.tsx +++ b/src/components/release-type-selector.tsx @@ -24,7 +24,6 @@ interface ReleaseTypeSelectorProps { selectedReleaseType: "stable" | "nightly" | null; onReleaseTypeSelect: (releaseType: "stable" | "nightly" | null) => void; availableReleaseTypes: BrowserReleaseTypes; - browser: string; isDownloading: boolean; onDownload: () => void; placeholder?: string; @@ -36,7 +35,7 @@ export function ReleaseTypeSelector({ selectedReleaseType, onReleaseTypeSelect, availableReleaseTypes, - browser, + // browser prop removed; callers control availableReleaseTypes isDownloading, onDownload, placeholder = "Select release type...", @@ -45,11 +44,13 @@ export function ReleaseTypeSelector({ }: ReleaseTypeSelectorProps) { const [popoverOpen, setPopoverOpen] = useState(false); + // Nightly visibility is controlled by callers. This component will render + // whichever options are provided via availableReleaseTypes. const releaseOptions = [ ...(availableReleaseTypes.stable ? [{ type: "stable" as const, version: availableReleaseTypes.stable }] : []), - ...(availableReleaseTypes.nightly && browser !== "chromium" + ...(availableReleaseTypes.nightly ? [{ type: "nightly" as const, version: availableReleaseTypes.nightly }] : []), ]; @@ -159,11 +160,6 @@ export function ReleaseTypeSelector({ {releaseOptions[0].type} - {releaseOptions[0].type === "nightly" && ( - - Nightly - - )} {releaseOptions[0].version}