refactor: cleanup, korean translation

This commit is contained in:
zhom
2026-05-23 14:22:45 +04:00
parent 375530e358
commit 69da467ce0
31 changed files with 2573 additions and 307 deletions
+69 -20
View File
@@ -1,9 +1,12 @@
"use client";
import { invoke } from "@tauri-apps/api/core";
import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { FiWifi } from "react-icons/fi";
import { LuLayers, LuPuzzle, LuShield, LuUsers } from "react-icons/lu";
import { LoadingButton } from "@/components/loading-button";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -19,6 +22,8 @@ interface UnsyncedEntityCounts {
proxies: number;
groups: number;
vpns: number;
extensions: number;
extension_groups: number;
}
interface SyncAllDialogProps {
@@ -67,27 +72,55 @@ export function SyncAllDialog({ isOpen, onClose }: SyncAllDialogProps) {
}
}, [onClose, t]);
const totalCount =
(counts?.proxies ?? 0) + (counts?.groups ?? 0) + (counts?.vpns ?? 0);
const items = useMemo(() => {
if (!counts) return [];
return [
{
key: "proxies",
count: counts.proxies,
label: t("syncAll.labels.proxies"),
Icon: FiWifi,
},
{
key: "vpns",
count: counts.vpns,
label: t("syncAll.labels.vpns"),
Icon: LuShield,
},
{
key: "groups",
count: counts.groups,
label: t("syncAll.labels.groups"),
Icon: LuUsers,
},
{
key: "extensions",
count: counts.extensions,
label: t("syncAll.labels.extensions"),
Icon: LuPuzzle,
},
{
key: "extensionGroups",
count: counts.extension_groups,
label: t("syncAll.labels.extensionGroups"),
Icon: LuLayers,
},
].filter((item) => item.count > 0);
}, [counts, t]);
// Don't show if there's nothing to sync
const totalCount = items.reduce((sum, item) => sum + item.count, 0);
// Don't render anything when there's nothing to sync — the parent
// mounts this dialog eagerly after login, so silent-close is correct.
if (!isLoading && totalCount === 0) {
return null;
}
const parts: string[] = [];
if (counts?.proxies && counts.proxies > 0) {
parts.push(t("syncAll.proxies", { count: counts.proxies }));
}
if (counts?.groups && counts.groups > 0) {
parts.push(t("syncAll.groups", { count: counts.groups }));
}
if (counts?.vpns && counts.vpns > 0) {
parts.push(t("syncAll.vpns", { count: counts.vpns }));
}
return (
<Dialog open={isOpen && totalCount > 0} onOpenChange={onClose}>
<Dialog
open={isOpen && (isLoading || totalCount > 0)}
onOpenChange={onClose}
>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle>{t("syncAll.title")}</DialogTitle>
@@ -99,10 +132,26 @@ export function SyncAllDialog({ isOpen, onClose }: SyncAllDialogProps) {
<div className="size-6 rounded-full border-2 border-current animate-spin border-t-transparent" />
</div>
) : (
<div className="py-4">
<p className="text-sm text-muted-foreground">
{t("syncAll.itemsList", { items: parts.join(", ") })}
</p>
<div className="grid grid-cols-2 gap-2 py-2">
{items.map(({ key, count, label, Icon }) => (
<div
key={key}
className="flex items-center gap-3 rounded-lg border border-border/60 bg-card/50 p-3 transition-colors hover:bg-card"
>
<div className="flex size-9 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary">
<Icon className="size-4" />
</div>
<div className="min-w-0 flex-1 text-sm font-medium truncate">
{label}
</div>
<Badge
variant="secondary"
className="shrink-0 tabular-nums px-2"
>
{count}
</Badge>
</div>
))}
</div>
)}