mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-05-26 18:17:49 +02:00
refactor: cleanup
This commit is contained in:
@@ -233,8 +233,8 @@ export function AccountPage({
|
||||
<TabsContent value="account" className="mt-4">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="grid place-items-center w-12 h-12 rounded-full bg-accent text-foreground shrink-0">
|
||||
<LuUser className="w-6 h-6" />
|
||||
<div className="grid place-items-center size-12 rounded-full bg-accent text-foreground shrink-0">
|
||||
<LuUser className="size-6" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
{isLoggedIn && user ? (
|
||||
@@ -309,7 +309,7 @@ export function AccountPage({
|
||||
disabled={isRefreshing}
|
||||
className="h-8 text-xs gap-1.5"
|
||||
>
|
||||
<LuRefreshCw className="w-3 h-3" />
|
||||
<LuRefreshCw className="size-3" />
|
||||
{t("account.refresh")}
|
||||
</Button>
|
||||
<LoadingButton
|
||||
@@ -322,7 +322,7 @@ export function AccountPage({
|
||||
}}
|
||||
className="h-8 text-xs gap-1.5"
|
||||
>
|
||||
<LuLogOut className="w-3 h-3" />
|
||||
<LuLogOut className="size-3" />
|
||||
{t("account.logout")}
|
||||
</LoadingButton>
|
||||
</>
|
||||
@@ -332,7 +332,7 @@ export function AccountPage({
|
||||
onClick={onOpenSignIn}
|
||||
className="h-8 text-xs gap-1.5"
|
||||
>
|
||||
<LuCloud className="w-3 h-3" />
|
||||
<LuCloud className="size-3" />
|
||||
{t("account.signIn")}
|
||||
</Button>
|
||||
)}
|
||||
@@ -410,9 +410,9 @@ export function AccountPage({
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
{showToken ? (
|
||||
<LuEyeOff className="w-3.5 h-3.5" />
|
||||
<LuEyeOff className="size-3.5" />
|
||||
) : (
|
||||
<LuEye className="w-3.5 h-3.5" />
|
||||
<LuEye className="size-3.5" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -37,7 +37,7 @@ export function AppUpdateToast({
|
||||
return (
|
||||
<div className="flex items-start p-4 w-full max-w-md rounded-lg border shadow-lg bg-card border-border text-card-foreground">
|
||||
<div className="mr-3 mt-0.5">
|
||||
<LuCheckCheck className="flex-shrink-0 w-5 h-5" />
|
||||
<LuCheckCheck className="flex-shrink-0 size-5" />
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
@@ -59,9 +59,9 @@ export function AppUpdateToast({
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={onDismiss}
|
||||
className="p-0 w-6 h-6 shrink-0"
|
||||
className="p-0 size-6 shrink-0"
|
||||
>
|
||||
<FaTimes className="w-3 h-3" />
|
||||
<FaTimes className="size-3" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -72,7 +72,7 @@ export function AppUpdateToast({
|
||||
size="sm"
|
||||
className="flex gap-2 items-center text-xs"
|
||||
>
|
||||
<LuCheckCheck className="w-3 h-3" />
|
||||
<LuCheckCheck className="size-3" />
|
||||
{t("appUpdate.toast.restartNow")}
|
||||
</RippleButton>
|
||||
) : (
|
||||
@@ -83,7 +83,7 @@ export function AppUpdateToast({
|
||||
size="sm"
|
||||
className="flex gap-2 items-center text-xs"
|
||||
>
|
||||
<FaExternalLinkAlt className="w-3 h-3" />
|
||||
<FaExternalLinkAlt className="size-3" />
|
||||
{t("appUpdate.toast.viewRelease")}
|
||||
</RippleButton>
|
||||
)
|
||||
|
||||
@@ -36,16 +36,18 @@ export function CloneProfileDialog({
|
||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isOpen && profile) {
|
||||
const defaultName = `${profile.name} (Copy)`;
|
||||
setName(defaultName);
|
||||
setTimeout(() => {
|
||||
inputRef.current?.focus();
|
||||
inputRef.current?.select();
|
||||
}, 0);
|
||||
} else {
|
||||
if (!(isOpen && profile)) {
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
setName(`${profile.name} (Copy)`);
|
||||
const handle = window.setTimeout(() => {
|
||||
inputRef.current?.focus();
|
||||
inputRef.current?.select();
|
||||
}, 0);
|
||||
return () => {
|
||||
window.clearTimeout(handle);
|
||||
};
|
||||
}, [isOpen, profile]);
|
||||
|
||||
if (!profile) return null;
|
||||
|
||||
@@ -335,7 +335,7 @@ export function CookieCopyDialog({
|
||||
<DialogContent className="max-w-2xl max-h-[80vh] flex flex-col">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<LuCookie className="w-5 h-5" />
|
||||
<LuCookie className="size-5" />
|
||||
{t("cookies.copy.title")}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
@@ -372,7 +372,7 @@ export function CookieCopyDialog({
|
||||
disabled={isRunning}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{IconComponent && <IconComponent className="w-4 h-4" />}
|
||||
{IconComponent && <IconComponent className="size-4" />}
|
||||
<span>{profile.name}</span>
|
||||
{isRunning && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
@@ -437,7 +437,7 @@ export function CookieCopyDialog({
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<LuSearch className="absolute left-2 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
||||
<LuSearch className="absolute left-2 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder={t("cookies.copy.searchPlaceholder")}
|
||||
value={searchQuery}
|
||||
@@ -450,7 +450,7 @@ export function CookieCopyDialog({
|
||||
|
||||
{isLoadingCookies ? (
|
||||
<div className="flex items-center justify-center h-40">
|
||||
<div className="animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full" />
|
||||
<div className="animate-spin size-6 border-2 border-primary border-t-transparent rounded-full" />
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="p-4 text-center text-destructive bg-destructive/10 rounded-md">
|
||||
@@ -565,9 +565,9 @@ function DomainRow({
|
||||
}}
|
||||
>
|
||||
{isExpanded ? (
|
||||
<LuChevronDown className="w-4 h-4" />
|
||||
<LuChevronDown className="size-4" />
|
||||
) : (
|
||||
<LuChevronRight className="w-4 h-4" />
|
||||
<LuChevronRight className="size-4" />
|
||||
)}
|
||||
<span className="font-medium">{domain.domain}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
|
||||
@@ -429,7 +429,7 @@ export function CookieManagementDialog({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LuUpload className="w-10 h-10 text-muted-foreground mb-4" />
|
||||
<LuUpload className="size-10 text-muted-foreground mb-4" />
|
||||
<p className="text-sm text-muted-foreground text-center">
|
||||
{t("cookies.management.dropPrompt")}
|
||||
<br />
|
||||
@@ -556,7 +556,7 @@ export function CookieManagementDialog({
|
||||
|
||||
{isLoadingExportCookies ? (
|
||||
<div className="flex items-center justify-center h-24">
|
||||
<div className="animate-spin h-5 w-5 border-2 border-primary border-t-transparent rounded-full" />
|
||||
<div className="animate-spin size-5 border-2 border-primary border-t-transparent rounded-full" />
|
||||
</div>
|
||||
) : !exportCookieData || exportCookieData.domains.length === 0 ? (
|
||||
<div className="p-4 text-center text-sm text-muted-foreground border rounded-md">
|
||||
@@ -645,9 +645,9 @@ function ExportDomainRow({
|
||||
}}
|
||||
>
|
||||
{isExpanded ? (
|
||||
<LuChevronDown className="w-3.5 h-3.5" />
|
||||
<LuChevronDown className="size-3.5" />
|
||||
) : (
|
||||
<LuChevronRight className="w-3.5 h-3.5" />
|
||||
<LuChevronRight className="size-3.5" />
|
||||
)}
|
||||
<span className="font-medium truncate">{domain.domain}</span>
|
||||
<span className="text-xs text-muted-foreground shrink-0">
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useId,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { GoPlus } from "react-icons/go";
|
||||
import { LuCheck, LuChevronsUpDown } from "react-icons/lu";
|
||||
@@ -116,6 +123,8 @@ export function CreateProfileDialog({
|
||||
crossOsUnlocked = false,
|
||||
}: CreateProfileDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
const proxyListboxIdAntiDetect = useId();
|
||||
const proxyListboxIdRegular = useId();
|
||||
const [profileName, setProfileName] = useState("");
|
||||
const [currentStep, setCurrentStep] = useState<
|
||||
"browser-selection" | "browser-config"
|
||||
@@ -605,11 +614,11 @@ export function CreateProfileDialog({
|
||||
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">
|
||||
<div className="flex justify-center items-center size-8">
|
||||
{(() => {
|
||||
const IconComponent = getBrowserIcon("wayfern");
|
||||
return IconComponent ? (
|
||||
<IconComponent className="w-6 h-6" />
|
||||
<IconComponent className="size-6" />
|
||||
) : null;
|
||||
})()}
|
||||
</div>
|
||||
@@ -631,11 +640,11 @@ export function CreateProfileDialog({
|
||||
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">
|
||||
<div className="flex justify-center items-center size-8">
|
||||
{(() => {
|
||||
const IconComponent = getBrowserIcon("camoufox");
|
||||
return IconComponent ? (
|
||||
<IconComponent className="w-6 h-6" />
|
||||
<IconComponent className="size-6" />
|
||||
) : null;
|
||||
})()}
|
||||
</div>
|
||||
@@ -676,9 +685,9 @@ export function CreateProfileDialog({
|
||||
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">
|
||||
<div className="flex justify-center items-center size-8">
|
||||
{IconComponent && (
|
||||
<IconComponent className="w-6 h-6" />
|
||||
<IconComponent className="size-6" />
|
||||
)}
|
||||
</div>
|
||||
<div className="text-left">
|
||||
@@ -729,7 +738,7 @@ export function CreateProfileDialog({
|
||||
|
||||
{/* Ephemeral Option */}
|
||||
<div className="space-y-3 p-4 border rounded-lg bg-muted/30">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Checkbox
|
||||
id="ephemeral"
|
||||
checked={ephemeral}
|
||||
@@ -749,7 +758,7 @@ export function CreateProfileDialog({
|
||||
{/* Password Option */}
|
||||
{!ephemeral && (
|
||||
<div className="space-y-3 p-4 border rounded-lg bg-muted/30">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Checkbox
|
||||
id="enable-password"
|
||||
checked={enablePassword}
|
||||
@@ -814,7 +823,7 @@ export function CreateProfileDialog({
|
||||
{/* Wayfern Download Status */}
|
||||
{isLoadingReleaseTypes && (
|
||||
<div className="flex gap-3 items-center p-3 rounded-md border">
|
||||
<div className="w-4 h-4 rounded-full border-2 animate-spin border-muted/40 border-t-primary" />
|
||||
<div className="size-4 rounded-full border-2 animate-spin border-muted/40 border-t-primary" />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("createProfile.version.fetching")}
|
||||
</p>
|
||||
@@ -922,7 +931,7 @@ export function CreateProfileDialog({
|
||||
{/* Camoufox Download Status */}
|
||||
{isLoadingReleaseTypes && (
|
||||
<div className="flex gap-3 items-center p-3 rounded-md border">
|
||||
<div className="w-4 h-4 rounded-full border-2 animate-spin border-muted/40 border-t-primary" />
|
||||
<div className="size-4 rounded-full border-2 animate-spin border-muted/40 border-t-primary" />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("createProfile.version.fetching")}
|
||||
</p>
|
||||
@@ -1041,7 +1050,7 @@ export function CreateProfileDialog({
|
||||
<div className="space-y-3">
|
||||
{isLoadingReleaseTypes && (
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="w-4 h-4 rounded-full border-2 animate-spin border-muted/40 border-t-primary" />
|
||||
<div className="size-4 rounded-full border-2 animate-spin border-muted/40 border-t-primary" />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("createProfile.version.fetching")}
|
||||
</p>
|
||||
@@ -1154,7 +1163,7 @@ export function CreateProfileDialog({
|
||||
}}
|
||||
className="px-2 h-7 text-xs"
|
||||
>
|
||||
<GoPlus className="mr-1 w-3 h-3" />{" "}
|
||||
<GoPlus className="mr-1 size-3" />{" "}
|
||||
{t("createProfile.proxy.addProxy")}
|
||||
</RippleButton>
|
||||
</div>
|
||||
@@ -1168,6 +1177,7 @@ export function CreateProfileDialog({
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={proxyPopoverOpen}
|
||||
aria-controls={proxyListboxIdAntiDetect}
|
||||
className="w-full justify-between font-normal"
|
||||
>
|
||||
{(() => {
|
||||
@@ -1190,10 +1200,11 @@ export function CreateProfileDialog({
|
||||
t("createProfile.proxy.noProxy")
|
||||
);
|
||||
})()}
|
||||
<LuChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
<LuChevronsUpDown className="ml-2 size-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
id={proxyListboxIdAntiDetect}
|
||||
className="w-[240px] p-0"
|
||||
sideOffset={8}
|
||||
>
|
||||
@@ -1217,7 +1228,7 @@ export function CreateProfileDialog({
|
||||
>
|
||||
<LuCheck
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
"mr-2 size-4",
|
||||
!selectedProxyId
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
@@ -1236,7 +1247,7 @@ export function CreateProfileDialog({
|
||||
>
|
||||
<LuCheck
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
"mr-2 size-4",
|
||||
selectedProxyId === proxy.id
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
@@ -1261,7 +1272,7 @@ export function CreateProfileDialog({
|
||||
>
|
||||
<LuCheck
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
"mr-2 size-4",
|
||||
selectedProxyId ===
|
||||
`vpn-${vpn.id}`
|
||||
? "opacity-100"
|
||||
@@ -1412,7 +1423,7 @@ export function CreateProfileDialog({
|
||||
<div className="space-y-3">
|
||||
{isLoadingReleaseTypes && (
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="w-4 h-4 rounded-full border-2 animate-spin border-muted/40 border-t-primary" />
|
||||
<div className="size-4 rounded-full border-2 animate-spin border-muted/40 border-t-primary" />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Fetching available versions...
|
||||
</p>
|
||||
@@ -1520,7 +1531,7 @@ export function CreateProfileDialog({
|
||||
}}
|
||||
className="px-2 h-7 text-xs"
|
||||
>
|
||||
<GoPlus className="mr-1 w-3 h-3" />{" "}
|
||||
<GoPlus className="mr-1 size-3" />{" "}
|
||||
{t("createProfile.proxy.addProxy")}
|
||||
</RippleButton>
|
||||
</div>
|
||||
@@ -1534,6 +1545,7 @@ export function CreateProfileDialog({
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={proxyPopoverOpen}
|
||||
aria-controls={proxyListboxIdRegular}
|
||||
className="w-full justify-between font-normal"
|
||||
>
|
||||
{(() => {
|
||||
@@ -1556,10 +1568,11 @@ export function CreateProfileDialog({
|
||||
t("createProfile.proxy.noProxy")
|
||||
);
|
||||
})()}
|
||||
<LuChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
<LuChevronsUpDown className="ml-2 size-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
id={proxyListboxIdRegular}
|
||||
className="w-[240px] p-0"
|
||||
sideOffset={8}
|
||||
>
|
||||
@@ -1583,7 +1596,7 @@ export function CreateProfileDialog({
|
||||
>
|
||||
<LuCheck
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
"mr-2 size-4",
|
||||
!selectedProxyId
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
@@ -1602,7 +1615,7 @@ export function CreateProfileDialog({
|
||||
>
|
||||
<LuCheck
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
"mr-2 size-4",
|
||||
selectedProxyId === proxy.id
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
@@ -1627,7 +1640,7 @@ export function CreateProfileDialog({
|
||||
>
|
||||
<LuCheck
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
"mr-2 size-4",
|
||||
selectedProxyId ===
|
||||
`vpn-${vpn.id}`
|
||||
? "opacity-100"
|
||||
|
||||
@@ -174,42 +174,42 @@ function formatEtaCompact(seconds: number): string {
|
||||
function getToastIcon(type: ToastProps["type"], stage?: string) {
|
||||
switch (type) {
|
||||
case "success":
|
||||
return <LuCheckCheck className="flex-shrink-0 w-4 h-4 text-foreground" />;
|
||||
return <LuCheckCheck className="flex-shrink-0 size-4 text-foreground" />;
|
||||
case "error":
|
||||
return (
|
||||
<LuTriangleAlert className="flex-shrink-0 w-4 h-4 text-foreground" />
|
||||
<LuTriangleAlert className="flex-shrink-0 size-4 text-foreground" />
|
||||
);
|
||||
case "download":
|
||||
if (stage === "completed") {
|
||||
return (
|
||||
<LuCheckCheck className="flex-shrink-0 w-4 h-4 text-foreground" />
|
||||
<LuCheckCheck className="flex-shrink-0 size-4 text-foreground" />
|
||||
);
|
||||
}
|
||||
return <LuDownload className="flex-shrink-0 w-4 h-4 text-foreground" />;
|
||||
return <LuDownload className="flex-shrink-0 size-4 text-foreground" />;
|
||||
|
||||
case "version-update":
|
||||
return (
|
||||
<LuRefreshCw className="flex-shrink-0 w-4 h-4 animate-spin text-foreground" />
|
||||
<LuRefreshCw className="flex-shrink-0 size-4 animate-spin text-foreground" />
|
||||
);
|
||||
case "fetching":
|
||||
return (
|
||||
<LuRefreshCw className="flex-shrink-0 w-4 h-4 animate-spin text-foreground" />
|
||||
<LuRefreshCw className="flex-shrink-0 size-4 animate-spin text-foreground" />
|
||||
);
|
||||
case "twilight-update":
|
||||
return (
|
||||
<LuRefreshCw className="flex-shrink-0 w-4 h-4 animate-spin text-foreground" />
|
||||
<LuRefreshCw className="flex-shrink-0 size-4 animate-spin text-foreground" />
|
||||
);
|
||||
case "sync-progress":
|
||||
return (
|
||||
<LuRefreshCw className="flex-shrink-0 w-4 h-4 animate-spin text-foreground" />
|
||||
<LuRefreshCw className="flex-shrink-0 size-4 animate-spin text-foreground" />
|
||||
);
|
||||
case "loading":
|
||||
return (
|
||||
<div className="flex-shrink-0 w-4 h-4 rounded-full border-2 animate-spin border-foreground border-t-transparent" />
|
||||
<div className="flex-shrink-0 size-4 rounded-full border-2 animate-spin border-foreground border-t-transparent" />
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<div className="flex-shrink-0 w-4 h-4 rounded-full border-2 animate-spin border-foreground border-t-transparent" />
|
||||
<div className="flex-shrink-0 size-4 rounded-full border-2 animate-spin border-foreground border-t-transparent" />
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -235,7 +235,7 @@ export function UnifiedToast(props: ToastProps) {
|
||||
className="ml-2 p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors flex-shrink-0"
|
||||
aria-label={t("common.buttons.cancel")}
|
||||
>
|
||||
<LuX className="w-3 h-3" />
|
||||
<LuX className="size-3" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -272,7 +272,7 @@ export function UnifiedToast(props: ToastProps) {
|
||||
<>Looking for updates for {progress.current_browser}</>
|
||||
)}
|
||||
</p>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<div className="flex-1 bg-muted rounded-full h-1.5 min-w-0">
|
||||
<div
|
||||
className="bg-foreground h-1.5 rounded-full transition-all duration-150"
|
||||
|
||||
@@ -106,7 +106,7 @@ function DataTableActionBarAction({
|
||||
{...props}
|
||||
>
|
||||
{isPending ? (
|
||||
<div className="w-3.5 h-3.5 rounded-full border border-current animate-spin border-t-transparent" />
|
||||
<div className="size-3.5 rounded-full border border-current animate-spin border-t-transparent" />
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
|
||||
@@ -155,13 +155,13 @@ export function DeleteGroupDialog({
|
||||
setDeleteAction(value as "move" | "delete");
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<RadioGroupItem value="move" id="move" />
|
||||
<Label htmlFor="move" className="text-sm">
|
||||
{t("groups.moveToDefault")}
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<RadioGroupItem value="delete" id="delete" />
|
||||
<Label
|
||||
htmlFor="delete"
|
||||
|
||||
@@ -105,7 +105,7 @@ export function DeviceCodeVerifyDialog({
|
||||
disabled={isOpeningLogin}
|
||||
className="w-full gap-1.5"
|
||||
>
|
||||
<LuExternalLink className="w-3.5 h-3.5" />
|
||||
<LuExternalLink className="size-3.5" />
|
||||
{t("sync.cloud.openLogin")}
|
||||
</Button>
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -137,7 +137,7 @@ export function DnsBlocklistDialog({
|
||||
className="w-full"
|
||||
>
|
||||
<LuRefreshCw
|
||||
className={`mr-2 h-4 w-4 ${isRefreshing ? "animate-spin" : ""}`}
|
||||
className={`mr-2 size-4 ${isRefreshing ? "animate-spin" : ""}`}
|
||||
/>
|
||||
{t("dnsBlocklist.refreshAll")}
|
||||
</Button>
|
||||
|
||||
@@ -483,7 +483,7 @@ export function ExtensionManagementDialog({
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="inline-flex">
|
||||
<FaChrome className="w-3.5 h-3.5 text-muted-foreground" />
|
||||
<FaChrome className="size-3.5 text-muted-foreground" />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
@@ -495,7 +495,7 @@ export function ExtensionManagementDialog({
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="inline-flex">
|
||||
<FaFirefox className="w-3.5 h-3.5 text-muted-foreground" />
|
||||
<FaFirefox className="size-3.5 text-muted-foreground" />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
@@ -508,7 +508,7 @@ export function ExtensionManagementDialog({
|
||||
};
|
||||
|
||||
const renderExtensionIcon = (ext: Extension, size: "sm" | "md" = "md") => {
|
||||
const sizeClass = size === "sm" ? "w-4 h-4" : "w-5 h-5";
|
||||
const sizeClass = size === "sm" ? "size-4" : "size-5";
|
||||
if (extensionIcons[ext.id]) {
|
||||
return (
|
||||
// biome-ignore lint/performance/noImgElement: base64 data URI icons cannot use next/image
|
||||
@@ -533,7 +533,7 @@ export function ExtensionManagementDialog({
|
||||
{!subPage && (
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<LuPuzzle className="w-5 h-5" />
|
||||
<LuPuzzle className="size-5" />
|
||||
{t("extensions.title")}
|
||||
{limitedMode && <ProBadge />}
|
||||
</DialogTitle>
|
||||
@@ -615,7 +615,7 @@ export function ExtensionManagementDialog({
|
||||
document.getElementById("ext-file-input")?.click()
|
||||
}
|
||||
>
|
||||
<LuUpload className="w-4 h-4" />
|
||||
<LuUpload className="size-4" />
|
||||
{t("extensions.upload")}
|
||||
</RippleButton>
|
||||
</label>
|
||||
@@ -697,7 +697,7 @@ export function ExtensionManagementDialog({
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full shrink-0 ${syncDot.color} ${
|
||||
className={`size-2 rounded-full shrink-0 ${syncDot.color} ${
|
||||
syncDot.animate ? "animate-pulse" : ""
|
||||
}`}
|
||||
/>
|
||||
@@ -743,14 +743,14 @@ export function ExtensionManagementDialog({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 w-7 p-0"
|
||||
className="size-7 p-0"
|
||||
onClick={() => {
|
||||
setEditingExtension(ext);
|
||||
setEditExtensionName(ext.name);
|
||||
setPendingUpdateFile(null);
|
||||
}}
|
||||
>
|
||||
<LuPencil className="w-3.5 h-3.5" />
|
||||
<LuPencil className="size-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
@@ -762,12 +762,12 @@ export function ExtensionManagementDialog({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 w-7 p-0"
|
||||
className="size-7 p-0"
|
||||
onClick={() => {
|
||||
setExtensionToDelete(ext);
|
||||
}}
|
||||
>
|
||||
<LuTrash2 className="w-3.5 h-3.5" />
|
||||
<LuTrash2 className="size-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
@@ -795,7 +795,7 @@ export function ExtensionManagementDialog({
|
||||
className="flex gap-2 items-center"
|
||||
disabled={limitedMode}
|
||||
>
|
||||
<GoPlus className="w-4 h-4" />
|
||||
<GoPlus className="size-4" />
|
||||
{t("extensions.createGroup")}
|
||||
</RippleButton>
|
||||
</div>
|
||||
@@ -865,7 +865,7 @@ export function ExtensionManagementDialog({
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full shrink-0 ${groupSyncDot.color} ${
|
||||
className={`size-2 rounded-full shrink-0 ${groupSyncDot.color} ${
|
||||
groupSyncDot.animate
|
||||
? "animate-pulse"
|
||||
: ""
|
||||
@@ -956,7 +956,7 @@ export function ExtensionManagementDialog({
|
||||
]);
|
||||
}}
|
||||
>
|
||||
<LuPencil className="w-4 h-4" />
|
||||
<LuPencil className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
@@ -972,7 +972,7 @@ export function ExtensionManagementDialog({
|
||||
setGroupToDelete(group);
|
||||
}}
|
||||
>
|
||||
<LuTrash2 className="w-4 h-4" />
|
||||
<LuTrash2 className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
@@ -1086,14 +1086,14 @@ export function ExtensionManagementDialog({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0 shrink-0"
|
||||
className="size-6 p-0 shrink-0"
|
||||
onClick={() => {
|
||||
setEditGroupExtensionIds((prev) =>
|
||||
prev.filter((id) => id !== extId),
|
||||
);
|
||||
}}
|
||||
>
|
||||
<LuTrash2 className="w-3 h-3" />
|
||||
<LuTrash2 className="size-3" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
@@ -1219,7 +1219,7 @@ export function ExtensionManagementDialog({
|
||||
<span className="truncate">
|
||||
{editingExtension.homepage_url}
|
||||
</span>
|
||||
<LuExternalLink className="w-3 h-3 shrink-0" />
|
||||
<LuExternalLink className="size-3 shrink-0" />
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
@@ -1245,7 +1245,7 @@ export function ExtensionManagementDialog({
|
||||
document.getElementById("ext-edit-file-input")?.click()
|
||||
}
|
||||
>
|
||||
<LuUpload className="w-3 h-3 mr-1" />
|
||||
<LuUpload className="size-3 mr-1" />
|
||||
{t("extensions.selectFile")}
|
||||
</RippleButton>
|
||||
<input
|
||||
|
||||
@@ -165,7 +165,7 @@ export function GroupAssignmentDialog({
|
||||
setCreateDialogOpen(true);
|
||||
}}
|
||||
>
|
||||
<GoPlus className="mr-1 w-3 h-3" />{" "}
|
||||
<GoPlus className="mr-1 size-3" />{" "}
|
||||
{t("groupManagement.createGroup")}
|
||||
</RippleButton>
|
||||
</div>
|
||||
|
||||
@@ -273,7 +273,7 @@ export function GroupManagementDialog({
|
||||
}}
|
||||
className="flex gap-2 items-center"
|
||||
>
|
||||
<GoPlus className="w-4 h-4" />
|
||||
<GoPlus className="size-4" />
|
||||
{t("proxies.management.create")}
|
||||
</RippleButton>
|
||||
</div>
|
||||
@@ -326,7 +326,7 @@ export function GroupManagementDialog({
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full shrink-0 ${syncDot.color} ${
|
||||
className={`size-2 rounded-full shrink-0 ${syncDot.color} ${
|
||||
syncDot.animate ? "animate-pulse" : ""
|
||||
}`}
|
||||
/>
|
||||
@@ -383,7 +383,7 @@ export function GroupManagementDialog({
|
||||
handleEditGroup(group);
|
||||
}}
|
||||
>
|
||||
<LuPencil className="w-4 h-4" />
|
||||
<LuPencil className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
@@ -401,7 +401,7 @@ export function GroupManagementDialog({
|
||||
handleDeleteGroup(group);
|
||||
}}
|
||||
>
|
||||
<LuTrash2 className="w-4 h-4" />
|
||||
<LuTrash2 className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
|
||||
@@ -208,9 +208,9 @@ const HomeHeader = ({
|
||||
behavior: "smooth",
|
||||
});
|
||||
}}
|
||||
className="absolute left-0 top-1/2 -translate-y-1/2 z-10 grid place-items-center w-5 h-5 rounded-full bg-card/90 hover:bg-accent text-muted-foreground hover:text-foreground transition-colors shadow-sm"
|
||||
className="absolute left-0 top-1/2 -translate-y-1/2 z-10 grid place-items-center size-5 rounded-full bg-card/90 hover:bg-accent text-muted-foreground hover:text-foreground transition-colors shadow-sm"
|
||||
>
|
||||
<LuChevronLeft className="w-3 h-3" />
|
||||
<LuChevronLeft className="size-3" />
|
||||
</button>
|
||||
)}
|
||||
<div
|
||||
@@ -283,9 +283,9 @@ const HomeHeader = ({
|
||||
behavior: "smooth",
|
||||
});
|
||||
}}
|
||||
className="absolute right-0 top-1/2 -translate-y-1/2 z-10 grid place-items-center w-5 h-5 rounded-full bg-card/90 hover:bg-accent text-muted-foreground hover:text-foreground transition-colors shadow-sm"
|
||||
className="absolute right-0 top-1/2 -translate-y-1/2 z-10 grid place-items-center size-5 rounded-full bg-card/90 hover:bg-accent text-muted-foreground hover:text-foreground transition-colors shadow-sm"
|
||||
>
|
||||
<LuChevronRight className="w-3 h-3" />
|
||||
<LuChevronRight className="size-3" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -304,7 +304,7 @@ const HomeHeader = ({
|
||||
}}
|
||||
className="pr-7 pl-8 w-52 h-7 text-xs"
|
||||
/>
|
||||
<LuSearch className="absolute left-2.5 top-1/2 w-3.5 h-3.5 transform -translate-y-1/2 text-muted-foreground pointer-events-none" />
|
||||
<LuSearch className="absolute left-2.5 top-1/2 size-3.5 transform -translate-y-1/2 text-muted-foreground pointer-events-none" />
|
||||
{searchQuery ? (
|
||||
<button
|
||||
type="button"
|
||||
@@ -314,7 +314,7 @@ const HomeHeader = ({
|
||||
className="absolute right-1.5 top-1/2 p-0.5 rounded-sm transition-colors transform -translate-y-1/2 hover:bg-accent"
|
||||
aria-label={t("header.clearSearch")}
|
||||
>
|
||||
<LuX className="w-3.5 h-3.5 text-muted-foreground hover:text-foreground" />
|
||||
<LuX className="size-3.5 text-muted-foreground hover:text-foreground" />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -331,7 +331,7 @@ const HomeHeader = ({
|
||||
}}
|
||||
className="flex gap-1.5 items-center h-7 px-2.5 text-xs"
|
||||
>
|
||||
<GoPlus className="w-3.5 h-3.5" />
|
||||
<GoPlus className="size-3.5" />
|
||||
{t("header.newProfile")}
|
||||
</Button>
|
||||
</span>
|
||||
|
||||
@@ -383,7 +383,7 @@ export function ImportProfileDialog({
|
||||
>
|
||||
<div className="flex gap-2 items-center">
|
||||
{IconComponent && (
|
||||
<IconComponent className="w-4 h-4" />
|
||||
<IconComponent className="size-4" />
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">
|
||||
@@ -475,7 +475,7 @@ export function ImportProfileDialog({
|
||||
<SelectItem key={browser} value={browser}>
|
||||
<div className="flex gap-2 items-center">
|
||||
{IconComponent && (
|
||||
<IconComponent className="w-4 h-4" />
|
||||
<IconComponent className="size-4" />
|
||||
)}
|
||||
<span>{getBrowserDisplayName(browser)}</span>
|
||||
</div>
|
||||
@@ -507,7 +507,7 @@ export function ImportProfileDialog({
|
||||
onClick={() => void handleBrowseFolder()}
|
||||
title={t("importProfile.browseFolderTitle")}
|
||||
>
|
||||
<FaFolder className="w-4 h-4" />
|
||||
<FaFolder className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<p className="mt-2 text-xs text-muted-foreground">
|
||||
|
||||
@@ -225,7 +225,7 @@ export function IntegrationsDialog({
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="api" className="space-y-4 mt-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Checkbox
|
||||
id="api-enabled"
|
||||
checked={apiServerPort !== null}
|
||||
@@ -251,7 +251,7 @@ export function IntegrationsDialog({
|
||||
<Label className="text-sm font-medium">
|
||||
{t("integrations.apiPortLabel")}
|
||||
</Label>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Button
|
||||
size="sm"
|
||||
disabled={
|
||||
@@ -336,7 +336,7 @@ export function IntegrationsDialog({
|
||||
<Label className="text-sm font-medium">
|
||||
{t("integrations.apiTokenLabel")}
|
||||
</Label>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<div className="relative flex-1">
|
||||
<Input
|
||||
type={showApiToken ? "text" : "password"}
|
||||
@@ -354,9 +354,9 @@ export function IntegrationsDialog({
|
||||
}}
|
||||
>
|
||||
{showApiToken ? (
|
||||
<EyeOff className="h-4 w-4" />
|
||||
<EyeOff className="size-4" />
|
||||
) : (
|
||||
<Eye className="h-4 w-4" />
|
||||
<Eye className="size-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -376,7 +376,7 @@ export function IntegrationsDialog({
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="mcp" className="space-y-4 mt-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Checkbox
|
||||
id="mcp-enabled"
|
||||
checked={settings.mcp_enabled && mcpConfig !== null}
|
||||
@@ -407,7 +407,7 @@ export function IntegrationsDialog({
|
||||
<Label className="text-sm font-medium">
|
||||
{t("integrations.mcp.url")}
|
||||
</Label>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<div className="relative flex-1">
|
||||
<Input
|
||||
type={showMcpToken ? "text" : "password"}
|
||||
@@ -425,9 +425,9 @@ export function IntegrationsDialog({
|
||||
}}
|
||||
>
|
||||
{showMcpToken ? (
|
||||
<EyeOff className="h-4 w-4" />
|
||||
<EyeOff className="size-4" />
|
||||
) : (
|
||||
<Eye className="h-4 w-4" />
|
||||
<Eye className="size-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@ export const LoadingButton = ({ isLoading, className, ...props }: Props) => {
|
||||
disabled={props.disabled || isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<LuLoaderCircle className="h-4 w-4 animate-spin" />
|
||||
<LuLoaderCircle className="size-4 animate-spin" />
|
||||
) : (
|
||||
props.children
|
||||
)}
|
||||
|
||||
@@ -26,6 +26,10 @@ interface LocationProxyDialogProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
function LoadingSpinner() {
|
||||
return <Loader2 className="size-4 animate-spin text-muted-foreground" />;
|
||||
}
|
||||
|
||||
export function LocationProxyDialog({
|
||||
isOpen,
|
||||
onClose,
|
||||
@@ -219,10 +223,6 @@ export function LocationProxyDialog({
|
||||
const cityOptions = cities.map((c) => ({ value: c.code, label: c.name }));
|
||||
const ispOptions = isps.map((i) => ({ value: i.code, label: i.name }));
|
||||
|
||||
const LoadingSpinner = () => (
|
||||
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={handleClose}>
|
||||
<DialogContent className="max-w-md">
|
||||
|
||||
@@ -434,7 +434,7 @@ const MultipleSelector = React.forwardRef<
|
||||
handleUnselect(option);
|
||||
}}
|
||||
>
|
||||
<LuX className="w-3 h-3 text-muted-foreground hover:text-foreground" />
|
||||
<LuX className="size-3 text-muted-foreground hover:text-foreground" />
|
||||
</button>
|
||||
</Badge>
|
||||
);
|
||||
|
||||
@@ -131,9 +131,9 @@ export function PermissionDialog({
|
||||
const getPermissionIcon = (type: PermissionType) => {
|
||||
switch (type) {
|
||||
case "microphone":
|
||||
return <BsMic className="w-8 h-8" />;
|
||||
return <BsMic className="size-8" />;
|
||||
case "camera":
|
||||
return <BsCamera className="w-8 h-8" />;
|
||||
return <BsCamera className="size-8" />;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -195,7 +195,7 @@ export function PermissionDialog({
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader className="text-center">
|
||||
<div className="flex justify-center items-center mx-auto mb-4 w-16 h-16 bg-primary/15 rounded-full">
|
||||
<div className="flex justify-center items-center mx-auto mb-4 size-16 bg-primary/15 rounded-full">
|
||||
{getPermissionIcon(permissionType)}
|
||||
</div>
|
||||
<DialogTitle className="text-xl">
|
||||
|
||||
@@ -350,11 +350,11 @@ function ExtCell({
|
||||
disabled={isSaving}
|
||||
className="flex items-center gap-1.5 h-7 px-1.5 text-xs text-muted-foreground hover:text-foreground hover:bg-accent/50 rounded transition-colors duration-100 w-full text-left disabled:opacity-50"
|
||||
>
|
||||
<LuPuzzle className="w-3 h-3 shrink-0" />
|
||||
<LuPuzzle className="size-3 shrink-0" />
|
||||
<span className="truncate flex-1" title={label}>
|
||||
{label}
|
||||
</span>
|
||||
<LuChevronDown className="w-3 h-3 shrink-0 text-muted-foreground" />
|
||||
<LuChevronDown className="size-3 shrink-0 text-muted-foreground" />
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-56 p-0" align="start">
|
||||
@@ -369,7 +369,7 @@ function ExtCell({
|
||||
void onPick(null);
|
||||
}}
|
||||
>
|
||||
{groupId === null && <LuCheck className="mr-2 w-3.5 h-3.5" />}
|
||||
{groupId === null && <LuCheck className="mr-2 size-3.5" />}
|
||||
<span className={groupId === null ? "" : "ml-5"}>
|
||||
{meta.t("profiles.table.extDefault")}
|
||||
</span>
|
||||
@@ -382,7 +382,7 @@ function ExtCell({
|
||||
void onPick(g.id);
|
||||
}}
|
||||
>
|
||||
{groupId === g.id && <LuCheck className="mr-2 w-3.5 h-3.5" />}
|
||||
{groupId === g.id && <LuCheck className="mr-2 size-3.5" />}
|
||||
<span className={groupId === g.id ? "" : "ml-5"}>
|
||||
{g.name}
|
||||
</span>
|
||||
@@ -445,11 +445,11 @@ function DnsCell({
|
||||
: meta.t("dnsBlocklist.none")
|
||||
}
|
||||
>
|
||||
<FiWifi className="w-3 h-3 shrink-0" />
|
||||
<FiWifi className="size-3 shrink-0" />
|
||||
<span className="flex-1 truncate uppercase text-[10px] font-mono tracking-wide">
|
||||
{level ?? "—"}
|
||||
</span>
|
||||
<LuChevronDown className="w-3 h-3 shrink-0 text-muted-foreground" />
|
||||
<LuChevronDown className="size-3 shrink-0 text-muted-foreground" />
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-48 p-0" align="start">
|
||||
@@ -462,7 +462,7 @@ function DnsCell({
|
||||
void onPick(null);
|
||||
}}
|
||||
>
|
||||
{level === null && <LuCheck className="mr-2 w-3.5 h-3.5" />}
|
||||
{level === null && <LuCheck className="mr-2 size-3.5" />}
|
||||
<span className={level === null ? "" : "ml-5"}>
|
||||
{meta.t("dnsBlocklist.none")}
|
||||
</span>
|
||||
@@ -475,9 +475,7 @@ function DnsCell({
|
||||
void onPick(l.value);
|
||||
}}
|
||||
>
|
||||
{level === l.value && (
|
||||
<LuCheck className="mr-2 w-3.5 h-3.5" />
|
||||
)}
|
||||
{level === l.value && <LuCheck className="mr-2 size-3.5" />}
|
||||
<span className={level === l.value ? "" : "ml-5"}>
|
||||
{meta.t(l.labelKey)}
|
||||
</span>
|
||||
@@ -1960,7 +1958,7 @@ export function ProfilesDataTable({
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="flex justify-center items-center w-4 h-4">
|
||||
<span className="flex justify-center items-center size-4">
|
||||
<button
|
||||
type="button"
|
||||
className="flex justify-center items-center p-0 border-none cursor-pointer"
|
||||
@@ -1969,9 +1967,9 @@ export function ProfilesDataTable({
|
||||
}}
|
||||
aria-label={t("common.aria.selectProfile")}
|
||||
>
|
||||
<span className="w-4 h-4 group">
|
||||
<OsIcon className="w-4 h-4 text-muted-foreground group-hover:hidden" />
|
||||
<span className="peer border-input dark:bg-input/30 dark:data-[state=checked]:bg-primary size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none w-4 h-4 hidden group-hover:block pointer-events-none items-center justify-center duration-150" />
|
||||
<span className="size-4 group">
|
||||
<OsIcon className="size-4 text-muted-foreground group-hover:hidden" />
|
||||
<span className="peer border-input dark:bg-input/30 dark:data-[state=checked]:bg-primary size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none size-4 hidden group-hover:block pointer-events-none items-center justify-center duration-150" />
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
@@ -1999,14 +1997,14 @@ export function ProfilesDataTable({
|
||||
sideOffset={4}
|
||||
horizontalOffset={8}
|
||||
>
|
||||
<span className="flex justify-center items-center w-4 h-4">
|
||||
<span className="flex justify-center items-center size-4">
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
onCheckedChange={(value) => {
|
||||
meta.handleCheckboxChange(profile.id, !!value);
|
||||
}}
|
||||
aria-label={t("common.aria.selectRow")}
|
||||
className="w-4 h-4"
|
||||
className="size-4"
|
||||
/>
|
||||
</span>
|
||||
</NonHoverableTooltip>
|
||||
@@ -2025,9 +2023,9 @@ export function ProfilesDataTable({
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="flex justify-center items-center w-4 h-4 cursor-not-allowed">
|
||||
<span className="flex justify-center items-center size-4 cursor-not-allowed">
|
||||
{IconComponent && (
|
||||
<IconComponent className="w-4 h-4 opacity-50" />
|
||||
<IconComponent className="size-4 opacity-50" />
|
||||
)}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
@@ -2047,14 +2045,14 @@ export function ProfilesDataTable({
|
||||
sideOffset={4}
|
||||
horizontalOffset={8}
|
||||
>
|
||||
<span className="flex justify-center items-center w-4 h-4">
|
||||
<span className="flex justify-center items-center size-4">
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
onCheckedChange={(value) => {
|
||||
meta.handleCheckboxChange(profile.id, !!value);
|
||||
}}
|
||||
aria-label={t("common.aria.selectRow")}
|
||||
className="w-4 h-4"
|
||||
className="size-4"
|
||||
/>
|
||||
</span>
|
||||
</NonHoverableTooltip>
|
||||
@@ -2067,7 +2065,7 @@ export function ProfilesDataTable({
|
||||
sideOffset={4}
|
||||
horizontalOffset={8}
|
||||
>
|
||||
<span className="flex relative justify-center items-center w-4 h-4">
|
||||
<span className="flex relative justify-center items-center size-4">
|
||||
<button
|
||||
type="button"
|
||||
className="flex justify-center items-center p-0 border-none cursor-pointer"
|
||||
@@ -2076,11 +2074,11 @@ export function ProfilesDataTable({
|
||||
}}
|
||||
aria-label={t("common.aria.selectProfile")}
|
||||
>
|
||||
<span className="w-4 h-4 group">
|
||||
<span className="size-4 group">
|
||||
{IconComponent && (
|
||||
<IconComponent className="w-4 h-4 group-hover:hidden" />
|
||||
<IconComponent className="size-4 group-hover:hidden" />
|
||||
)}
|
||||
<span className="peer border-input dark:bg-input/30 dark:data-[state=checked]:bg-primary size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none w-4 h-4 hidden group-hover:block pointer-events-none items-center justify-center duration-150" />
|
||||
<span className="peer border-input dark:bg-input/30 dark:data-[state=checked]:bg-primary size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none size-4 hidden group-hover:block pointer-events-none items-center justify-center duration-150" />
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
@@ -2194,7 +2192,7 @@ export function ProfilesDataTable({
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span>
|
||||
<LuTriangleAlert className="w-4 h-4 text-warning" />
|
||||
<LuTriangleAlert className="size-4 text-warning" />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
@@ -2217,7 +2215,7 @@ export function ProfilesDataTable({
|
||||
: meta.t("profiles.actions.launch")
|
||||
}
|
||||
className={cn(
|
||||
"h-7 w-7 p-0 grid place-items-center",
|
||||
"size-7 p-0 grid place-items-center",
|
||||
!canLaunch && "opacity-50 cursor-not-allowed",
|
||||
canLaunch && "cursor-pointer",
|
||||
isFollower && "border-accent",
|
||||
@@ -2231,11 +2229,11 @@ export function ProfilesDataTable({
|
||||
}
|
||||
>
|
||||
{isLaunching || isStopping ? (
|
||||
<div className="w-3 h-3 rounded-full border border-current animate-spin border-t-transparent" />
|
||||
<div className="size-3 rounded-full border border-current animate-spin border-t-transparent" />
|
||||
) : isRunning ? (
|
||||
<LuSquare className="w-3.5 h-3.5 fill-current" />
|
||||
<LuSquare className="size-3.5 fill-current" />
|
||||
) : (
|
||||
<LuPlay className="w-3.5 h-3.5 fill-current" />
|
||||
<LuPlay className="size-3.5 fill-current" />
|
||||
)}
|
||||
</RippleButton>
|
||||
</span>
|
||||
@@ -2265,9 +2263,9 @@ export function ProfilesDataTable({
|
||||
>
|
||||
{meta.t("common.labels.name")}
|
||||
{column.getIsSorted() === "asc" ? (
|
||||
<LuChevronUp className="ml-2 w-4 h-4" />
|
||||
<LuChevronUp className="ml-2 size-4" />
|
||||
) : column.getIsSorted() === "desc" ? (
|
||||
<LuChevronDown className="ml-2 w-4 h-4" />
|
||||
<LuChevronDown className="ml-2 size-4" />
|
||||
) : null}
|
||||
</Button>
|
||||
);
|
||||
@@ -2382,7 +2380,7 @@ export function ProfilesDataTable({
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span>
|
||||
<LuLock className="w-3 h-3 text-muted-foreground" />
|
||||
<LuLock className="size-3 text-muted-foreground" />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
@@ -2606,7 +2604,7 @@ export function ProfilesDataTable({
|
||||
>
|
||||
<LuCheck
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
"mr-2 size-4",
|
||||
selectedId === null
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
@@ -2633,7 +2631,7 @@ export function ProfilesDataTable({
|
||||
>
|
||||
<LuCheck
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
"mr-2 size-4",
|
||||
effectiveProxyId === proxy.id &&
|
||||
!effectiveVpn
|
||||
? "opacity-100"
|
||||
@@ -2659,7 +2657,7 @@ export function ProfilesDataTable({
|
||||
>
|
||||
<LuCheck
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
"mr-2 size-4",
|
||||
effectiveVpnId === vpn.id
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
@@ -2701,7 +2699,7 @@ export function ProfilesDataTable({
|
||||
)
|
||||
}
|
||||
>
|
||||
<span className="mr-2 h-4 w-4" />+{" "}
|
||||
<span className="mr-2 size-4" />+{" "}
|
||||
{country.name}
|
||||
</CommandItem>
|
||||
))}
|
||||
@@ -2793,11 +2791,11 @@ export function ProfilesDataTable({
|
||||
<span className="flex justify-center items-center h-9 w-full">
|
||||
{dot.encrypted ? (
|
||||
<LuLock
|
||||
className={`w-3 h-3 ${dot.color.replace("bg-", "text-")}${dot.animate ? " animate-pulse" : ""}`}
|
||||
className={`size-3 ${dot.color.replace("bg-", "text-")}${dot.animate ? " animate-pulse" : ""}`}
|
||||
/>
|
||||
) : (
|
||||
<span
|
||||
className={`w-2 h-2 rounded-full ${dot.color}${dot.animate ? " animate-pulse" : ""}`}
|
||||
className={`size-2 rounded-full ${dot.color}${dot.animate ? " animate-pulse" : ""}`}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
@@ -2818,7 +2816,7 @@ export function ProfilesDataTable({
|
||||
<div className="flex justify-end items-center h-9 w-full">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="p-0 w-7 h-7"
|
||||
className="p-0 size-7"
|
||||
disabled={!meta.isClient}
|
||||
onClick={() => {
|
||||
setProfileForInfoDialog(profile);
|
||||
@@ -2827,7 +2825,7 @@ export function ProfilesDataTable({
|
||||
<span className="sr-only">
|
||||
{t("profiles.aria.profileInfo")}
|
||||
</span>
|
||||
<LuInfo className="w-4 h-4" />
|
||||
<LuInfo className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -97,11 +97,11 @@ interface ProfileInfoDialogProps {
|
||||
function OSIcon({ os }: { os: string }) {
|
||||
switch (os) {
|
||||
case "macos":
|
||||
return <FaApple className="w-3.5 h-3.5" />;
|
||||
return <FaApple className="size-3.5" />;
|
||||
case "windows":
|
||||
return <FaWindows className="w-3.5 h-3.5" />;
|
||||
return <FaWindows className="size-3.5" />;
|
||||
case "linux":
|
||||
return <FaLinux className="w-3.5 h-3.5" />;
|
||||
return <FaLinux className="size-3.5" />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -317,7 +317,7 @@ export function ProfileInfoDialog({
|
||||
|
||||
const actions: ActionItem[] = [
|
||||
{
|
||||
icon: <LuGlobe className="w-4 h-4" />,
|
||||
icon: <LuGlobe className="size-4" />,
|
||||
label: t("profiles.actions.viewNetwork"),
|
||||
onClick: () => {
|
||||
handleAction(() => onOpenTrafficDialog?.(profile.id));
|
||||
@@ -325,7 +325,7 @@ export function ProfileInfoDialog({
|
||||
disabled: isCrossOs,
|
||||
},
|
||||
{
|
||||
icon: <LuRefreshCw className="w-4 h-4" />,
|
||||
icon: <LuRefreshCw className="size-4" />,
|
||||
label: t("profiles.actions.syncSettings"),
|
||||
onClick: () => {
|
||||
handleAction(() => onOpenProfileSyncDialog?.(profile));
|
||||
@@ -334,7 +334,7 @@ export function ProfileInfoDialog({
|
||||
hidden: profile.ephemeral === true,
|
||||
},
|
||||
{
|
||||
icon: <LuGroup className="w-4 h-4" />,
|
||||
icon: <LuGroup className="size-4" />,
|
||||
label: t("profiles.actions.assignToGroup"),
|
||||
onClick: () => {
|
||||
handleAction(() => onAssignProfilesToGroup?.([profile.id]));
|
||||
@@ -343,7 +343,7 @@ export function ProfileInfoDialog({
|
||||
runningBadge: isRunning,
|
||||
},
|
||||
{
|
||||
icon: <LuFingerprint className="w-4 h-4" />,
|
||||
icon: <LuFingerprint className="size-4" />,
|
||||
label: t("profiles.actions.changeFingerprint"),
|
||||
onClick: () => {
|
||||
handleAction(() => onConfigureCamoufox?.(profile));
|
||||
@@ -353,7 +353,7 @@ export function ProfileInfoDialog({
|
||||
hidden: !isCamoufoxOrWayfern || !onConfigureCamoufox,
|
||||
},
|
||||
{
|
||||
icon: <LuUsers className="w-4 h-4" />,
|
||||
icon: <LuUsers className="size-4" />,
|
||||
label: t("profiles.synchronizer.launchWithSync"),
|
||||
onClick: () => {
|
||||
handleAction(() => onLaunchWithSync?.(profile));
|
||||
@@ -363,7 +363,7 @@ export function ProfileInfoDialog({
|
||||
hidden: profile.browser !== "wayfern" || !onLaunchWithSync,
|
||||
},
|
||||
{
|
||||
icon: <LuCopy className="w-4 h-4" />,
|
||||
icon: <LuCopy className="size-4" />,
|
||||
label: t("profiles.actions.copyCookiesToProfile"),
|
||||
onClick: () => {
|
||||
handleAction(() => onCopyCookiesToProfile?.(profile));
|
||||
@@ -376,7 +376,7 @@ export function ProfileInfoDialog({
|
||||
!onCopyCookiesToProfile,
|
||||
},
|
||||
{
|
||||
icon: <LuCookie className="w-4 h-4" />,
|
||||
icon: <LuCookie className="size-4" />,
|
||||
label: t("profileInfo.actions.manageCookies"),
|
||||
onClick: () => {
|
||||
handleAction(() => onOpenCookieManagement?.(profile));
|
||||
@@ -389,7 +389,7 @@ export function ProfileInfoDialog({
|
||||
!onOpenCookieManagement,
|
||||
},
|
||||
{
|
||||
icon: <LuSettings className="w-4 h-4" />,
|
||||
icon: <LuSettings className="size-4" />,
|
||||
label: t("profiles.actions.clone"),
|
||||
onClick: () => {
|
||||
handleAction(() => onCloneProfile?.(profile));
|
||||
@@ -399,7 +399,7 @@ export function ProfileInfoDialog({
|
||||
hidden: profile.ephemeral === true,
|
||||
},
|
||||
{
|
||||
icon: <LuPuzzle className="w-4 h-4" />,
|
||||
icon: <LuPuzzle className="size-4" />,
|
||||
label: t("profileInfo.actions.assignExtensionGroup"),
|
||||
onClick: () => {
|
||||
handleAction(() => onAssignExtensionGroup?.([profile.id]));
|
||||
@@ -409,21 +409,21 @@ export function ProfileInfoDialog({
|
||||
hidden: profile.ephemeral === true,
|
||||
},
|
||||
{
|
||||
icon: <LuShieldCheck className="w-4 h-4" />,
|
||||
icon: <LuShieldCheck className="size-4" />,
|
||||
label: t("profileInfo.network.bypassRulesTitle"),
|
||||
onClick: () => {
|
||||
handleAction(() => onOpenBypassRules?.(profile));
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: <LuShield className="w-4 h-4" />,
|
||||
icon: <LuShield className="size-4" />,
|
||||
label: t("dnsBlocklist.title"),
|
||||
onClick: () => {
|
||||
handleAction(() => onOpenDnsBlocklist?.(profile));
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: <LuLink className="w-4 h-4" />,
|
||||
icon: <LuLink className="size-4" />,
|
||||
label: t("profiles.actions.launchHook"),
|
||||
onClick: () => {
|
||||
handleAction(() => onOpenLaunchHook?.(profile));
|
||||
@@ -431,7 +431,7 @@ export function ProfileInfoDialog({
|
||||
hidden: !onOpenLaunchHook,
|
||||
},
|
||||
{
|
||||
icon: <LuKey className="w-4 h-4" />,
|
||||
icon: <LuKey className="size-4" />,
|
||||
label: t("profiles.actions.setPassword"),
|
||||
onClick: () => {
|
||||
handleAction(() => onSetPassword?.(profile));
|
||||
@@ -444,7 +444,7 @@ export function ProfileInfoDialog({
|
||||
!onSetPassword,
|
||||
},
|
||||
{
|
||||
icon: <LuKey className="w-4 h-4" />,
|
||||
icon: <LuKey className="size-4" />,
|
||||
label: t("profiles.actions.changePassword"),
|
||||
onClick: () => {
|
||||
handleAction(() => onChangePassword?.(profile));
|
||||
@@ -454,7 +454,7 @@ export function ProfileInfoDialog({
|
||||
hidden: profile.password_protected !== true || !onChangePassword,
|
||||
},
|
||||
{
|
||||
icon: <LuLockOpen className="w-4 h-4" />,
|
||||
icon: <LuLockOpen className="size-4" />,
|
||||
label: t("profiles.actions.removePassword"),
|
||||
onClick: () => {
|
||||
handleAction(() => onRemovePassword?.(profile));
|
||||
@@ -465,7 +465,7 @@ export function ProfileInfoDialog({
|
||||
destructive: true,
|
||||
},
|
||||
{
|
||||
icon: <LuTrash2 className="w-4 h-4" />,
|
||||
icon: <LuTrash2 className="size-4" />,
|
||||
label: t("profiles.actions.delete"),
|
||||
onClick: () => {
|
||||
handleAction(() => onDeleteProfile?.(profile));
|
||||
@@ -646,12 +646,12 @@ function ProfileInfoLayout({
|
||||
}[] = [
|
||||
{
|
||||
id: "overview",
|
||||
icon: <LuClipboard className="w-3.5 h-3.5" />,
|
||||
icon: <LuClipboard className="size-3.5" />,
|
||||
label: t("profileInfo.sections.overview"),
|
||||
},
|
||||
{
|
||||
id: "fingerprint",
|
||||
icon: <LuFingerprint className="w-3.5 h-3.5" />,
|
||||
icon: <LuFingerprint className="size-3.5" />,
|
||||
label: t("profileInfo.sections.fingerprint"),
|
||||
badge: profile.password_protected
|
||||
? t("profileInfo.badges.locked")
|
||||
@@ -660,13 +660,13 @@ function ProfileInfoLayout({
|
||||
},
|
||||
{
|
||||
id: "network",
|
||||
icon: <LuGlobe className="w-3.5 h-3.5" />,
|
||||
icon: <LuGlobe className="size-3.5" />,
|
||||
label: t("profileInfo.sections.network"),
|
||||
badge: profile.proxy_id || profile.vpn_id ? networkLabel : undefined,
|
||||
},
|
||||
{
|
||||
id: "cookies",
|
||||
icon: <LuCookie className="w-3.5 h-3.5" />,
|
||||
icon: <LuCookie className="size-3.5" />,
|
||||
label: t("profileInfo.sections.cookies"),
|
||||
badge:
|
||||
cookieCount !== null && cookieCount > 0
|
||||
@@ -676,26 +676,26 @@ function ProfileInfoLayout({
|
||||
},
|
||||
{
|
||||
id: "extensions",
|
||||
icon: <LuPuzzle className="w-3.5 h-3.5" />,
|
||||
icon: <LuPuzzle className="size-3.5" />,
|
||||
label: t("profileInfo.sections.extensions"),
|
||||
badge: extensionGroupName ?? undefined,
|
||||
hidden: !extensionAction,
|
||||
},
|
||||
{
|
||||
id: "sync",
|
||||
icon: <LuRefreshCw className="w-3.5 h-3.5" />,
|
||||
icon: <LuRefreshCw className="size-3.5" />,
|
||||
label: t("profileInfo.sections.sync"),
|
||||
hidden: !syncAction,
|
||||
},
|
||||
{
|
||||
id: "automation",
|
||||
icon: <LuLink className="w-3.5 h-3.5" />,
|
||||
icon: <LuLink className="size-3.5" />,
|
||||
label: t("profileInfo.sections.launchHook"),
|
||||
badge: profile.launch_hook ? t("profileInfo.badges.active") : undefined,
|
||||
},
|
||||
{
|
||||
id: "security",
|
||||
icon: <LuKey className="w-3.5 h-3.5" />,
|
||||
icon: <LuKey className="size-3.5" />,
|
||||
label: t("profileInfo.sections.security"),
|
||||
},
|
||||
];
|
||||
@@ -704,7 +704,7 @@ function ProfileInfoLayout({
|
||||
<>
|
||||
{/* Top bar */}
|
||||
<div className="flex items-center gap-2 h-11 px-3 border-b border-border shrink-0">
|
||||
<LuUsers className="w-3.5 h-3.5 text-muted-foreground shrink-0" />
|
||||
<LuUsers className="size-3.5 text-muted-foreground shrink-0" />
|
||||
<div className="flex items-center gap-1.5 text-xs min-w-0 flex-1">
|
||||
<span className="font-semibold">
|
||||
{t("profileInfo.breadcrumbRoot")}
|
||||
@@ -720,7 +720,7 @@ function ProfileInfoLayout({
|
||||
disabled={isDisabled}
|
||||
onClick={() => onCloneProfile(profile)}
|
||||
>
|
||||
<LuCopy className="w-3 h-3" />
|
||||
<LuCopy className="size-3" />
|
||||
{t("profileInfo.duplicate")}
|
||||
</Button>
|
||||
)}
|
||||
@@ -728,9 +728,9 @@ function ProfileInfoLayout({
|
||||
type="button"
|
||||
aria-label={t("common.buttons.close")}
|
||||
onClick={onClose}
|
||||
className="grid place-items-center w-7 h-7 rounded-md text-muted-foreground hover:text-foreground hover:bg-accent/50 transition-colors duration-100"
|
||||
className="grid place-items-center size-7 rounded-md text-muted-foreground hover:text-foreground hover:bg-accent/50 transition-colors duration-100"
|
||||
>
|
||||
<LuX className="w-3.5 h-3.5" />
|
||||
<LuX className="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -773,7 +773,7 @@ function ProfileInfoLayout({
|
||||
disabled={deleteAction.disabled}
|
||||
className="flex items-center gap-2 h-7 px-2 rounded-md text-xs transition-colors duration-100 text-destructive hover:bg-destructive/10 disabled:opacity-50 disabled:pointer-events-none"
|
||||
>
|
||||
<LuTrash2 className="w-3.5 h-3.5 shrink-0" />
|
||||
<LuTrash2 className="size-3.5 shrink-0" />
|
||||
<span className="flex-1 text-left">
|
||||
{t("profileInfo.sections.delete")}
|
||||
</span>
|
||||
@@ -789,7 +789,7 @@ function ProfileInfoLayout({
|
||||
{/* Hero */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="rounded-lg bg-muted p-2.5 shrink-0">
|
||||
<ProfileIcon className="w-7 h-7 text-foreground" />
|
||||
<ProfileIcon className="size-7 text-foreground" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-1.5">
|
||||
@@ -809,7 +809,7 @@ function ProfileInfoLayout({
|
||||
<>
|
||||
<span className="text-muted-foreground">·</span>
|
||||
<span className="inline-flex items-center gap-1 text-success">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-success" />
|
||||
<span className="size-1.5 rounded-full bg-success" />
|
||||
{t("common.status.running")}
|
||||
</span>
|
||||
</>
|
||||
@@ -826,7 +826,7 @@ function ProfileInfoLayout({
|
||||
<>
|
||||
<span className="text-muted-foreground">·</span>
|
||||
<span className="inline-flex items-center gap-1 text-muted-foreground">
|
||||
<LuLock className="w-3 h-3" />
|
||||
<LuLock className="size-3" />
|
||||
{t("profiles.passwordProtectedBadge")}
|
||||
</span>
|
||||
</>
|
||||
@@ -875,9 +875,9 @@ function ProfileInfoLayout({
|
||||
aria-label={t("common.buttons.copy")}
|
||||
>
|
||||
{copied ? (
|
||||
<LuClipboardCheck className="w-3.5 h-3.5" />
|
||||
<LuClipboardCheck className="size-3.5" />
|
||||
) : (
|
||||
<LuClipboard className="w-3.5 h-3.5" />
|
||||
<LuClipboard className="size-3.5" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
@@ -1082,7 +1082,7 @@ function _SectionAction({
|
||||
>
|
||||
{icon}
|
||||
<span className="flex-1">{label}</span>
|
||||
<LuChevronRight className="w-3.5 h-3.5 text-muted-foreground" />
|
||||
<LuChevronRight className="size-3.5 text-muted-foreground" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -1132,7 +1132,7 @@ function LaunchHookEditor({
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center gap-2 text-sm font-semibold">
|
||||
<LuLink className="w-4 h-4" />
|
||||
<LuLink className="size-4" />
|
||||
{t("profileInfo.sections.launchHook")}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -1216,7 +1216,7 @@ function SyncSectionInline({
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center gap-2 text-sm font-semibold">
|
||||
<LuRefreshCw className="w-4 h-4" />
|
||||
<LuRefreshCw className="size-4" />
|
||||
{t("profileInfo.sections.sync")}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -1331,7 +1331,7 @@ function NetworkSectionInline({
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center gap-2 text-sm font-semibold">
|
||||
<LuGlobe className="w-4 h-4" />
|
||||
<LuGlobe className="size-4" />
|
||||
{t("profileInfo.sections.network")}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -1464,7 +1464,7 @@ function ExtensionsSectionInline({
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center gap-2 text-sm font-semibold">
|
||||
<LuPuzzle className="w-4 h-4" />
|
||||
<LuPuzzle className="size-4" />
|
||||
{t("profileInfo.sections.extensions")}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -1553,7 +1553,7 @@ function CookiesSectionInline({
|
||||
return (
|
||||
<div className="flex flex-col gap-3 min-h-0 flex-1">
|
||||
<div className="flex items-center gap-2 text-sm font-semibold">
|
||||
<LuCookie className="w-4 h-4" />
|
||||
<LuCookie className="size-4" />
|
||||
{t("profileInfo.sections.cookies")}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -1651,7 +1651,7 @@ function FingerprintSectionInline({
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center gap-2 text-sm font-semibold">
|
||||
<LuFingerprint className="w-4 h-4" />
|
||||
<LuFingerprint className="size-4" />
|
||||
{t("profileInfo.sections.fingerprint")}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -1705,7 +1705,7 @@ function FingerprintSectionInline({
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center gap-2 text-sm font-semibold">
|
||||
<LuFingerprint className="w-4 h-4" />
|
||||
<LuFingerprint className="size-4" />
|
||||
{t("profileInfo.sections.fingerprint")}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -1863,7 +1863,7 @@ function SecuritySectionInline({
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center gap-2 text-sm font-semibold">
|
||||
<LuKey className="w-4 h-4" />
|
||||
<LuKey className="size-4" />
|
||||
{t("profileInfo.sections.security")}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -2226,7 +2226,7 @@ export function ProfileBypassRulesDialog({
|
||||
onClick={handleAddRule}
|
||||
disabled={!newRule.trim()}
|
||||
>
|
||||
<LuPlus className="w-4 h-4 mr-1" />
|
||||
<LuPlus className="size-4 mr-1" />
|
||||
{t("profileInfo.network.addRule")}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -2249,7 +2249,7 @@ export function ProfileBypassRulesDialog({
|
||||
}}
|
||||
className="text-muted-foreground hover:text-destructive transition-colors shrink-0"
|
||||
>
|
||||
<LuX className="w-3.5 h-3.5" />
|
||||
<LuX className="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -53,14 +53,16 @@ export function ProfilePasswordDialog({
|
||||
const firstInputRef = React.useRef<HTMLInputElement>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isOpen) {
|
||||
setOldPassword("");
|
||||
setPassword("");
|
||||
setConfirm("");
|
||||
setIsSubmitting(false);
|
||||
setLockoutSecondsRemaining(null);
|
||||
setTimeout(() => firstInputRef.current?.focus(), 0);
|
||||
}
|
||||
if (!isOpen) return;
|
||||
setOldPassword("");
|
||||
setPassword("");
|
||||
setConfirm("");
|
||||
setIsSubmitting(false);
|
||||
setLockoutSecondsRemaining(null);
|
||||
const handle = window.setTimeout(() => firstInputRef.current?.focus(), 0);
|
||||
return () => {
|
||||
window.clearTimeout(handle);
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
// Tick down the lockout timer
|
||||
|
||||
@@ -237,7 +237,7 @@ export function ProfileSelectorDialog({
|
||||
profile.browser,
|
||||
);
|
||||
return IconComponent ? (
|
||||
<IconComponent className="w-4 h-4" />
|
||||
<IconComponent className="size-4" />
|
||||
) : null;
|
||||
})()}
|
||||
</div>
|
||||
|
||||
@@ -188,7 +188,7 @@ export function ProfileSyncDialog({
|
||||
|
||||
{isCheckingConfig ? (
|
||||
<div className="flex justify-center py-8">
|
||||
<div className="w-6 h-6 rounded-full border-2 border-current animate-spin border-t-transparent" />
|
||||
<div className="size-6 rounded-full border-2 border-current animate-spin border-t-transparent" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-4 py-4">
|
||||
@@ -216,7 +216,7 @@ export function ProfileSyncDialog({
|
||||
disabled={isSaving}
|
||||
className="grid gap-3"
|
||||
>
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex items-start gap-x-3">
|
||||
<RadioGroupItem value="Disabled" id="sync-disabled" />
|
||||
<Label htmlFor="sync-disabled" className="cursor-pointer">
|
||||
<span className="font-medium">
|
||||
@@ -228,7 +228,7 @@ export function ProfileSyncDialog({
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex items-start gap-x-3">
|
||||
<RadioGroupItem value="Regular" id="sync-regular" />
|
||||
<Label htmlFor="sync-regular" className="cursor-pointer">
|
||||
<span className="font-medium">
|
||||
@@ -240,7 +240,7 @@ export function ProfileSyncDialog({
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex items-start gap-x-3">
|
||||
<RadioGroupItem
|
||||
value="Encrypted"
|
||||
id="sync-encrypted"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { emit } from "@tauri-apps/api/event";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useId, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { LuCheck, LuChevronsUpDown } from "react-icons/lu";
|
||||
import { toast } from "sonner";
|
||||
@@ -55,6 +55,7 @@ export function ProxyAssignmentDialog({
|
||||
vpnConfigs = [],
|
||||
}: ProxyAssignmentDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
const proxyListboxId = useId();
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
const [selectionType, setSelectionType] = useState<"none" | "proxy" | "vpn">(
|
||||
"none",
|
||||
@@ -183,6 +184,7 @@ export function ProxyAssignmentDialog({
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={proxyPopoverOpen}
|
||||
aria-controls={proxyListboxId}
|
||||
className="w-full justify-between font-normal"
|
||||
>
|
||||
{(() => {
|
||||
@@ -199,10 +201,14 @@ export function ProxyAssignmentDialog({
|
||||
);
|
||||
return proxy ? proxy.name : t("proxyAssignment.noneOption");
|
||||
})()}
|
||||
<LuChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
<LuChevronsUpDown className="ml-2 size-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[240px] p-0" sideOffset={8}>
|
||||
<PopoverContent
|
||||
id={proxyListboxId}
|
||||
className="w-[240px] p-0"
|
||||
sideOffset={8}
|
||||
>
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder={t("proxyAssignment.searchPlaceholder")}
|
||||
@@ -219,7 +225,7 @@ export function ProxyAssignmentDialog({
|
||||
>
|
||||
<LuCheck
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
"mr-2 size-4",
|
||||
selectionType === "none"
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
@@ -243,7 +249,7 @@ export function ProxyAssignmentDialog({
|
||||
>
|
||||
<LuCheck
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
"mr-2 size-4",
|
||||
selectionType === "proxy" &&
|
||||
selectedId === proxy.id
|
||||
? "opacity-100"
|
||||
@@ -269,7 +275,7 @@ export function ProxyAssignmentDialog({
|
||||
>
|
||||
<LuCheck
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
"mr-2 size-4",
|
||||
selectionType === "vpn" && selectedId === vpn.id
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
|
||||
@@ -118,12 +118,12 @@ export function ProxyCheckButton({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 w-7 p-0"
|
||||
className="size-7 p-0"
|
||||
onClick={handleCheck}
|
||||
disabled={isCurrentlyChecking || disabled}
|
||||
>
|
||||
{isCurrentlyChecking ? (
|
||||
<div className="w-3 h-3 rounded-full border border-current animate-spin border-t-transparent" />
|
||||
<div className="size-3 rounded-full border border-current animate-spin border-t-transparent" />
|
||||
) : result?.is_valid && result.country_code ? (
|
||||
<span className="relative inline-flex items-center justify-center">
|
||||
<FlagIcon countryCode={result.country_code} className="h-2.5" />
|
||||
@@ -132,7 +132,7 @@ export function ProxyCheckButton({
|
||||
) : result && !result.is_valid ? (
|
||||
<span className="text-destructive text-sm">✕</span>
|
||||
) : (
|
||||
<FiCheck className="w-3 h-3" />
|
||||
<FiCheck className="size-3" />
|
||||
)}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
|
||||
@@ -108,13 +108,13 @@ export function ProxyExportDialog({ isOpen, onClose }: ProxyExportDialogProps) {
|
||||
}}
|
||||
className="flex gap-4"
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<RadioGroupItem value="json" id="format-json" />
|
||||
<Label htmlFor="format-json" className="cursor-pointer">
|
||||
{t("proxies.exportDialog.json")}
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<RadioGroupItem value="txt" id="format-txt" />
|
||||
<Label htmlFor="format-txt" className="cursor-pointer">
|
||||
{t("proxies.exportDialog.txt")}
|
||||
@@ -154,9 +154,9 @@ export function ProxyExportDialog({ isOpen, onClose }: ProxyExportDialogProps) {
|
||||
className="flex gap-2 items-center"
|
||||
>
|
||||
{copied ? (
|
||||
<LuCheck className="w-4 h-4" />
|
||||
<LuCheck className="size-4" />
|
||||
) : (
|
||||
<LuCopy className="w-4 h-4" />
|
||||
<LuCopy className="size-4" />
|
||||
)}
|
||||
{copied
|
||||
? t("proxies.exportDialog.copied")
|
||||
@@ -167,7 +167,7 @@ export function ProxyExportDialog({ isOpen, onClose }: ProxyExportDialogProps) {
|
||||
disabled={!exportContent || isLoading}
|
||||
className="flex gap-2 items-center"
|
||||
>
|
||||
<LuDownload className="w-4 h-4" />
|
||||
<LuDownload className="size-4" />
|
||||
{t("common.buttons.download")}
|
||||
</RippleButton>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -315,7 +315,7 @@ export function ProxyImportDialog({ isOpen, onClose }: ProxyImportDialogProps) {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LuUpload className="w-10 h-10 text-muted-foreground mb-4" />
|
||||
<LuUpload className="size-10 text-muted-foreground mb-4" />
|
||||
<p className="text-sm text-muted-foreground text-center">
|
||||
{t("proxies.importDialog.dropzonePrompt")}
|
||||
<br />
|
||||
|
||||
@@ -451,7 +451,7 @@ export function ProxyManagementDialog({
|
||||
}}
|
||||
className="flex gap-2 items-center"
|
||||
>
|
||||
<LuUpload className="w-4 h-4" />
|
||||
<LuUpload className="size-4" />
|
||||
{t("common.buttons.import")}
|
||||
</RippleButton>
|
||||
<RippleButton
|
||||
@@ -463,7 +463,7 @@ export function ProxyManagementDialog({
|
||||
className="flex gap-2 items-center"
|
||||
disabled={storedProxies.length === 0}
|
||||
>
|
||||
<LuDownload className="w-4 h-4" />
|
||||
<LuDownload className="size-4" />
|
||||
{t("common.buttons.export")}
|
||||
</RippleButton>
|
||||
</div>
|
||||
@@ -473,7 +473,7 @@ export function ProxyManagementDialog({
|
||||
onClick={handleCreateProxy}
|
||||
className="flex gap-2 items-center"
|
||||
>
|
||||
<GoPlus className="w-4 h-4" />
|
||||
<GoPlus className="size-4" />
|
||||
{t("proxies.management.create")}
|
||||
</RippleButton>
|
||||
</div>
|
||||
@@ -519,7 +519,7 @@ export function ProxyManagementDialog({
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full shrink-0 ${syncDot.color} ${
|
||||
className={`size-2 rounded-full shrink-0 ${syncDot.color} ${
|
||||
syncDot.animate
|
||||
? "animate-pulse"
|
||||
: ""
|
||||
@@ -605,7 +605,7 @@ export function ProxyManagementDialog({
|
||||
handleEditProxy(proxy);
|
||||
}}
|
||||
>
|
||||
<LuPencil className="w-4 h-4" />
|
||||
<LuPencil className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
@@ -627,7 +627,7 @@ export function ProxyManagementDialog({
|
||||
(proxyUsage[proxy.id] ?? 0) > 0
|
||||
}
|
||||
>
|
||||
<LuTrash2 className="w-4 h-4" />
|
||||
<LuTrash2 className="size-4" />
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
@@ -681,7 +681,7 @@ export function ProxyManagementDialog({
|
||||
}}
|
||||
className="flex gap-2 items-center"
|
||||
>
|
||||
<LuUpload className="w-4 h-4" />
|
||||
<LuUpload className="size-4" />
|
||||
{t("common.buttons.import")}
|
||||
</RippleButton>
|
||||
</div>
|
||||
@@ -690,7 +690,7 @@ export function ProxyManagementDialog({
|
||||
onClick={handleCreateVpn}
|
||||
className="flex gap-2 items-center"
|
||||
>
|
||||
<GoPlus className="w-4 h-4" />
|
||||
<GoPlus className="size-4" />
|
||||
{t("proxies.management.create")}
|
||||
</RippleButton>
|
||||
</div>
|
||||
@@ -738,7 +738,7 @@ export function ProxyManagementDialog({
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full shrink-0 ${syncDot.color} ${
|
||||
className={`size-2 rounded-full shrink-0 ${syncDot.color} ${
|
||||
syncDot.animate
|
||||
? "animate-pulse"
|
||||
: ""
|
||||
@@ -814,7 +814,7 @@ export function ProxyManagementDialog({
|
||||
handleEditVpn(vpn);
|
||||
}}
|
||||
>
|
||||
<LuPencil className="w-4 h-4" />
|
||||
<LuPencil className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
@@ -834,7 +834,7 @@ export function ProxyManagementDialog({
|
||||
(vpnUsage[vpn.id] ?? 0) > 0
|
||||
}
|
||||
>
|
||||
<LuTrash2 className="w-4 h-4" />
|
||||
<LuTrash2 className="size-4" />
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
|
||||
+11
-11
@@ -290,7 +290,7 @@ export function RailNav({ currentPage, onNavigate }: RailNavProps) {
|
||||
ref={logoRef}
|
||||
type="button"
|
||||
aria-label={t("header.donutLogo")}
|
||||
className="grid place-items-center w-7 h-7 rounded-md cursor-pointer select-none text-foreground bg-transparent"
|
||||
className="grid place-items-center size-7 rounded-md cursor-pointer select-none text-foreground bg-transparent"
|
||||
onClick={handleClick}
|
||||
onPointerDown={() => {
|
||||
setIsPressed(true);
|
||||
@@ -322,12 +322,12 @@ export function RailNav({ currentPage, onNavigate }: RailNavProps) {
|
||||
"animate-[wiggle_0.3s_ease-in-out]",
|
||||
)}
|
||||
>
|
||||
<Logo className="w-5 h-5 will-change-transform" />
|
||||
<Logo className="size-5 will-change-transform" />
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
) : (
|
||||
<div className="w-7 h-7" />
|
||||
<div className="size-7" />
|
||||
)}
|
||||
|
||||
<div className="w-5 h-px bg-border my-1" />
|
||||
@@ -345,7 +345,7 @@ export function RailNav({ currentPage, onNavigate }: RailNavProps) {
|
||||
aria-label={t(labelKey)}
|
||||
aria-current={active ? "page" : undefined}
|
||||
className={cn(
|
||||
"relative grid place-items-center w-7 h-7 rounded-md transition-colors duration-100",
|
||||
"relative grid place-items-center size-7 rounded-md transition-colors duration-100",
|
||||
active
|
||||
? "text-foreground bg-accent"
|
||||
: "text-muted-foreground hover:text-card-foreground hover:bg-accent/50",
|
||||
@@ -357,7 +357,7 @@ export function RailNav({ currentPage, onNavigate }: RailNavProps) {
|
||||
className="absolute left-[-7px] top-1.5 bottom-1.5 w-[2px] rounded-full bg-foreground"
|
||||
/>
|
||||
)}
|
||||
<Icon className="w-3.5 h-3.5" />
|
||||
<Icon className="size-3.5" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">{t(labelKey)}</TooltipContent>
|
||||
@@ -377,13 +377,13 @@ export function RailNav({ currentPage, onNavigate }: RailNavProps) {
|
||||
aria-label={t("rail.more.label")}
|
||||
aria-expanded={moreOpen}
|
||||
className={cn(
|
||||
"grid place-items-center w-7 h-7 rounded-md transition-colors duration-100",
|
||||
"grid place-items-center size-7 rounded-md transition-colors duration-100",
|
||||
moreOpen
|
||||
? "text-foreground bg-accent"
|
||||
: "text-muted-foreground hover:text-card-foreground hover:bg-accent/50",
|
||||
)}
|
||||
>
|
||||
<GoKebabHorizontal className="w-3.5 h-3.5" />
|
||||
<GoKebabHorizontal className="size-3.5" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">{t("rail.more.label")}</TooltipContent>
|
||||
@@ -399,7 +399,7 @@ export function RailNav({ currentPage, onNavigate }: RailNavProps) {
|
||||
aria-label={t("rail.settings")}
|
||||
aria-current={currentPage === "settings" ? "page" : undefined}
|
||||
className={cn(
|
||||
"relative grid place-items-center w-7 h-7 rounded-md transition-colors duration-100",
|
||||
"relative grid place-items-center size-7 rounded-md transition-colors duration-100",
|
||||
currentPage === "settings"
|
||||
? "text-foreground bg-accent"
|
||||
: "text-muted-foreground hover:text-card-foreground hover:bg-accent/50",
|
||||
@@ -411,7 +411,7 @@ export function RailNav({ currentPage, onNavigate }: RailNavProps) {
|
||||
className="absolute left-[-7px] top-1.5 bottom-1.5 w-[2px] rounded-full bg-foreground"
|
||||
/>
|
||||
)}
|
||||
<GoGear className="w-3.5 h-3.5" />
|
||||
<GoGear className="size-3.5" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">{t("rail.settings")}</TooltipContent>
|
||||
@@ -438,8 +438,8 @@ export function RailNav({ currentPage, onNavigate }: RailNavProps) {
|
||||
}}
|
||||
className="flex items-center gap-2 w-full px-2 py-1.5 rounded-md hover:bg-accent transition-colors duration-100 text-left"
|
||||
>
|
||||
<span className="grid place-items-center w-5 h-5 rounded bg-muted text-muted-foreground shrink-0">
|
||||
<Icon className="w-3 h-3" />
|
||||
<span className="grid place-items-center size-5 rounded bg-muted text-muted-foreground shrink-0">
|
||||
<Icon className="size-3" />
|
||||
</span>
|
||||
<span className="flex flex-col min-w-0">
|
||||
<span className="text-xs font-medium text-foreground truncate">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useId, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { LuCheck, LuChevronsUpDown, LuDownload } from "react-icons/lu";
|
||||
import { LoadingButton } from "@/components/loading-button";
|
||||
@@ -44,6 +44,7 @@ export function ReleaseTypeSelector({
|
||||
}: ReleaseTypeSelectorProps) {
|
||||
const { t } = useTranslation();
|
||||
const [popoverOpen, setPopoverOpen] = useState(false);
|
||||
const listboxId = useId();
|
||||
const effectivePlaceholder =
|
||||
placeholder ?? t("releaseTypeSelector.placeholder");
|
||||
|
||||
@@ -91,13 +92,14 @@ export function ReleaseTypeSelector({
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={popoverOpen}
|
||||
aria-controls={listboxId}
|
||||
className="justify-between w-full"
|
||||
>
|
||||
{selectedDisplayText}
|
||||
<LuChevronsUpDown className="ml-2 w-4 h-4 opacity-50 shrink-0" />
|
||||
<LuChevronsUpDown className="ml-2 size-4 opacity-50 shrink-0" />
|
||||
</RippleButton>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0">
|
||||
<PopoverContent id={listboxId} className="p-0">
|
||||
<Command>
|
||||
<CommandEmpty>
|
||||
{t("releaseTypeSelector.noReleaseTypes")}
|
||||
@@ -126,7 +128,7 @@ export function ReleaseTypeSelector({
|
||||
>
|
||||
<LuCheck
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
"mr-2 size-4",
|
||||
selectedReleaseType === option.type
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
@@ -187,7 +189,7 @@ export function ReleaseTypeSelector({
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
>
|
||||
<LuDownload className="mr-2 w-4 h-4" />
|
||||
<LuDownload className="mr-2 size-4" />
|
||||
{isDownloading
|
||||
? t("releaseTypeSelector.downloading")
|
||||
: t("releaseTypeSelector.downloadBrowser")}
|
||||
|
||||
@@ -165,9 +165,9 @@ export function SettingsDialog({
|
||||
const getPermissionIcon = useCallback((type: PermissionType) => {
|
||||
switch (type) {
|
||||
case "microphone":
|
||||
return <BsMic className="w-4 h-4" />;
|
||||
return <BsMic className="size-4" />;
|
||||
case "camera":
|
||||
return <BsCamera className="w-4 h-4" />;
|
||||
return <BsCamera className="size-4" />;
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -738,7 +738,7 @@ export function SettingsDialog({
|
||||
<button
|
||||
type="button"
|
||||
aria-label={label}
|
||||
className="w-8 h-8 rounded-md border shadow-sm cursor-pointer"
|
||||
className="size-8 rounded-md border shadow-sm cursor-pointer"
|
||||
style={{ backgroundColor: colorValue }}
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
@@ -887,7 +887,7 @@ export function SettingsDialog({
|
||||
key={permission.permission_type}
|
||||
className="flex justify-between items-center p-3 rounded-lg border"
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="flex items-center gap-x-3">
|
||||
{getPermissionIcon(permission.permission_type)}
|
||||
<div>
|
||||
<div className="text-sm font-medium">
|
||||
@@ -900,7 +900,7 @@ export function SettingsDialog({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
{getStatusBadge(permission.isGranted)}
|
||||
{!permission.isGranted && (
|
||||
<LoadingButton
|
||||
@@ -1170,7 +1170,7 @@ export function SettingsDialog({
|
||||
</Label>
|
||||
|
||||
{!isLinux && (
|
||||
<div className="flex items-start space-x-3 p-3 rounded-lg border">
|
||||
<div className="flex items-start gap-x-3 p-3 rounded-lg border">
|
||||
<Checkbox
|
||||
id="disable-auto-updates"
|
||||
checked={settings.disable_auto_updates ?? false}
|
||||
@@ -1192,7 +1192,7 @@ export function SettingsDialog({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-start space-x-3 p-3 rounded-lg border">
|
||||
<div className="flex items-start gap-x-3 p-3 rounded-lg border">
|
||||
<Checkbox
|
||||
id="keep-decrypted-profiles-in-ram"
|
||||
checked={settings.keep_decrypted_profiles_in_ram ?? false}
|
||||
|
||||
@@ -303,7 +303,7 @@ export function SharedCamoufoxConfigForm({
|
||||
|
||||
{/* Randomize Fingerprint Option */}
|
||||
<div className="space-y-3 p-4 border rounded-lg bg-muted/30">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Checkbox
|
||||
id="randomize-fingerprint"
|
||||
checked={config.randomize_fingerprint_on_launch ?? false}
|
||||
@@ -323,7 +323,7 @@ export function SharedCamoufoxConfigForm({
|
||||
|
||||
{/* Automatic Location Configuration */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Checkbox
|
||||
id="auto-location-advanced"
|
||||
checked={isAutoLocationEnabled}
|
||||
@@ -367,7 +367,7 @@ export function SharedCamoufoxConfigForm({
|
||||
<div className="space-y-3">
|
||||
<Label>{t("fingerprint.blockingOptions")}</Label>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Checkbox
|
||||
id="block-images"
|
||||
checked={config.block_images ?? false}
|
||||
@@ -379,7 +379,7 @@ export function SharedCamoufoxConfigForm({
|
||||
{t("fingerprint.blockImages")}
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Checkbox
|
||||
id="block-webrtc"
|
||||
checked={config.block_webrtc ?? false}
|
||||
@@ -391,7 +391,7 @@ export function SharedCamoufoxConfigForm({
|
||||
{t("fingerprint.blockWebRTC")}
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Checkbox
|
||||
id="block-webgl"
|
||||
checked={config.block_webgl ?? false}
|
||||
@@ -1025,7 +1025,7 @@ export function SharedCamoufoxConfigForm({
|
||||
<Label>{t("fingerprint.battery")}</Label>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Checkbox
|
||||
id="battery-charging"
|
||||
checked={fingerprintConfig["battery:charging"] ?? false}
|
||||
@@ -1176,7 +1176,7 @@ export function SharedCamoufoxConfigForm({
|
||||
|
||||
{/* Randomize Fingerprint Option */}
|
||||
<div className="space-y-3 p-4 border rounded-lg bg-muted/30">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Checkbox
|
||||
id="randomize-fingerprint-auto"
|
||||
checked={config.randomize_fingerprint_on_launch ?? false}
|
||||
@@ -1199,7 +1199,7 @@ export function SharedCamoufoxConfigForm({
|
||||
|
||||
{/* Automatic Location Configuration */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Checkbox
|
||||
id="auto-location"
|
||||
checked={isAutoLocationEnabled}
|
||||
|
||||
@@ -96,7 +96,7 @@ export function SyncAllDialog({ isOpen, onClose }: SyncAllDialogProps) {
|
||||
|
||||
{isLoading ? (
|
||||
<div className="flex justify-center py-8">
|
||||
<div className="w-6 h-6 rounded-full border-2 border-current animate-spin border-t-transparent" />
|
||||
<div className="size-6 rounded-full border-2 border-current animate-spin border-t-transparent" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-4">
|
||||
|
||||
@@ -248,7 +248,7 @@ export function SyncConfigDialog({
|
||||
{isLoggedIn && user ? (
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="flex gap-2 items-center text-sm">
|
||||
<div className="w-2 h-2 rounded-full bg-success" />
|
||||
<div className="size-2 rounded-full bg-success" />
|
||||
{t("sync.cloud.connected")}
|
||||
</div>
|
||||
|
||||
@@ -353,7 +353,7 @@ export function SyncConfigDialog({
|
||||
<TabsContent value="cloud">
|
||||
{isCloudLoading ? (
|
||||
<div className="flex justify-center py-8">
|
||||
<div className="w-6 h-6 rounded-full border-2 border-current animate-spin border-t-transparent" />
|
||||
<div className="size-6 rounded-full border-2 border-current animate-spin border-t-transparent" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-4 py-4">
|
||||
@@ -373,7 +373,7 @@ export function SyncConfigDialog({
|
||||
<TabsContent value="self-hosted">
|
||||
{isLoading ? (
|
||||
<div className="flex justify-center py-8">
|
||||
<div className="w-6 h-6 rounded-full border-2 border-current animate-spin border-t-transparent" />
|
||||
<div className="size-6 rounded-full border-2 border-current animate-spin border-t-transparent" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-4 py-4">
|
||||
@@ -419,9 +419,9 @@ export function SyncConfigDialog({
|
||||
}
|
||||
>
|
||||
{showToken ? (
|
||||
<LuEyeOff className="w-4 h-4 text-muted-foreground hover:text-foreground" />
|
||||
<LuEyeOff className="size-4 text-muted-foreground hover:text-foreground" />
|
||||
) : (
|
||||
<LuEye className="w-4 h-4 text-muted-foreground hover:text-foreground" />
|
||||
<LuEye className="size-4 text-muted-foreground hover:text-foreground" />
|
||||
)}
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
@@ -434,19 +434,19 @@ export function SyncConfigDialog({
|
||||
|
||||
{connectionStatus === "testing" && (
|
||||
<div className="flex gap-2 items-center text-sm text-muted-foreground">
|
||||
<div className="w-4 h-4 rounded-full border-2 border-current animate-spin border-t-transparent" />
|
||||
<div className="size-4 rounded-full border-2 border-current animate-spin border-t-transparent" />
|
||||
{t("sync.status.syncing")}
|
||||
</div>
|
||||
)}
|
||||
{connectionStatus === "connected" && (
|
||||
<div className="flex gap-2 items-center text-sm text-muted-foreground">
|
||||
<div className="w-2 h-2 rounded-full bg-success" />
|
||||
<div className="size-2 rounded-full bg-success" />
|
||||
{t("sync.status.connected")}
|
||||
</div>
|
||||
)}
|
||||
{connectionStatus === "error" && (
|
||||
<div className="flex gap-2 items-center text-sm text-muted-foreground">
|
||||
<div className="w-2 h-2 rounded-full bg-destructive" />
|
||||
<div className="size-2 rounded-full bg-destructive" />
|
||||
{t("sync.status.disconnected")}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -108,24 +108,28 @@ export function CustomThemeProvider({ children }: CustomThemeProviderProps) {
|
||||
|
||||
// Re-apply custom theme after mount
|
||||
useEffect(() => {
|
||||
if (!isLoading && theme === "custom") {
|
||||
const reapplyCustomTheme = async () => {
|
||||
try {
|
||||
const { invoke } = await import("@tauri-apps/api/core");
|
||||
const settings = await invoke<AppSettings>("get_app_settings");
|
||||
if (settings?.theme === "custom" && settings.custom_theme) {
|
||||
applyThemeColors(settings.custom_theme);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Failed to reapply custom theme:", error);
|
||||
}
|
||||
};
|
||||
setTimeout(() => {
|
||||
void reapplyCustomTheme();
|
||||
}, 100);
|
||||
} else if (!isLoading) {
|
||||
if (isLoading) return;
|
||||
if (theme !== "custom") {
|
||||
clearThemeColors();
|
||||
return;
|
||||
}
|
||||
const reapplyCustomTheme = async () => {
|
||||
try {
|
||||
const { invoke } = await import("@tauri-apps/api/core");
|
||||
const settings = await invoke<AppSettings>("get_app_settings");
|
||||
if (settings?.theme === "custom" && settings.custom_theme) {
|
||||
applyThemeColors(settings.custom_theme);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Failed to reapply custom theme:", error);
|
||||
}
|
||||
};
|
||||
const handle = window.setTimeout(() => {
|
||||
void reapplyCustomTheme();
|
||||
}, 100);
|
||||
return () => {
|
||||
window.clearTimeout(handle);
|
||||
};
|
||||
}, [isLoading, theme]);
|
||||
|
||||
// Listen for system theme changes when in "system" mode
|
||||
|
||||
@@ -398,7 +398,7 @@ export function TrafficDetailsDialog({
|
||||
<div className="flex items-center justify-center gap-6 mt-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className="w-3 h-3 rounded"
|
||||
className="size-3 rounded"
|
||||
style={{ backgroundColor: "var(--chart-1)" }}
|
||||
/>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
@@ -407,7 +407,7 @@ export function TrafficDetailsDialog({
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className="w-3 h-3 rounded"
|
||||
className="size-3 rounded"
|
||||
style={{ backgroundColor: "var(--chart-2)" }}
|
||||
/>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
|
||||
@@ -229,7 +229,7 @@ const ChartTooltipContent = React.forwardRef<
|
||||
className={cn(
|
||||
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
|
||||
{
|
||||
"h-2.5 w-2.5": indicator === "dot",
|
||||
"size-2.5": indicator === "dot",
|
||||
"w-1": indicator === "line",
|
||||
"w-0 border-[1.5px] border-dashed bg-transparent":
|
||||
indicator === "dashed",
|
||||
@@ -321,7 +321,7 @@ const ChartLegendContent = React.forwardRef<
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
<div
|
||||
className="h-2 w-2 shrink-0 rounded-[2px]"
|
||||
className="size-2 shrink-0 rounded-[2px]"
|
||||
style={{
|
||||
backgroundColor: item.color,
|
||||
}}
|
||||
|
||||
@@ -245,7 +245,7 @@ export const ColorPickerSelection = memo(
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
className="absolute w-4 h-4 rounded-full border-2 border-white -translate-x-1/2 -translate-y-1/2 pointer-events-none"
|
||||
className="absolute size-4 rounded-full border-2 border-white -translate-x-1/2 -translate-y-1/2 pointer-events-none"
|
||||
style={{
|
||||
left: `${positionX * 100}%`,
|
||||
top: `${positionY * 100}%`,
|
||||
@@ -281,7 +281,7 @@ export const ColorPickerHue = ({
|
||||
<Slider.Track className="relative my-0.5 h-3 w-full grow rounded-full bg-[linear-gradient(90deg,#FF0000,#FFFF00,#00FF00,#00FFFF,#0000FF,#FF00FF,#FF0000)]">
|
||||
<Slider.Range className="absolute h-full" />
|
||||
</Slider.Track>
|
||||
<Slider.Thumb className="block w-4 h-4 rounded-full border shadow transition-colors border-primary/50 bg-background focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
|
||||
<Slider.Thumb className="block size-4 rounded-full border shadow transition-colors border-primary/50 bg-background focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
|
||||
</Slider.Root>
|
||||
);
|
||||
};
|
||||
@@ -315,7 +315,7 @@ export const ColorPickerAlpha = ({
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent rounded-full to-black/50" />
|
||||
<Slider.Range className="absolute h-full bg-transparent rounded-full" />
|
||||
</Slider.Track>
|
||||
<Slider.Thumb className="block w-4 h-4 rounded-full border shadow transition-colors border-primary/50 bg-background focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
|
||||
<Slider.Thumb className="block size-4 rounded-full border shadow transition-colors border-primary/50 bg-background focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
|
||||
</Slider.Root>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -47,6 +47,7 @@ export function Combobox({
|
||||
}: ComboboxProps) {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const listboxId = React.useId();
|
||||
|
||||
const resolvedPlaceholder = placeholder ?? t("common.buttons.select");
|
||||
const resolvedSearchPlaceholder =
|
||||
@@ -59,16 +60,17 @@ export function Combobox({
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
aria-controls={listboxId}
|
||||
disabled={disabled}
|
||||
className={cn("w-full justify-between", className)}
|
||||
>
|
||||
{value
|
||||
? options.find((option) => option.value === value)?.label
|
||||
: resolvedPlaceholder}
|
||||
<LuChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
<LuChevronsUpDown className="ml-2 size-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-full p-0">
|
||||
<PopoverContent id={listboxId} className="w-full p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder={resolvedSearchPlaceholder} />
|
||||
<CommandList>
|
||||
@@ -85,7 +87,7 @@ export function Combobox({
|
||||
>
|
||||
<LuCheck
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
"mr-2 size-4",
|
||||
value === option.value ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -55,12 +55,12 @@ export function CopyToClipboard({
|
||||
{copied ? t("common.srOnly.copied") : t("common.srOnly.copy")}
|
||||
</span>
|
||||
<LuCopy
|
||||
className={`h-4 w-4 transition-all duration-150 ${
|
||||
className={`size-4 transition-all duration-150 ${
|
||||
copied ? "scale-0" : "scale-100"
|
||||
}`}
|
||||
/>
|
||||
<LuCheck
|
||||
className={`absolute inset-0 m-auto h-4 w-4 text-foreground transition-all duration-150 ${
|
||||
className={`absolute inset-0 m-auto size-4 text-foreground transition-all duration-150 ${
|
||||
copied ? "scale-100" : "scale-0"
|
||||
}`}
|
||||
/>
|
||||
|
||||
@@ -28,13 +28,13 @@ const RadioGroupItem = React.forwardRef<
|
||||
<RadioGroupPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"aspect-square size-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
|
||||
<LuCircle className="h-2.5 w-2.5 fill-current text-current" />
|
||||
<LuCircle className="size-2.5 fill-current text-current" />
|
||||
</RadioGroupPrimitive.Indicator>
|
||||
</RadioGroupPrimitive.Item>
|
||||
);
|
||||
|
||||
@@ -70,18 +70,18 @@ export function VpnCheckButton({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 w-7 p-0"
|
||||
className="size-7 p-0"
|
||||
onClick={handleCheck}
|
||||
disabled={isCurrentlyChecking || disabled}
|
||||
>
|
||||
{isCurrentlyChecking ? (
|
||||
<div className="w-3 h-3 rounded-full border border-current animate-spin border-t-transparent" />
|
||||
<div className="size-3 rounded-full border border-current animate-spin border-t-transparent" />
|
||||
) : result?.is_valid ? (
|
||||
<FiCheck className="w-3 h-3 text-success" />
|
||||
<FiCheck className="size-3 text-success" />
|
||||
) : result && !result.is_valid ? (
|
||||
<span className="text-destructive text-sm">✕</span>
|
||||
) : (
|
||||
<FiCheck className="w-3 h-3" />
|
||||
<FiCheck className="size-3" />
|
||||
)}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
|
||||
@@ -219,7 +219,7 @@ export function VpnImportDialog({ isOpen, onClose }: VpnImportDialogProps) {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LuUpload className="w-10 h-10 text-muted-foreground mb-4" />
|
||||
<LuUpload className="size-10 text-muted-foreground mb-4" />
|
||||
<p className="text-sm text-muted-foreground text-center">
|
||||
{t("vpns.import.dropzonePrompt")}
|
||||
</p>
|
||||
@@ -244,7 +244,7 @@ export function VpnImportDialog({ isOpen, onClose }: VpnImportDialogProps) {
|
||||
{step === "vpn-preview" && vpnPreview && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-3 p-4 bg-muted/30 rounded-lg">
|
||||
<LuShield className="w-8 h-8 text-primary" />
|
||||
<LuShield className="size-8 text-primary" />
|
||||
<div>
|
||||
<div className="font-medium">
|
||||
{t("vpns.import.configurationLabel", {
|
||||
@@ -292,7 +292,7 @@ export function VpnImportDialog({ isOpen, onClose }: VpnImportDialogProps) {
|
||||
>
|
||||
{vpnImportResult.success ? (
|
||||
<div className="flex items-center gap-3">
|
||||
<LuShield className="w-8 h-8 text-success" />
|
||||
<LuShield className="size-8 text-success" />
|
||||
<div>
|
||||
<div className="font-medium text-success">
|
||||
{t("vpns.import.importedSuccess")}
|
||||
|
||||
@@ -228,7 +228,7 @@ export function WayfernConfigForm({
|
||||
|
||||
{/* Randomize Fingerprint Option */}
|
||||
<div className="space-y-3 p-4 border rounded-lg bg-muted/30">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Checkbox
|
||||
id="randomize-fingerprint"
|
||||
checked={config.randomize_fingerprint_on_launch ?? false}
|
||||
@@ -248,7 +248,7 @@ export function WayfernConfigForm({
|
||||
|
||||
{/* Automatic Location Configuration */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Checkbox
|
||||
id="auto-location-advanced"
|
||||
checked={isAutoLocationEnabled}
|
||||
@@ -954,7 +954,7 @@ export function WayfernConfigForm({
|
||||
<Label>{t("fingerprint.battery")}</Label>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Checkbox
|
||||
id="battery-charging"
|
||||
checked={fingerprintConfig.batteryCharging ?? false}
|
||||
@@ -1133,7 +1133,7 @@ export function WayfernConfigForm({
|
||||
|
||||
{/* Randomize Fingerprint Option */}
|
||||
<div className="space-y-3 p-4 border rounded-lg bg-muted/30">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Checkbox
|
||||
id="randomize-fingerprint-auto"
|
||||
checked={config.randomize_fingerprint_on_launch ?? false}
|
||||
@@ -1156,7 +1156,7 @@ export function WayfernConfigForm({
|
||||
|
||||
{/* Automatic Location Configuration */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Checkbox
|
||||
id="auto-location"
|
||||
checked={isAutoLocationEnabled}
|
||||
|
||||
@@ -79,7 +79,7 @@ export function WindowResizeWarningDialog({
|
||||
|
||||
<p className="text-sm text-muted-foreground">{description}</p>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Checkbox
|
||||
id="dont-show-again"
|
||||
checked={dontShowAgain}
|
||||
|
||||
@@ -206,3 +206,14 @@
|
||||
[data-sonner-toast] select {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms;
|
||||
animation-iteration-count: 1;
|
||||
transition-duration: 0.01ms;
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user