errors fix
This commit is contained in:
+22
-13
@@ -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
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user