refactor: better profile creation flow

This commit is contained in:
zhom
2025-09-01 14:40:55 +04:00
parent b133f928d4
commit bc7c8d1a1e
+476 -231
View File
@@ -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}