subs and fixes hack game

This commit is contained in:
2026-04-23 15:35:12 +07:00
parent 8c33f5c097
commit 40f7de803b
7 changed files with 387 additions and 111 deletions
+90 -49
View File
@@ -12,7 +12,7 @@ import {
Globe,
Terminal,
} from "lucide-react";
import { fetchProfile } from "./api";
import { activateTrial, buySubscription, fetchProfile } from "./api";
import { useNavigate } from "react-router-dom";
// --- ЛОКАЛИЗАЦИЯ И СТАТИКА ---
const DICT = {
@@ -153,10 +153,8 @@ export default function Profile() {
// БЕЗОПАСНОЕ ИЗВЛЕЧЕНИЕ: Если чего-то нет, не падаем, а ставим дефолт
// Твой бэкенд отдает Json(user), так что data - это и есть объект пользователя
const user = data?.user || data || {};
const subscription = data?.subscription || {
plan_name: "Free",
expires_at: null,
};
const subscription = data?.subscription; // Теперь может быть null
const hasSub = subscription && subscription.plan_name;
const recruits = data?.recruits || 0;
const earned = data?.earned || 0;
const plans = data?.plans || [
@@ -173,6 +171,28 @@ export default function Profile() {
setTimeout(() => setCopied(false), 2000);
};
const getPrice = (usd: number, rub: number) => {
return lang === "ru" ? `${rub} ₽/мес` : `$${usd}/mo`;
};
const handleTrial = async () => {
try {
await activateTrial();
window.location.reload();
} catch (e) {
alert("Error / Уже использовано");
}
};
const handleBuy = async (planName: string) => {
try {
await buySubscription(planName);
window.location.reload();
} catch (e) {
alert("Error");
}
};
return (
<div className="min-h-screen bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-[#131124] via-[#08080C] to-black p-4 sm:p-6 pb-24 text-foreground font-mono">
<header className="mb-8 text-center pt-8 relative z-10 flex flex-col items-center">
@@ -195,50 +215,64 @@ export default function Profile() {
animate={{ opacity: 1, y: 0 }}
className="grid gap-6 max-w-2xl mx-auto relative z-10"
>
{/* PROFILE & BALANCE */}
<div className="bg-white/[0.03] backdrop-blur-2xl border border-white/10 rounded-3xl p-6 shadow-[0_8px_32px_0_rgba(0,0,0,0.4)] relative overflow-hidden">
<div className="absolute -top-20 -right-20 w-64 h-64 bg-primary/20 rounded-full blur-[80px] pointer-events-none" />
<div className="flex justify-between items-start mb-8">
<div>
<p className="text-muted-foreground text-xs uppercase tracking-widest mb-1">
{t.operative}
</p>
<p className="text-sm font-bold opacity-80 text-primary">
@{user.tg_username || user.tg_id || "Anon"}
</p>
</div>
<div className="text-right">
<p className="text-muted-foreground text-xs uppercase tracking-widest mb-1">
{t.balance}
</p>
<p className="text-3xl font-black text-secondary drop-shadow-[0_0_12px_rgba(0,229,242,0.6)]">
{user.balance || 0}{" "}
<span className="text-sm opacity-70">E$</span>
</p>
</div>
</div>
<div className="bg-black/40 border border-white/5 rounded-2xl p-4">
<div className="flex justify-between items-center mb-2">
<div className="flex items-center gap-2">
<Activity className="w-4 h-4 text-primary" />
<span className="text-sm uppercase tracking-widest text-muted-foreground">
{t.uplink_status}
</span>
</div>
<span
className={`px-3 py-1 rounded-full text-xs font-bold uppercase ${subscription.plan_name === "Free" ? "bg-white/10 text-white" : "bg-primary/20 text-primary border border-primary/30 shadow-[0_0_10px_rgba(139,61,255,0.3)]"}`}
>
{subscription.plan_name} TIER
</span>
</div>
<p className="text-xs text-muted-foreground">
{subscription.expires_at
? `${t.expires}: ${new Date(subscription.expires_at).toLocaleDateString()}`
: t.no_limit}
<div className="flex justify-between items-start mb-8">
<div>
<p className="text-muted-foreground text-xs uppercase tracking-widest mb-1">
{t.operative}
</p>
<p className="text-sm font-bold opacity-80 text-primary">
@{user.tg_username || user.tg_id || "Anon"}
</p>
</div>
<div className="text-right">
<p className="text-muted-foreground text-xs uppercase tracking-widest mb-1">
{t.balance} | TICKETS:{" "}
<span className="text-primary">{user.game_tickets || 0}</span>
</p>
<p className="text-3xl font-black text-secondary drop-shadow-[0_0_12px_rgba(0,229,242,0.6)]">
{user.balance || 0} <span className="text-sm opacity-70">E$</span>
</p>
</div>
</div>
<div className="bg-black/40 border border-white/5 rounded-2xl p-4">
<div className="flex justify-between items-center mb-2">
<div className="flex items-center gap-2">
<Activity className="w-4 h-4 text-primary" />
<span className="text-sm uppercase tracking-widest text-muted-foreground">
{t.uplink_status}
</span>
</div>
{hasSub ? (
<span className="px-3 py-1 rounded-full text-xs font-bold uppercase bg-primary/20 text-primary border border-primary/30 shadow-[0_0_10px_rgba(139,61,255,0.3)]">
{subscription.plan_name}
</span>
) : (
<span className="px-3 py-1 rounded-full text-xs font-bold uppercase bg-white/10 text-muted-foreground">
OFFLINE
</span>
)}
</div>
{hasSub ? (
<p className="text-xs text-muted-foreground">
{t.expires}:{" "}
{new Date(subscription.expires_at).toLocaleDateString()}
</p>
) : (
<div className="flex justify-between items-center mt-2">
<p className="text-xs text-muted-foreground">
No active subscription.
</p>
{!user.has_used_trial && (
<button
onClick={handleTrial}
className="text-xs bg-secondary/20 hover:bg-secondary/40 text-secondary px-3 py-1 rounded transition-colors"
>
ACTIVATE 3-DAY TRIAL (5GB)
</button>
)}
</div>
)}
</div>
<button
@@ -308,8 +342,15 @@ export default function Profile() {
<li key={i}>- {f}</li>
))}
</ul>
<button className="mt-auto w-full py-2 rounded-xl bg-white/5 group-hover:bg-primary text-xs font-bold transition-colors relative z-10">
{t.acquire}
<button
onClick={() => handleBuy(plan.name)}
className="mt-auto w-full py-2 rounded-xl bg-white/5 group-hover:bg-primary text-xs font-bold transition-colors relative z-10"
>
{t.acquire}{" "}
{getPrice(
plan.price_usd || plan.price,
plan.price_rub || plan.price * 100,
)}
</button>
</div>
);