errors fix

This commit is contained in:
2026-04-24 17:01:57 +07:00
parent 0dc6562dd2
commit 17a4980329
6 changed files with 203 additions and 82 deletions
+22 -13
View File
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useEffect, useState, Suspense } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Profile from "./Profile";
import HackGame from "./HackGame";
@@ -6,41 +6,39 @@ import { TOKEN_KEY } from "./api";
import AdminDashboard from "./AdminDashboard";
function AuthHandler({ children }: { children: React.ReactNode }) {
// Добавляем состояния для отслеживания загрузки и авторизации
const [isProcessing, setIsProcessing] = useState(true);
const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => {
// 1. Ищем токен в URL
const hash = window.location.hash;
if (hash.includes("token=")) {
const params = new URLSearchParams(hash.replace("#", "?"));
const token = params.get("token");
if (token) {
localStorage.setItem(TOKEN_KEY, token);
// Зачищаем URL от токена, чтобы не светить им
window.history.replaceState(null, "", window.location.pathname);
}
}
// 2. Проверяем наличие токена после обработки URL
if (localStorage.getItem(TOKEN_KEY)) {
setIsAuthenticated(true);
}
// 3. Сигнализируем, что проверка завершена
setIsProcessing(false);
}, []);
// БЛОКИРОВЩИК РЕНДЕРА: Пока обрабатываем токен, не пускаем детей рендериться
if (isProcessing) {
return <div className="min-h-screen bg-[#05050A]" />;
return (
<div className="flex h-full w-full items-center justify-center bg-[#05050A]">
<span className="text-secondary animate-pulse font-mono tracking-widest text-sm uppercase">
Инициализация защищенного соединения...
</span>
</div>
);
}
// Если токена нет, показываем ошибку ПРАВИЛЬНЫМ React-способом
if (!isAuthenticated) {
return (
<div className="min-h-screen bg-[#05050A] flex items-center justify-center font-mono">
<div className="flex h-full w-full items-center justify-center bg-[#05050A] font-mono">
<h1 className="text-[#FF003C] text-xl tracking-widest uppercase animate-pulse drop-shadow-[0_0_15px_rgba(255,0,60,0.5)]">
[ERROR] UNAUTHORIZED ACCESS
</h1>
@@ -48,8 +46,19 @@ function AuthHandler({ children }: { children: React.ReactNode }) {
);
}
// Если токен на месте — пускаем в ЛК
return <>{children}</>;
return (
<div className="relative h-full w-full overflow-hidden bg-black">
<Suspense
fallback={
<div className="flex items-center justify-center h-full text-secondary font-mono">
Загрузка модулей...
</div>
}
>
{children}
</Suspense>
</div>
);
}
export default function App() {
+49 -46
View File
@@ -1,48 +1,68 @@
import { useState } from "react";
import React, { useEffect, useRef, useState } from "react";
import { useNavigate } from "react-router-dom";
import { Cyberhack, type CyberhackResult } from "cyberhack";
import { AlertTriangle } from "lucide-react";
import { submitHackResult } from "./api";
const DICT = {
en: {
title: "SIMULATION MODE WARNING",
text: "Accessing the mainframe without an active Sub-Tier or Hack Tickets will route your connection through a simulation proxy. You can practice your Breach Protocol infinitely, but NO REAL E$ WILL BE REWARDED. Acquire a Tier to earn credits.",
accept: "[Acknowledge & Initialize]",
},
ru: {
title: "ВНИМАНИЕ: РЕЖИМ СИМУЛЯЦИИ",
text: "Доступ к магистрали без активной подписки или игровых тикетов переводит сессию в режим симуляции. Вы можете тренировать Протокол Взлома бесконечно, но РЕАЛЬНЫЕ E$ НАЧИСЛЯТЬСЯ НЕ БУДУТ. Арендуйте Узел для заработка.",
accept: "[Принять и Инициализировать]",
},
};
export default function HackGame() {
const navigate = useNavigate();
const [lang] = useState<"en" | "ru">(
() => (localStorage.getItem("lang") as "en" | "ru") || "en",
);
const t = DICT[lang];
const [showRules, setShowRules] = useState(true);
const canvasRef = useRef<HTMLCanvasElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const [engineReady, setEngineReady] = useState(false);
useEffect(() => {
// 1. TWA Setup
if (typeof window !== "undefined" && (window as any).Telegram?.WebApp) {
const tg = (window as any).Telegram.WebApp;
tg.expand();
if (typeof tg.disableVerticalSwipes === "function") {
tg.disableVerticalSwipes();
}
}
// 2. Resize Observer для вычисления реальных пикселей
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
if (canvasRef.current) {
const { width, height } = entry.contentRect;
canvasRef.current.width = width;
canvasRef.current.height = height;
// Только после получения первых размеров считаем движок готовым к рендеру
if (width > 0 && height > 0 && !engineReady) {
setEngineReady(true);
}
}
}
});
if (containerRef.current) {
resizeObserver.observe(containerRef.current);
}
return () => {
resizeObserver.disconnect();
};
}, [engineReady]);
const handleComplete = async (result: CyberhackResult) => {
console.log("[BREACH_PROTOCOL] Terminated. Output:", result);
try {
const earnedPoints = result.total_coins || 0;
if (earnedPoints > 0) {
await submitHackResult(earnedPoints);
console.log(
`[BREACH_PROTOCOL] ${earnedPoints} E$ synced with Netrunner Core.`,
);
}
} catch (err) {
console.error("[ERROR] Failed to sync data:", err);
}
window.location.href = "https://account.netrunner-vpn.com";
navigate("/profile");
};
return (
<div className="fixed inset-0 z-50 bg-[#05050A] font-mono">
<div
ref={containerRef}
className="absolute inset-0 pointer-events-auto bg-[#05050A] z-50"
>
<button
onClick={() => navigate("/profile")}
className="absolute top-4 right-4 text-muted-foreground hover:text-[#FF003C] z-[60] text-xs uppercase tracking-widest border border-white/10 hover:border-[#FF003C]/50 px-3 py-1 rounded transition-colors bg-black/50 backdrop-blur-sm"
@@ -50,32 +70,15 @@ export default function HackGame() {
[Abort Sequence]
</button>
{showRules && (
<div className="absolute inset-0 z-[70] bg-black/80 backdrop-blur-md flex items-center justify-center p-4">
<div className="bg-[#0B0D17] border border-primary/50 rounded-2xl p-6 sm:p-8 max-w-md w-full shadow-[0_0_40px_rgba(139,61,255,0.15)] relative overflow-hidden">
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-transparent via-primary to-transparent opacity-50" />
<div className="flex items-center gap-3 mb-4">
<AlertTriangle className="w-6 h-6 text-primary animate-pulse" />
<h2 className="text-primary font-black text-lg sm:text-xl tracking-widest uppercase">
{t.title}
</h2>
</div>
<p className="text-muted-foreground text-xs sm:text-sm mb-8 leading-relaxed opacity-90">
{t.text}
</p>
<button
onClick={() => setShowRules(false)}
className="w-full bg-primary/10 hover:bg-primary/30 text-primary border border-primary/50 text-xs sm:text-sm font-bold uppercase tracking-widest py-3 rounded-xl transition-all shadow-[0_0_15px_rgba(139,61,255,0.2)] hover:shadow-[0_0_25px_rgba(139,61,255,0.4)]"
>
{t.accept}
</button>
</div>
{!engineReady && (
<div className="absolute inset-0 flex items-center justify-center text-secondary z-10 font-mono text-sm animate-pulse">
Allocating memory buffer...
</div>
)}
{/* 🔥 ИСПРАВЛЕНО: Убрал flex centering, поставил жесткий absolute inset-0 block */}
{!showRules && (
<div className="absolute inset-0 w-full h-full block bg-[#05050A]">
{/* Отрисовываем компонент только когда контейнер готов */}
{engineReady && (
<div className="absolute inset-0 w-full h-full block">
<Cyberhack
timeLimit={45}
baseValue={50}
+7 -6
View File
@@ -1,7 +1,6 @@
@import "tailwindcss";
@theme {
/* Твоя палитра Netrunner */
--color-background: #0b0d17;
--color-foreground: #f8fafc;
--color-primary: #8b3dff;
@@ -10,16 +9,18 @@
--color-muted-foreground: #a0aec0;
--color-border: #2a2e45;
--color-card: rgba(22, 25, 43, 0.6);
--font-mono: "JetBrains Mono", monospace;
}
/* Сброс стилей для полноэкранного Web App */
@layer base {
body {
@apply bg-background text-foreground font-mono antialiased;
html,
body,
#root {
height: var(--tg-viewport-height, 100dvh);
width: 100vw;
margin: 0;
padding: 0;
overflow-x: hidden;
overflow: hidden; /* Критично для отключения нативного скроллинга TWA */
@apply bg-background text-foreground font-mono antialiased;
}
}