mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-09-25 02:40:46 +02:00
refactor: better profile creation flow
This commit is contained in:
@@ -6,7 +6,7 @@ import { GoPlus } from "react-icons/go";
|
|||||||
import { LoadingButton } from "@/components/loading-button";
|
import { LoadingButton } from "@/components/loading-button";
|
||||||
import { ProxyFormDialog } from "@/components/proxy-form-dialog";
|
import { ProxyFormDialog } from "@/components/proxy-form-dialog";
|
||||||
import { SharedCamoufoxConfigForm } from "@/components/shared-camoufox-config-form";
|
import { SharedCamoufoxConfigForm } from "@/components/shared-camoufox-config-form";
|
||||||
import { Combobox } from "@/components/ui/combobox";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
|
||||||
import { useBrowserDownload } from "@/hooks/use-browser-download";
|
import { useBrowserDownload } from "@/hooks/use-browser-download";
|
||||||
import { useProxyEvents } from "@/hooks/use-proxy-events";
|
import { useProxyEvents } from "@/hooks/use-proxy-events";
|
||||||
import { getBrowserIcon } from "@/lib/browser-utils";
|
import { getBrowserIcon } from "@/lib/browser-utils";
|
||||||
@@ -99,28 +100,43 @@ export function CreateProfileDialog({
|
|||||||
selectedGroupId,
|
selectedGroupId,
|
||||||
}: CreateProfileDialogProps) {
|
}: CreateProfileDialogProps) {
|
||||||
const [profileName, setProfileName] = useState("");
|
const [profileName, setProfileName] = useState("");
|
||||||
|
const [currentStep, setCurrentStep] = useState<
|
||||||
|
"browser-selection" | "browser-config"
|
||||||
|
>("browser-selection");
|
||||||
const [activeTab, setActiveTab] = useState("anti-detect");
|
const [activeTab, setActiveTab] = useState("anti-detect");
|
||||||
|
|
||||||
// Regular browser states
|
// Browser selection states
|
||||||
const [selectedBrowser, setSelectedBrowser] =
|
const [selectedBrowser, setSelectedBrowser] =
|
||||||
useState<BrowserTypeString | null>("camoufox");
|
useState<BrowserTypeString | null>(null);
|
||||||
const [selectedProxyId, setSelectedProxyId] = useState<string>();
|
const [selectedProxyId, setSelectedProxyId] = useState<string>();
|
||||||
|
|
||||||
const handleTabChange = (value: string) => {
|
|
||||||
if (value === "regular") {
|
|
||||||
setSelectedBrowser("firefox");
|
|
||||||
} else if (value === "anti-detect") {
|
|
||||||
setSelectedBrowser("camoufox");
|
|
||||||
}
|
|
||||||
|
|
||||||
setActiveTab(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Camoufox anti-detect states
|
// Camoufox anti-detect states
|
||||||
const [camoufoxConfig, setCamoufoxConfig] = useState<CamoufoxConfig>({
|
const [camoufoxConfig, setCamoufoxConfig] = useState<CamoufoxConfig>({
|
||||||
geoip: true, // Default to automatic geoip
|
geoip: true, // Default to automatic geoip
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle browser selection from the initial screen
|
||||||
|
const handleBrowserSelect = (browser: BrowserTypeString) => {
|
||||||
|
setSelectedBrowser(browser);
|
||||||
|
setCurrentStep("browser-config");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle back button
|
||||||
|
const handleBack = () => {
|
||||||
|
setCurrentStep("browser-selection");
|
||||||
|
setSelectedBrowser(null);
|
||||||
|
setProfileName("");
|
||||||
|
setSelectedProxyId(undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTabChange = (value: string) => {
|
||||||
|
setActiveTab(value);
|
||||||
|
setCurrentStep("browser-selection");
|
||||||
|
setSelectedBrowser(null);
|
||||||
|
setProfileName("");
|
||||||
|
setSelectedProxyId(undefined);
|
||||||
|
};
|
||||||
|
|
||||||
const [supportedBrowsers, setSupportedBrowsers] = useState<string[]>([]);
|
const [supportedBrowsers, setSupportedBrowsers] = useState<string[]>([]);
|
||||||
const { storedProxies } = useProxyEvents();
|
const { storedProxies } = useProxyEvents();
|
||||||
const [showProxyForm, setShowProxyForm] = useState(false);
|
const [showProxyForm, setShowProxyForm] = useState(false);
|
||||||
@@ -227,15 +243,15 @@ export function CreateProfileDialog({
|
|||||||
// Load data when dialog opens
|
// Load data when dialog opens
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
// Ensure we have a selected browser
|
|
||||||
if (!selectedBrowser) {
|
|
||||||
setSelectedBrowser("camoufox");
|
|
||||||
}
|
|
||||||
void loadSupportedBrowsers();
|
void loadSupportedBrowsers();
|
||||||
// Load camoufox release types when dialog opens
|
// Load release types when a browser is selected
|
||||||
void loadReleaseTypes(selectedBrowser || "camoufox");
|
if (selectedBrowser) {
|
||||||
|
void loadReleaseTypes(selectedBrowser);
|
||||||
|
}
|
||||||
// Check and download GeoIP database if needed for Camoufox
|
// Check and download GeoIP database if needed for Camoufox
|
||||||
void checkAndDownloadGeoIPDatabase();
|
if (selectedBrowser === "camoufox") {
|
||||||
|
void checkAndDownloadGeoIPDatabase();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
isOpen,
|
isOpen,
|
||||||
@@ -297,7 +313,29 @@ export function CreateProfileDialog({
|
|||||||
|
|
||||||
setIsCreating(true);
|
setIsCreating(true);
|
||||||
try {
|
try {
|
||||||
if (activeTab === "regular") {
|
if (activeTab === "anti-detect") {
|
||||||
|
// Anti-detect browser - always use Camoufox with best available version
|
||||||
|
const bestCamoufoxVersion = getBestAvailableVersion("camoufox");
|
||||||
|
if (!bestCamoufoxVersion) {
|
||||||
|
console.error("No Camoufox version available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The fingerprint will be generated at launch time by the Rust backend
|
||||||
|
// We don't need to generate it here during profile creation
|
||||||
|
const finalCamoufoxConfig = { ...camoufoxConfig };
|
||||||
|
|
||||||
|
await onCreateProfile({
|
||||||
|
name: profileName.trim(),
|
||||||
|
browserStr: "camoufox" as BrowserTypeString,
|
||||||
|
version: bestCamoufoxVersion.version,
|
||||||
|
releaseType: bestCamoufoxVersion.releaseType,
|
||||||
|
proxyId: selectedProxyId,
|
||||||
|
camoufoxConfig: finalCamoufoxConfig,
|
||||||
|
groupId: selectedGroupId !== "default" ? selectedGroupId : undefined,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Regular browser
|
||||||
if (!selectedBrowser) {
|
if (!selectedBrowser) {
|
||||||
console.error("Missing required browser selection");
|
console.error("Missing required browser selection");
|
||||||
return;
|
return;
|
||||||
@@ -318,27 +356,6 @@ export function CreateProfileDialog({
|
|||||||
proxyId: selectedProxyId,
|
proxyId: selectedProxyId,
|
||||||
groupId: selectedGroupId !== "default" ? selectedGroupId : undefined,
|
groupId: selectedGroupId !== "default" ? selectedGroupId : undefined,
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
// Anti-detect tab - always use Camoufox with best available version
|
|
||||||
const bestCamoufoxVersion = getBestAvailableVersion("camoufox");
|
|
||||||
if (!bestCamoufoxVersion) {
|
|
||||||
console.error("No Camoufox version available");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The fingerprint will be generated at launch time by the Rust backend
|
|
||||||
// We don't need to generate it here during profile creation
|
|
||||||
const finalCamoufoxConfig = { ...camoufoxConfig };
|
|
||||||
|
|
||||||
await onCreateProfile({
|
|
||||||
name: profileName.trim(),
|
|
||||||
browserStr: "camoufox" as BrowserTypeString,
|
|
||||||
version: bestCamoufoxVersion.version,
|
|
||||||
releaseType: bestCamoufoxVersion.releaseType,
|
|
||||||
proxyId: selectedProxyId,
|
|
||||||
camoufoxConfig: finalCamoufoxConfig,
|
|
||||||
groupId: selectedGroupId !== "default" ? selectedGroupId : undefined,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClose();
|
handleClose();
|
||||||
@@ -355,13 +372,14 @@ export function CreateProfileDialog({
|
|||||||
|
|
||||||
// Reset all states
|
// Reset all states
|
||||||
setProfileName("");
|
setProfileName("");
|
||||||
setSelectedBrowser("camoufox"); // Set default browser instead of null
|
setCurrentStep("browser-selection");
|
||||||
|
setActiveTab("anti-detect");
|
||||||
|
setSelectedBrowser(null);
|
||||||
setSelectedProxyId(undefined);
|
setSelectedProxyId(undefined);
|
||||||
setReleaseTypes({});
|
setReleaseTypes({});
|
||||||
setCamoufoxConfig({
|
setCamoufoxConfig({
|
||||||
geoip: true, // Reset to automatic geoip
|
geoip: true, // Reset to automatic geoip
|
||||||
});
|
});
|
||||||
setActiveTab("anti-detect");
|
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -400,11 +418,23 @@ export function CreateProfileDialog({
|
|||||||
isBrowserVersionAvailable,
|
isBrowserVersionAvailable,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Filter supported browsers for regular browsers (excluding mullvad and tor)
|
||||||
|
const regularBrowsers = browserOptions.filter(
|
||||||
|
(browser) =>
|
||||||
|
supportedBrowsers.includes(browser.value) &&
|
||||||
|
browser.value !== "mullvad-browser" &&
|
||||||
|
browser.value !== "tor-browser",
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={handleClose}>
|
<Dialog open={isOpen} onOpenChange={handleClose}>
|
||||||
<DialogContent className="w-full max-h-[90vh] flex flex-col">
|
<DialogContent className="w-full max-h-[90vh] flex flex-col">
|
||||||
<DialogHeader className="flex-shrink-0">
|
<DialogHeader className="flex-shrink-0">
|
||||||
<DialogTitle>Create New Profile</DialogTitle>
|
<DialogTitle>
|
||||||
|
{currentStep === "browser-selection"
|
||||||
|
? "Create New Profile"
|
||||||
|
: "Configure Profile"}
|
||||||
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<Tabs
|
<Tabs
|
||||||
@@ -420,213 +450,428 @@ export function CreateProfileDialog({
|
|||||||
<TabsTrigger value="regular">Regular</TabsTrigger>
|
<TabsTrigger value="regular">Regular</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<ScrollArea className="flex-1 h-[330px] overflow-y-hidden">
|
<ScrollArea className="overflow-y-auto flex-1">
|
||||||
<div className="flex flex-col justify-center items-center w-full">
|
<div className="flex flex-col justify-center items-center w-full">
|
||||||
<div className="py-4 space-y-6 w-full max-w-md">
|
<div className="py-4 space-y-6 w-full max-w-md">
|
||||||
{/* Profile Name - Common to both tabs */}
|
{currentStep === "browser-selection" ? (
|
||||||
<div className="space-y-2">
|
<>
|
||||||
<Label htmlFor="profile-name">Profile Name</Label>
|
<TabsContent value="anti-detect" className="mt-0 space-y-6">
|
||||||
<Input
|
{/* Anti-Detect Browser Selection */}
|
||||||
id="profile-name"
|
<div className="space-y-6">
|
||||||
value={profileName}
|
<div className="text-center">
|
||||||
onChange={(e) => setProfileName(e.target.value)}
|
<h3 className="text-lg font-medium">
|
||||||
placeholder="Enter profile name"
|
Anti-Detect Browser
|
||||||
/>
|
</h3>
|
||||||
</div>
|
<p className="mt-2 text-sm text-muted-foreground">
|
||||||
|
Choose Firefox for anti-detection capabilities
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<TabsContent value="regular" className="mt-0 space-y-6">
|
<Button
|
||||||
<div className="space-y-4">
|
onClick={() => handleBrowserSelect("camoufox")}
|
||||||
<div className="space-y-2">
|
className="flex gap-3 justify-start items-center p-4 w-full h-16 border-2 transition-colors hover:border-primary/50"
|
||||||
<Label>Browser</Label>
|
variant="outline"
|
||||||
<Combobox
|
>
|
||||||
options={browserOptions
|
<div className="flex justify-center items-center w-8 h-8">
|
||||||
.filter(
|
|
||||||
(browser) =>
|
|
||||||
supportedBrowsers.includes(browser.value) &&
|
|
||||||
browser.value !== "mullvad-browser" &&
|
|
||||||
browser.value !== "tor-browser",
|
|
||||||
)
|
|
||||||
.map((browser) => {
|
|
||||||
const IconComponent = getBrowserIcon(browser.value);
|
|
||||||
return {
|
|
||||||
value: browser.value,
|
|
||||||
label: browser.label,
|
|
||||||
icon: IconComponent,
|
|
||||||
};
|
|
||||||
})}
|
|
||||||
value={selectedBrowser || ""}
|
|
||||||
onValueChange={(value) =>
|
|
||||||
setSelectedBrowser(value as BrowserTypeString)
|
|
||||||
}
|
|
||||||
placeholder="Select a browser..."
|
|
||||||
searchPlaceholder="Search browsers..."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{selectedBrowser && (
|
|
||||||
<div className="space-y-3">
|
|
||||||
{!isBrowserCurrentlyDownloading(selectedBrowser) &&
|
|
||||||
!isBrowserVersionAvailable(selectedBrowser) &&
|
|
||||||
getBestAvailableVersion(selectedBrowser) && (
|
|
||||||
<div className="flex gap-3 items-center">
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{(() => {
|
|
||||||
const bestVersion =
|
|
||||||
getBestAvailableVersion(selectedBrowser);
|
|
||||||
return `Latest version (${bestVersion?.version}) needs to be downloaded`;
|
|
||||||
})()}
|
|
||||||
</p>
|
|
||||||
<LoadingButton
|
|
||||||
onClick={() => handleDownload(selectedBrowser)}
|
|
||||||
isLoading={isBrowserCurrentlyDownloading(
|
|
||||||
selectedBrowser,
|
|
||||||
)}
|
|
||||||
className="ml-auto"
|
|
||||||
size="sm"
|
|
||||||
disabled={isBrowserCurrentlyDownloading(
|
|
||||||
selectedBrowser,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
Download
|
|
||||||
</LoadingButton>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!isBrowserCurrentlyDownloading(selectedBrowser) &&
|
|
||||||
isBrowserVersionAvailable(selectedBrowser) && (
|
|
||||||
<div className="text-sm text-muted-foreground">
|
|
||||||
{(() => {
|
|
||||||
const bestVersion =
|
|
||||||
getBestAvailableVersion(selectedBrowser);
|
|
||||||
return `✓ Latest version (${bestVersion?.version}) is available`;
|
|
||||||
})()}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{isBrowserCurrentlyDownloading(selectedBrowser) && (
|
|
||||||
<div className="text-sm text-muted-foreground">
|
|
||||||
{(() => {
|
{(() => {
|
||||||
const bestVersion =
|
const IconComponent = getBrowserIcon("firefox");
|
||||||
getBestAvailableVersion(selectedBrowser);
|
return IconComponent ? (
|
||||||
return `Downloading version (${bestVersion?.version})...`;
|
<IconComponent className="w-6 h-6" />
|
||||||
|
) : null;
|
||||||
})()}
|
})()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className="text-left">
|
||||||
|
<div className="font-medium">Firefox</div>
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
Anti-Detect Browser
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</TabsContent>
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="anti-detect" className="mt-0 space-y-6">
|
<TabsContent value="regular" className="mt-0 space-y-6">
|
||||||
<div className="space-y-6">
|
{/* Regular Browser Selection */}
|
||||||
{/* Camoufox Download Status */}
|
<div className="space-y-6">
|
||||||
{!isBrowserCurrentlyDownloading("camoufox") &&
|
<div className="text-center">
|
||||||
!isBrowserVersionAvailable("camoufox") &&
|
<h3 className="text-lg font-medium">
|
||||||
getBestAvailableVersion("camoufox") && (
|
Regular Browsers
|
||||||
<div className="flex gap-3 items-center p-3 rounded-md border">
|
</h3>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="mt-2 text-sm text-muted-foreground">
|
||||||
{(() => {
|
Choose from supported regular browsers
|
||||||
const bestVersion =
|
|
||||||
getBestAvailableVersion("camoufox");
|
|
||||||
return `Camoufox version (${bestVersion?.version}) needs to be downloaded`;
|
|
||||||
})()}
|
|
||||||
</p>
|
</p>
|
||||||
<LoadingButton
|
|
||||||
onClick={() => handleDownload("camoufox")}
|
|
||||||
isLoading={isBrowserCurrentlyDownloading(
|
|
||||||
"camoufox",
|
|
||||||
)}
|
|
||||||
size="sm"
|
|
||||||
disabled={isBrowserCurrentlyDownloading("camoufox")}
|
|
||||||
>
|
|
||||||
{isBrowserCurrentlyDownloading("camoufox")
|
|
||||||
? "Downloading..."
|
|
||||||
: "Download"}
|
|
||||||
</LoadingButton>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
{!isBrowserCurrentlyDownloading("camoufox") &&
|
<div className="space-y-3">
|
||||||
isBrowserVersionAvailable("camoufox") && (
|
{regularBrowsers.map((browser) => {
|
||||||
<div className="p-3 text-sm rounded-md border text-muted-foreground">
|
if (browser.value === "camoufox") return null; // Skip camoufox as it's handled in anti-detect tab
|
||||||
{(() => {
|
const IconComponent = getBrowserIcon(browser.value);
|
||||||
const bestVersion =
|
return (
|
||||||
getBestAvailableVersion("camoufox");
|
<Button
|
||||||
return `✓ Camoufox version (${bestVersion?.version}) is available`;
|
key={browser.value}
|
||||||
})()}
|
onClick={() =>
|
||||||
|
handleBrowserSelect(browser.value)
|
||||||
|
}
|
||||||
|
className="flex gap-3 justify-start items-center p-4 w-full h-16 border-2 transition-colors hover:border-primary/50"
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
<div className="flex justify-center items-center w-8 h-8">
|
||||||
|
{IconComponent && (
|
||||||
|
<IconComponent className="w-6 h-6" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="text-left">
|
||||||
|
<div className="font-medium">
|
||||||
|
{browser.label}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
Regular Browser
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
{isBrowserCurrentlyDownloading("camoufox") && (
|
|
||||||
<div className="p-3 text-sm rounded-md border text-muted-foreground">
|
|
||||||
{(() => {
|
|
||||||
const bestVersion =
|
|
||||||
getBestAvailableVersion("camoufox");
|
|
||||||
return `Downloading Camoufox version (${bestVersion?.version})...`;
|
|
||||||
})()}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</TabsContent>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<TabsContent value="anti-detect" className="mt-0">
|
||||||
|
{/* Anti-Detect Configuration */}
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Profile Name */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="profile-name">Profile Name</Label>
|
||||||
|
<Input
|
||||||
|
id="profile-name"
|
||||||
|
value={profileName}
|
||||||
|
onChange={(e) => setProfileName(e.target.value)}
|
||||||
|
placeholder="Enter profile name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<SharedCamoufoxConfigForm
|
{selectedBrowser === "camoufox" ? (
|
||||||
config={camoufoxConfig}
|
// Camoufox Configuration
|
||||||
onConfigChange={updateCamoufoxConfig}
|
<div className="space-y-6">
|
||||||
isCreating
|
{/* Camoufox Download Status */}
|
||||||
/>
|
{!isBrowserCurrentlyDownloading("camoufox") &&
|
||||||
</div>
|
!isBrowserVersionAvailable("camoufox") &&
|
||||||
</TabsContent>
|
getBestAvailableVersion("camoufox") && (
|
||||||
|
<div className="flex gap-3 items-center p-3 rounded-md border">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{(() => {
|
||||||
|
const bestVersion =
|
||||||
|
getBestAvailableVersion("camoufox");
|
||||||
|
return `Camoufox version (${bestVersion?.version}) needs to be downloaded`;
|
||||||
|
})()}
|
||||||
|
</p>
|
||||||
|
<LoadingButton
|
||||||
|
onClick={() => handleDownload("camoufox")}
|
||||||
|
isLoading={isBrowserCurrentlyDownloading(
|
||||||
|
"camoufox",
|
||||||
|
)}
|
||||||
|
size="sm"
|
||||||
|
disabled={isBrowserCurrentlyDownloading(
|
||||||
|
"camoufox",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{isBrowserCurrentlyDownloading("camoufox")
|
||||||
|
? "Downloading..."
|
||||||
|
: "Download"}
|
||||||
|
</LoadingButton>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!isBrowserCurrentlyDownloading("camoufox") &&
|
||||||
|
isBrowserVersionAvailable("camoufox") && (
|
||||||
|
<div className="p-3 text-sm rounded-md border text-muted-foreground">
|
||||||
|
{(() => {
|
||||||
|
const bestVersion =
|
||||||
|
getBestAvailableVersion("camoufox");
|
||||||
|
return `✓ Camoufox version (${bestVersion?.version}) is available`;
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{isBrowserCurrentlyDownloading("camoufox") && (
|
||||||
|
<div className="p-3 text-sm rounded-md border text-muted-foreground">
|
||||||
|
{(() => {
|
||||||
|
const bestVersion =
|
||||||
|
getBestAvailableVersion("camoufox");
|
||||||
|
return `Downloading Camoufox version (${bestVersion?.version})...`;
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Proxy Selection - Common to both tabs - Always visible */}
|
<SharedCamoufoxConfigForm
|
||||||
<div className="space-y-3">
|
config={camoufoxConfig}
|
||||||
<div className="flex justify-between items-center">
|
onConfigChange={updateCamoufoxConfig}
|
||||||
<Label>Proxy</Label>
|
isCreating
|
||||||
<RippleButton
|
/>
|
||||||
size="sm"
|
</div>
|
||||||
variant="outline"
|
) : (
|
||||||
onClick={() => setShowProxyForm(true)}
|
// Regular Browser Configuration
|
||||||
className="px-2 h-7 text-xs"
|
<div className="space-y-4">
|
||||||
>
|
{selectedBrowser && (
|
||||||
<GoPlus className="mr-1 w-3 h-3" /> Add Proxy
|
<div className="space-y-3">
|
||||||
</RippleButton>
|
{!isBrowserCurrentlyDownloading(
|
||||||
</div>
|
selectedBrowser,
|
||||||
{storedProxies.length > 0 ? (
|
) &&
|
||||||
<Select
|
!isBrowserVersionAvailable(selectedBrowser) &&
|
||||||
value={selectedProxyId || "none"}
|
getBestAvailableVersion(selectedBrowser) && (
|
||||||
onValueChange={(value) =>
|
<div className="flex gap-3 items-center">
|
||||||
setSelectedProxyId(value === "none" ? undefined : value)
|
<p className="text-sm text-muted-foreground">
|
||||||
}
|
{(() => {
|
||||||
>
|
const bestVersion =
|
||||||
<SelectTrigger>
|
getBestAvailableVersion(
|
||||||
<SelectValue placeholder="No proxy" />
|
selectedBrowser,
|
||||||
</SelectTrigger>
|
);
|
||||||
<SelectContent>
|
return `Latest version (${bestVersion?.version}) needs to be downloaded`;
|
||||||
<SelectItem value="none">No proxy</SelectItem>
|
})()}
|
||||||
{storedProxies.map((proxy) => (
|
</p>
|
||||||
<SelectItem key={proxy.id} value={proxy.id}>
|
<LoadingButton
|
||||||
{proxy.name}
|
onClick={() =>
|
||||||
</SelectItem>
|
handleDownload(selectedBrowser)
|
||||||
))}
|
}
|
||||||
</SelectContent>
|
isLoading={isBrowserCurrentlyDownloading(
|
||||||
</Select>
|
selectedBrowser,
|
||||||
) : (
|
)}
|
||||||
<div className="flex gap-3 items-center p-3 text-sm rounded-md border text-muted-foreground">
|
className="ml-auto"
|
||||||
No proxies available. Add one to route this profile's
|
size="sm"
|
||||||
traffic.
|
disabled={isBrowserCurrentlyDownloading(
|
||||||
</div>
|
selectedBrowser,
|
||||||
)}
|
)}
|
||||||
</div>
|
>
|
||||||
|
Download
|
||||||
|
</LoadingButton>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!isBrowserCurrentlyDownloading(
|
||||||
|
selectedBrowser,
|
||||||
|
) &&
|
||||||
|
isBrowserVersionAvailable(
|
||||||
|
selectedBrowser,
|
||||||
|
) && (
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
{(() => {
|
||||||
|
const bestVersion =
|
||||||
|
getBestAvailableVersion(
|
||||||
|
selectedBrowser,
|
||||||
|
);
|
||||||
|
return `✓ Latest version (${bestVersion?.version}) is available`;
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{isBrowserCurrentlyDownloading(
|
||||||
|
selectedBrowser,
|
||||||
|
) && (
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
{(() => {
|
||||||
|
const bestVersion =
|
||||||
|
getBestAvailableVersion(
|
||||||
|
selectedBrowser,
|
||||||
|
);
|
||||||
|
return `Downloading version (${bestVersion?.version})...`;
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Proxy Selection - Always visible */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<Label>Proxy</Label>
|
||||||
|
<RippleButton
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setShowProxyForm(true)}
|
||||||
|
className="px-2 h-7 text-xs"
|
||||||
|
>
|
||||||
|
<GoPlus className="mr-1 w-3 h-3" /> Add Proxy
|
||||||
|
</RippleButton>
|
||||||
|
</div>
|
||||||
|
{storedProxies.length > 0 ? (
|
||||||
|
<Select
|
||||||
|
value={selectedProxyId || "none"}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
setSelectedProxyId(
|
||||||
|
value === "none" ? undefined : value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="No proxy" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="none">No proxy</SelectItem>
|
||||||
|
{storedProxies.map((proxy) => (
|
||||||
|
<SelectItem key={proxy.id} value={proxy.id}>
|
||||||
|
{proxy.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
) : (
|
||||||
|
<div className="flex gap-3 items-center p-3 text-sm rounded-md border text-muted-foreground">
|
||||||
|
No proxies available. Add one to route this
|
||||||
|
profile's traffic.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="regular" className="mt-0">
|
||||||
|
{/* Regular Browser Configuration */}
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Profile Name */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="profile-name">Profile Name</Label>
|
||||||
|
<Input
|
||||||
|
id="profile-name"
|
||||||
|
value={profileName}
|
||||||
|
onChange={(e) => setProfileName(e.target.value)}
|
||||||
|
placeholder="Enter profile name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Regular Browser Configuration */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
{selectedBrowser && (
|
||||||
|
<div className="space-y-3">
|
||||||
|
{!isBrowserCurrentlyDownloading(
|
||||||
|
selectedBrowser,
|
||||||
|
) &&
|
||||||
|
!isBrowserVersionAvailable(selectedBrowser) &&
|
||||||
|
getBestAvailableVersion(selectedBrowser) && (
|
||||||
|
<div className="flex gap-3 items-center">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{(() => {
|
||||||
|
const bestVersion =
|
||||||
|
getBestAvailableVersion(
|
||||||
|
selectedBrowser,
|
||||||
|
);
|
||||||
|
return `Latest version (${bestVersion?.version}) needs to be downloaded`;
|
||||||
|
})()}
|
||||||
|
</p>
|
||||||
|
<LoadingButton
|
||||||
|
onClick={() =>
|
||||||
|
handleDownload(selectedBrowser)
|
||||||
|
}
|
||||||
|
isLoading={isBrowserCurrentlyDownloading(
|
||||||
|
selectedBrowser,
|
||||||
|
)}
|
||||||
|
className="ml-auto"
|
||||||
|
size="sm"
|
||||||
|
disabled={isBrowserCurrentlyDownloading(
|
||||||
|
selectedBrowser,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Download
|
||||||
|
</LoadingButton>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!isBrowserCurrentlyDownloading(
|
||||||
|
selectedBrowser,
|
||||||
|
) &&
|
||||||
|
isBrowserVersionAvailable(selectedBrowser) && (
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
{(() => {
|
||||||
|
const bestVersion =
|
||||||
|
getBestAvailableVersion(
|
||||||
|
selectedBrowser,
|
||||||
|
);
|
||||||
|
return `✓ Latest version (${bestVersion?.version}) is available`;
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{isBrowserCurrentlyDownloading(
|
||||||
|
selectedBrowser,
|
||||||
|
) && (
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
{(() => {
|
||||||
|
const bestVersion =
|
||||||
|
getBestAvailableVersion(selectedBrowser);
|
||||||
|
return `Downloading version (${bestVersion?.version})...`;
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Proxy Selection - Always visible */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<Label>Proxy</Label>
|
||||||
|
<RippleButton
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setShowProxyForm(true)}
|
||||||
|
className="px-2 h-7 text-xs"
|
||||||
|
>
|
||||||
|
<GoPlus className="mr-1 w-3 h-3" /> Add Proxy
|
||||||
|
</RippleButton>
|
||||||
|
</div>
|
||||||
|
{storedProxies.length > 0 ? (
|
||||||
|
<Select
|
||||||
|
value={selectedProxyId || "none"}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
setSelectedProxyId(
|
||||||
|
value === "none" ? undefined : value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="No proxy" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="none">No proxy</SelectItem>
|
||||||
|
{storedProxies.map((proxy) => (
|
||||||
|
<SelectItem key={proxy.id} value={proxy.id}>
|
||||||
|
{proxy.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
) : (
|
||||||
|
<div className="flex gap-3 items-center p-3 text-sm rounded-md border text-muted-foreground">
|
||||||
|
No proxies available. Add one to route this
|
||||||
|
profile's traffic.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
<DialogFooter className="flex-shrink-0 pt-4 border-t">
|
<DialogFooter className="flex-shrink-0 pt-4 border-t">
|
||||||
|
{currentStep === "browser-config" ? (
|
||||||
|
<>
|
||||||
|
<RippleButton variant="outline" onClick={handleBack}>
|
||||||
|
Back
|
||||||
|
</RippleButton>
|
||||||
|
<LoadingButton
|
||||||
|
onClick={handleCreate}
|
||||||
|
isLoading={isCreating}
|
||||||
|
disabled={isCreateDisabled}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</LoadingButton>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
<RippleButton variant="outline" onClick={handleClose}>
|
<RippleButton variant="outline" onClick={handleClose}>
|
||||||
Cancel
|
Cancel
|
||||||
</RippleButton>
|
</RippleButton>
|
||||||
<LoadingButton
|
)}
|
||||||
onClick={handleCreate}
|
</DialogFooter>
|
||||||
isLoading={isCreating}
|
|
||||||
disabled={isCreateDisabled}
|
|
||||||
>
|
|
||||||
Create
|
|
||||||
</LoadingButton>
|
|
||||||
</DialogFooter>
|
|
||||||
</Tabs>
|
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<ProxyFormDialog
|
<ProxyFormDialog
|
||||||
isOpen={showProxyForm}
|
isOpen={showProxyForm}
|
||||||
|
|||||||
Reference in New Issue
Block a user