trial updates
This commit is contained in:
+57
-52
@@ -14,7 +14,7 @@ import {
|
||||
} from "lucide-react";
|
||||
import { activateTrial, buySubscription, fetchProfile } from "./api";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
// --- ЛОКАЛИЗАЦИЯ И СТАТИКА ---
|
||||
|
||||
const DICT = {
|
||||
en: {
|
||||
auth_ready: "App Authorization Ready",
|
||||
@@ -22,16 +22,22 @@ const DICT = {
|
||||
balance: "Balance",
|
||||
uplink_status: "Uplink Status",
|
||||
expires: "Expires",
|
||||
no_limit: "Bandwidth limited. Upgrade for max routing speed.",
|
||||
system_override: "System Override",
|
||||
manage_plan: "Manage Plan",
|
||||
acquire: "Acquire Tier",
|
||||
activate_trial: "Activate Trial",
|
||||
trial_used: "Trial Exhausted",
|
||||
syndicate: "Syndicate Network",
|
||||
recruits: "Recruits",
|
||||
earned: "Eddies Earned",
|
||||
ref_desc: "Expand the grid. Earn 100 E$ per initialized uplink.",
|
||||
copied: "Copied!",
|
||||
plans: {
|
||||
TRIAL: {
|
||||
tag: "Free Trial",
|
||||
desc: "Experience the Grid. 3 days of unrestricted access.",
|
||||
features: ["5GB Bandwidth", "No logs", "Standard Nodes"],
|
||||
},
|
||||
GHOST: {
|
||||
tag: "Personal Uplink",
|
||||
desc: "The ideal choice for personal use. Minimal digital footprint.",
|
||||
@@ -59,16 +65,22 @@ const DICT = {
|
||||
balance: "Баланс",
|
||||
uplink_status: "Статус Узла",
|
||||
expires: "Истекает",
|
||||
no_limit: "Скорость ограничена. Повысьте уровень доступа.",
|
||||
system_override: "Терминал Доступа",
|
||||
manage_plan: "Управление подпиской",
|
||||
acquire: "Арендовать",
|
||||
activate_trial: "Начать Триал",
|
||||
trial_used: "Триал Использован",
|
||||
syndicate: "Синдикат",
|
||||
recruits: "Рекруты",
|
||||
earned: "Заработано (E$)",
|
||||
ref_desc: "Расширяй сеть. Получай 100 E$ за каждый новый узел.",
|
||||
copied: "Скопировано!",
|
||||
plans: {
|
||||
TRIAL: {
|
||||
tag: "Бесплатный Тест",
|
||||
desc: "Испытай Сеть. 3 дня безлимитного доступа.",
|
||||
features: ["5GB Трафика", "Без логов", "Стандартные узлы"],
|
||||
},
|
||||
GHOST: {
|
||||
tag: "Личный Узел",
|
||||
desc: "Идеально для себя. Минимальный цифровой след.",
|
||||
@@ -88,10 +100,18 @@ const DICT = {
|
||||
},
|
||||
};
|
||||
|
||||
// Захардкодим список, чтобы Триал был виден красиво
|
||||
const UI_PLANS = [
|
||||
{ name: "TRIAL", price: 0, price_usd: 0, price_rub: 0 },
|
||||
{ name: "GHOST", price: 5, price_usd: 5, price_rub: 500 },
|
||||
{ name: "NETRUNNER", price: 9, price_usd: 9, price_rub: 900 },
|
||||
{ name: "MAINFRAME", price: 20, price_usd: 20, price_rub: 2000 },
|
||||
];
|
||||
|
||||
export default function Profile() {
|
||||
const [data, setData] = useState<any>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null); // <-- ДОБАВЛЕНО
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [showPlans, setShowPlans] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
@@ -100,17 +120,14 @@ export default function Profile() {
|
||||
(localStorage.getItem("lang") as "en" | "ru") || "en",
|
||||
);
|
||||
const t = DICT[lang];
|
||||
|
||||
const botUsername = import.meta.env.VITE_BOT_USERNAME || "";
|
||||
|
||||
useEffect(() => {
|
||||
fetchProfile()
|
||||
.then((res) => {
|
||||
setData(res);
|
||||
})
|
||||
.then((res) => setData(res))
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
setError(err.message || "Failed to connect to Netrunner Core"); // <-- ДОБАВЛЕНО
|
||||
setError(err.message || "Failed to connect to Netrunner Core");
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
@@ -132,7 +149,6 @@ export default function Profile() {
|
||||
);
|
||||
}
|
||||
|
||||
// <-- ДОБАВЛЕН ЭКРАН ОШИБКИ ВМЕСТО return null -->
|
||||
if (error || !data) {
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col items-center justify-center bg-[#05050A] text-red-500 font-mono p-6 text-center">
|
||||
@@ -150,18 +166,12 @@ export default function Profile() {
|
||||
);
|
||||
}
|
||||
|
||||
// БЕЗОПАСНОЕ ИЗВЛЕЧЕНИЕ: Если чего-то нет, не падаем, а ставим дефолт
|
||||
// Твой бэкенд отдает Json(user), так что data - это и есть объект пользователя
|
||||
const user = data?.user || data || {};
|
||||
const subscription = data?.subscription; // Теперь может быть null
|
||||
// Теперь бэкенд отдает всё как надо: { user: {...}, subscription: {...}, recruits, earned }
|
||||
const user = data.user || {};
|
||||
const subscription = data.subscription;
|
||||
const hasSub = subscription && subscription.plan_name;
|
||||
const recruits = data?.recruits || 0;
|
||||
const earned = data?.earned || 0;
|
||||
const plans = data?.plans || [
|
||||
{ name: "GHOST", price: 5 },
|
||||
{ name: "NETRUNNER", price: 9 },
|
||||
{ name: "MAINFRAME", price: 20 },
|
||||
];
|
||||
const recruits = data.recruits || 0;
|
||||
const earned = data.earned || 0;
|
||||
|
||||
const copyRef = () => {
|
||||
navigator.clipboard.writeText(
|
||||
@@ -172,6 +182,7 @@ export default function Profile() {
|
||||
};
|
||||
|
||||
const getPrice = (usd: number, rub: number) => {
|
||||
if (usd === 0) return "FREE";
|
||||
return lang === "ru" ? `${rub} ₽/мес` : `$${usd}/mo`;
|
||||
};
|
||||
|
||||
@@ -259,19 +270,9 @@ export default function Profile() {
|
||||
{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>
|
||||
<p className="text-xs text-muted-foreground mt-2">
|
||||
No active subscription.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -283,6 +284,7 @@ export default function Profile() {
|
||||
<Terminal className="w-5 h-5 group-hover:animate-pulse relative z-10" />
|
||||
<span className="relative z-10">Run Breach Protocol</span>
|
||||
</button>
|
||||
|
||||
{/* SUBSCRIPTIONS */}
|
||||
<div className="bg-white/[0.03] backdrop-blur-xl border border-white/10 rounded-3xl p-6 shadow-[0_8px_32px_0_rgba(0,0,0,0.3)]">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
@@ -304,18 +306,17 @@ export default function Profile() {
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="grid sm:grid-cols-1 gap-4 mt-4 pb-2">
|
||||
{plans.map((plan: any) => {
|
||||
{UI_PLANS.map((plan: any) => {
|
||||
const planData = t.plans[
|
||||
plan.name as keyof typeof t.plans
|
||||
] || {
|
||||
tag: "Custom Tier",
|
||||
desc: "System expansion.",
|
||||
features: [],
|
||||
};
|
||||
] || { tag: "", desc: "", features: [] };
|
||||
const isTrial = plan.name === "TRIAL";
|
||||
const isTrialUsed = isTrial && user.has_used_trial;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={plan.name}
|
||||
className="bg-black/50 border border-white/10 hover:border-primary/50 rounded-2xl p-4 flex flex-col transition-colors group relative overflow-hidden"
|
||||
className={`bg-black/50 border border-white/10 rounded-2xl p-4 flex flex-col transition-colors group relative overflow-hidden ${isTrialUsed ? "opacity-50 grayscale" : "hover:border-primary/50"}`}
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-primary/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
@@ -328,10 +329,10 @@ export default function Profile() {
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-2xl font-black text-white">
|
||||
{plan.price}{" "}
|
||||
<span className="text-xs text-muted-foreground font-normal">
|
||||
E$/mo
|
||||
</span>
|
||||
{getPrice(
|
||||
plan.price_usd || plan.price,
|
||||
plan.price_rub || plan.price * 100,
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mb-3 relative z-10">
|
||||
@@ -343,14 +344,18 @@ export default function Profile() {
|
||||
))}
|
||||
</ul>
|
||||
<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"
|
||||
onClick={() =>
|
||||
isTrial ? handleTrial() : handleBuy(plan.name)
|
||||
}
|
||||
disabled={isTrialUsed}
|
||||
className={`mt-auto w-full py-2 rounded-xl text-xs font-bold transition-colors relative z-10
|
||||
${isTrialUsed ? "bg-white/5 text-muted-foreground cursor-not-allowed" : "bg-white/5 hover:bg-primary text-white"}`}
|
||||
>
|
||||
{t.acquire}{" "}
|
||||
{getPrice(
|
||||
plan.price_usd || plan.price,
|
||||
plan.price_rub || plan.price * 100,
|
||||
)}
|
||||
{isTrial
|
||||
? isTrialUsed
|
||||
? t.trial_used
|
||||
: t.activate_trial
|
||||
: `${t.acquire}`}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user