Add seed/Telegram login button and traffic usage indicator

New non-blocking login panel on the Home screen (seed backup/restore +
Telegram magic-link via the bot deep-link, polled through the new backend
endpoints) — uses its own popover instead of a full-screen Dialog so the
burger menu stays clickable while it's open. VpnStats now shows a used/limit
usage bar backed by a new useUsageStore.

Also registers tauri-plugin-shell (needed to open the t.me deep-link) and
threads an optional auth token from the app down through TunnelConfig to the
proxy client (desktop + Android), for instances running --require-auth.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 00:31:27 +07:00
parent bf50824833
commit 0cb6ad2860
17 changed files with 596 additions and 26 deletions
+65 -18
View File
@@ -1,4 +1,7 @@
import { useEffect } from "react";
import { useTrafficStore } from "@/store/useTrafficStore"; // путь к твоему стору
import { useUsageStore } from "@/store/useUsageStore";
import { useVpnStore } from "@/store/useVpnStore";
import { cn } from "@/lib/utils";
import { useTranslation } from "react-i18next";
@@ -19,33 +22,77 @@ export function VpnStats({ className }: { className?: string }) {
const rx = useTrafficStore((state) => state.rx);
const tx = useTrafficStore((state) => state.tx);
const status = useVpnStore((state) => state.status);
const usedBytes = useUsageStore((state) => state.usedBytes);
const limitBytes = useUsageStore((state) => state.limitBytes);
const startAutoRefresh = useUsageStore((state) => state.startAutoRefresh);
const stopAutoRefresh = useUsageStore((state) => state.stopAutoRefresh);
useEffect(() => {
if (status === "connected") {
startAutoRefresh();
} else {
stopAutoRefresh();
}
return () => stopAutoRefresh();
}, [status, startAutoRefresh, stopAutoRefresh]);
const usagePercent =
usedBytes !== null && limitBytes ? Math.min(100, (usedBytes / limitBytes) * 100) : null;
return (
<div
className={cn(
"w-full max-w-70 relative flex items-center justify-between px-8 py-4 rounded-2xl",
"w-full max-w-70 relative flex flex-col gap-3 px-8 py-4 rounded-2xl",
"bg-background/20 backdrop-blur-md border border-white/10 shadow-lg",
className,
)}
>
<div className="flex flex-col items-center flex-1">
<span className="text-[10px] uppercase tracking-widest text-muted-foreground font-bold">
{t("rx")}
</span>
<span className="text-sm font-mono text-foreground font-semibold">
{formatBytes(rx)}
</span>
<div className="flex items-center justify-between">
<div className="flex flex-col items-center flex-1">
<span className="text-[10px] uppercase tracking-widest text-muted-foreground font-bold">
{t("rx")}
</span>
<span className="text-sm font-mono text-foreground font-semibold">
{formatBytes(rx)}
</span>
</div>
<div className="h-8 w-px bg-border/50 mx-4" />
<div className="flex flex-col items-center flex-1">
<span className="text-[10px] uppercase tracking-widest text-muted-foreground font-bold">
{t("tx")}
</span>
<span className="text-sm font-mono text-foreground font-semibold">
{formatBytes(tx)}
</span>
</div>
</div>
<div className="h-8 w-px bg-border/50 mx-4" />
<div className="flex flex-col items-center flex-1">
<span className="text-[10px] uppercase tracking-widest text-muted-foreground font-bold">
{t("tx")}
</span>
<span className="text-sm font-mono text-foreground font-semibold">
{formatBytes(tx)}
</span>
</div>
{usedBytes !== null && (
<div className="flex flex-col gap-1 pt-1 border-t border-white/10">
<div className="flex items-center justify-between text-[10px] uppercase tracking-widest text-muted-foreground font-bold">
<span>{t("usage_label")}</span>
<span className="font-mono normal-case tracking-normal">
{limitBytes
? `${formatBytes(usedBytes)} / ${formatBytes(limitBytes)}`
: `${formatBytes(usedBytes)} / ${t("usage_unlimited")}`}
</span>
</div>
{usagePercent !== null && (
<div className="h-1.5 w-full rounded-full bg-white/10 overflow-hidden">
<div
className={cn(
"h-full rounded-full transition-all",
usagePercent >= 90 ? "bg-destructive" : "bg-primary",
)}
style={{ width: `${usagePercent}%` }}
/>
</div>
)}
</div>
)}
</div>
);
}