diff --git a/src/pages/Profile.tsx b/src/pages/Profile.tsx index 39d846c..08cbcae 100644 --- a/src/pages/Profile.tsx +++ b/src/pages/Profile.tsx @@ -1 +1,34 @@ -export default function Profile() { return
Profile
; } +import { useAuthStore } from "../store/auth"; +import { Navbar } from "../components/layout/Navbar"; +import { GlassCard } from "../components/ui/GlassCard"; + +export default function Profile() { + const { user } = useAuthStore(); + + return ( +
+ +
+

Профиль

+ +
Информация аккаунта
+
+
Имя{user?.name}
+
Email{user?.email || "—"}
+
Telegram + + {user?.tg_linked ? "✅ Привязан" : "⚠️ Не привязан"} + +
+
+
+ {!user?.tg_linked && ( + +
Привязать Telegram
+

Привяжите Telegram-аккаунт, чтобы получить полный доступ к VPN-ключам из бота.

+
+ )} +
+
+ ); +} diff --git a/src/pages/dashboard/Dashboard.tsx b/src/pages/dashboard/Dashboard.tsx index 9ac672f..6880936 100644 --- a/src/pages/dashboard/Dashboard.tsx +++ b/src/pages/dashboard/Dashboard.tsx @@ -1 +1,83 @@ -export default function Dashboard() { return
Dashboard
; } +import { useEffect, useState } from "react"; +import { Link } from "react-router-dom"; +import { api } from "../../api/client"; +import { DashboardLayout } from "./DashboardLayout"; +import { GlassCard } from "../../components/ui/GlassCard"; +import { StatusBadge } from "../../components/ui/StatusBadge"; +import { Button } from "../../components/ui/Button"; +import { QRModal } from "../../components/ui/QRModal"; +import { Key, Wallet, Smartphone, Calendar } from "lucide-react"; + +export default function Dashboard() { + const [profile, setProfile] = useState(null); + const [qrKey, setQrKey] = useState(null); + + useEffect(() => { + api.action("profile").then((r) => { if (r.ok) setProfile(r); }); + }, []); + + if (!profile) return
Загрузка...
; + + return ( + + {qrKey && setQrKey(null)} />} + +

Добро пожаловать, {profile.first_name} 👋

+ + {/* Stats */} +
+ {[ + { icon: Calendar, label: "Дней осталось", value: profile.days_left ?? 0 }, + { icon: Wallet, label: "Баланс", value: `${Math.round(profile.balance)} ₽` }, + { icon: Smartphone, label: "Устройств", value: profile.device_limit ?? 3 }, + { icon: Key, label: "Ключей", value: (profile.keys ?? []).length }, + ].map(({ icon: Icon, label, value }) => ( + + +
{value}
+
{label}
+
+ ))} +
+ + {/* Subscription status */} + +
+ Подписка + +
+ {profile.subscription_active ? ( + <> +

Активна до {profile.subscription_until}

+
+
+
+ + ) : ( +

Подписка неактивна. Купите тариф для доступа к VPN.

+ )} +
+ +
+ + + {/* Keys preview */} + {(profile.keys ?? []).slice(0, 2).map((k: any) => ( + +
+
+
{k.name || "Ключ"}
+
до {k.until} · {k.days_left} дн.
+
+ {(k.remnawave_link || k.link) && ( + + )} +
+
+ ))} + + ); +} diff --git a/src/pages/dashboard/DashboardLayout.tsx b/src/pages/dashboard/DashboardLayout.tsx new file mode 100644 index 0000000..2cde84d --- /dev/null +++ b/src/pages/dashboard/DashboardLayout.tsx @@ -0,0 +1,14 @@ +import { Navbar } from "../../components/layout/Navbar"; +import { Sidebar } from "../../components/layout/Sidebar"; + +export function DashboardLayout({ children }: { children: React.ReactNode }) { + return ( +
+ +
+ +
{children}
+
+
+ ); +} diff --git a/src/pages/dashboard/Devices.tsx b/src/pages/dashboard/Devices.tsx index 15cfed3..117e006 100644 --- a/src/pages/dashboard/Devices.tsx +++ b/src/pages/dashboard/Devices.tsx @@ -1 +1,42 @@ -export default function Devices() { return
Devices
; } +import { useEffect, useState } from "react"; +import { api } from "../../api/client"; +import { DashboardLayout } from "./DashboardLayout"; +import { GlassCard } from "../../components/ui/GlassCard"; +import { Button } from "../../components/ui/Button"; +import { Smartphone } from "lucide-react"; + +export default function Devices() { + const [data, setData] = useState(null); + const load = () => api.action("user_devices").then((r) => setData(r)); + useEffect(() => { load(); }, []); + + async function handleDelete(hwid: string) { + if (!data?.client_id) return; + await api.action("delete_device", { client_id: data.client_id, hwid }); + load(); + } + + return ( + +

Устройства

+ {!data ?
Загрузка...
: + !data.ok ?
{data.error}
: + !(data.devices ?? []).length ?
Устройств не найдено
: + (data.devices ?? []).map((d: any) => ( + +
+
+ +
+
{d.deviceModel || "Устройство"}
+
{[d.platform, d.osVersion].filter(Boolean).join(" ")}
+
{d.hwid}
+
+
+ +
+
+ ))} +
+ ); +} diff --git a/src/pages/dashboard/Keys.tsx b/src/pages/dashboard/Keys.tsx index 42131e0..dcc470d 100644 --- a/src/pages/dashboard/Keys.tsx +++ b/src/pages/dashboard/Keys.tsx @@ -1 +1,71 @@ -export default function Keys() { return
Keys
; } +import { useEffect, useState } from "react"; +import { api } from "../../api/client"; +import { DashboardLayout } from "./DashboardLayout"; +import { GlassCard } from "../../components/ui/GlassCard"; +import { Button } from "../../components/ui/Button"; +import { QRModal } from "../../components/ui/QRModal"; +import { StatusBadge } from "../../components/ui/StatusBadge"; + +export default function Keys() { + const [profile, setProfile] = useState(null); + const [qrKey, setQrKey] = useState(null); + const [renaming, setRenaming] = useState(null); + const [newName, setNewName] = useState(""); + + const load = () => api.action("profile").then((r) => { if (r.ok) setProfile(r); }); + useEffect(() => { load(); }, []); + + async function handleRename(clientId: string) { + if (!newName.trim()) return; + await api.action("rename_key", { client_id: clientId, name: newName.trim() }); + setRenaming(null); setNewName(""); + load(); + } + + async function handleFreeze(clientId: string, frozen: boolean) { + await api.action("freeze_key", { client_id: clientId, frozen }); + load(); + } + + const keys = profile?.keys ?? []; + + return ( + + {qrKey && setQrKey(null)} />} +

Мои ключи

+ {!keys.length &&
Ключей нет. Купите подписку.
} + {keys.map((k: any) => ( + +
+
+ {renaming === k.client_id ? ( +
+ setNewName(e.target.value)} autoFocus /> + + +
+ ) : ( +
+ {k.name || "Ключ"} + +
+ )} +
Сервер: {k.server_id} · до {k.until}
+
+ +
+
+ {(k.remnawave_link || k.link) && ( + + )} + +
+
+ ))} +
+ ); +} diff --git a/src/pages/dashboard/Payments.tsx b/src/pages/dashboard/Payments.tsx index eaab442..372962b 100644 --- a/src/pages/dashboard/Payments.tsx +++ b/src/pages/dashboard/Payments.tsx @@ -1 +1,26 @@ -export default function Payments() { return
Payments
; } +import { useEffect, useState } from "react"; +import { api } from "../../api/client"; +import { DashboardLayout } from "./DashboardLayout"; +import { GlassCard } from "../../components/ui/GlassCard"; + +export default function Payments() { + const [payments, setPayments] = useState([]); + useEffect(() => { + api.action("profile").then((r) => { if (r.ok) setPayments(r.payments ?? []); }); + }, []); + return ( + +

История платежей

+ {!payments.length ?
Платежей нет
: + payments.map((p, i) => ( + +
+
{p.created_at}
+
{p.payment_system} · {p.status === "success" ? "✅ Успешно" : "❌ Ошибка"}
+
+
{p.amount} ₽
+
+ ))} +
+ ); +} diff --git a/src/pages/dashboard/Servers.tsx b/src/pages/dashboard/Servers.tsx index 64b0d1e..06f63d8 100644 --- a/src/pages/dashboard/Servers.tsx +++ b/src/pages/dashboard/Servers.tsx @@ -1 +1,28 @@ -export default function Servers() { return
Servers
; } +import { useEffect, useState } from "react"; +import { api } from "../../api/client"; +import { DashboardLayout } from "./DashboardLayout"; +import { GlassCard } from "../../components/ui/GlassCard"; +import { StatusBadge } from "../../components/ui/StatusBadge"; + +export default function Servers() { + const [servers, setServers] = useState([]); + useEffect(() => { + api.action("servers").then((r) => { if (r.ok) setServers(r.servers ?? []); }); + }, []); + return ( + +

Серверы

+ {servers.map((s: any) => ( + +
+
+
{s.name}
+
{s.cluster} · {s.panel_type} · макс. {s.max_keys} ключей
+
+ +
+
+ ))} +
+ ); +}