diff --git a/package-lock.json b/package-lock.json index c0f017c..1acfeac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,16 @@ "version": "0.0.0", "dependencies": { "@monaco-editor/react": "^4.7.0", + "@stomp/stompjs": "^7.0.0", "axios": "^1.13.6", "lucide-react": "^0.575.0", "react": "^19.2.0", "react-dom": "^19.2.0", + "react-hot-toast": "^2.6.0", "react-resizable-panels": "^3.0.6", - "react-router-dom": "^7.13.1" + "react-router-dom": "^7.13.1", + "sockjs-client": "^1.6.1", + "zustand": "^5.0.13" }, "devDependencies": { "@eslint/js": "^9.39.1", @@ -1648,6 +1652,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@stomp/stompjs": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@stomp/stompjs/-/stompjs-7.3.0.tgz", + "integrity": "sha512-nKMLoFfJhrQAqkvvKd1vLq/cVBGCMwPRCD0LqW7UT1fecRx9C3GoKEIR2CYwVuErGeZu8w0kFkl2rlhPlqHVgQ==", + "license": "Apache-2.0" + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -1827,7 +1837,7 @@ "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -2375,7 +2385,6 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, "license": "MIT" }, "node_modules/data-urls": { @@ -2806,6 +2815,15 @@ "node": ">=0.10.0" } }, + "node_modules/eventsource": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", + "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/expect-type": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", @@ -2837,6 +2855,18 @@ "dev": true, "license": "MIT" }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -3039,6 +3069,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/goober": { + "version": "2.1.19", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.19.tgz", + "integrity": "sha512-U7veizMqxyKlM58+Z5j2ngJBH/r9siDmxpvNxSw0PylF6WQvrASJEZrxh1hidRBJc2jqoBVSyOban5u8m+6Rxg==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -3130,6 +3169,12 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "license": "MIT" + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -3205,6 +3250,12 @@ "node": ">=8" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3525,7 +3576,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -3783,6 +3833,12 @@ "node": ">=6" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" + }, "node_modules/react": { "version": "19.2.4", "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", @@ -3804,6 +3860,23 @@ "react": "^19.2.4" } }, + "node_modules/react-hot-toast": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz", + "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -3894,6 +3967,12 @@ "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -3949,6 +4028,26 @@ "fsevents": "~2.3.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -4014,6 +4113,34 @@ "dev": true, "license": "ISC" }, + "node_modules/sockjs-client": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.6.1.tgz", + "integrity": "sha512-2g0tjOR+fRs0amxENLi/q5TiJTqY+WXFOzb5UwXndlK6TO3U/mirZznpx6w34HVMoc3g7cY24yC/ZMIYnDlfkw==", + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "eventsource": "^2.0.2", + "faye-websocket": "^0.11.4", + "inherits": "^2.0.4", + "url-parse": "^1.5.10" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://tidelift.com/funding/github/npm/sockjs-client" + } + }, + "node_modules/sockjs-client/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -4244,6 +4371,16 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/vite": { "version": "7.3.2", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", @@ -4420,6 +4557,29 @@ "node": ">=20" } }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/whatwg-mimetype": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", @@ -4547,6 +4707,35 @@ "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } + }, + "node_modules/zustand": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.13.tgz", + "integrity": "sha512-efI2tVaVQPqtOh114loML/Z80Y4NP3yc+Ff0fYiZJPauNeWZeIp/bRFD7I9bfmCOYBh/PHxlglQ9+wvlwnPikQ==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index b744c07..ef69b79 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,11 @@ "react": "^19.2.0", "react-dom": "^19.2.0", "react-resizable-panels": "^3.0.6", - "react-router-dom": "^7.13.1" + "react-router-dom": "^7.13.1", + "@stomp/stompjs": "^7.0.0", + "sockjs-client": "^1.6.1", + "zustand": "^5.0.13", + "react-hot-toast": "^2.6.0" }, "devDependencies": { "@eslint/js": "^9.39.1", diff --git a/src/App.jsx b/src/App.jsx index f697b0c..006629b 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,21 +1,57 @@ -import {BrowserRouter as Router, Routes, Route, Navigate} from 'react-router-dom'; +import {BrowserRouter as Router, Routes, Route, Navigate, useParams, Outlet} from 'react-router-dom'; import LoginPage from './pages/LoginPage'; import BattlePage from './pages/BattlePage'; import WorkspacePage from './pages/WorkspacePage'; import './App.css'; import {ProfilePage} from "./pages/ProfilePage.jsx"; +import {WebSocketProvider} from "./context/WebSocketContext.jsx"; +import DraftPage from "./pages/DraftPage.jsx"; +import {MatchListener} from "./pages/MatchListener.jsx"; +import {Toaster} from "react-hot-toast"; +import {UserProvider} from "./context/UserContext.jsx"; + +const MatchLayout = () => { + const {matchId} = useParams(); + + return ( + + + + ); +}; function App() { + return ( - - }/> + + + + + }/> + + }/> + }/> + }/> + }> + }/> + }/> + - }/> - }/> - }/> - }/> - + + + ); } diff --git a/src/context/WebSocketContext.jsx b/src/context/WebSocketContext.jsx new file mode 100644 index 0000000..cad43fb --- /dev/null +++ b/src/context/WebSocketContext.jsx @@ -0,0 +1,54 @@ +import React, { createContext, useContext, useEffect, useRef, useState } from 'react'; +import { Client } from '@stomp/stompjs'; +import SockJS from 'sockjs-client'; + +const WebSocketContext = createContext(null); + +export const WebSocketProvider = ({ children }) => { + const stompClientRef = useRef(null); + const [isConnected, setIsConnected] = useState(false); + + useEffect(() => { + const client = new Client({ + webSocketFactory: () => new SockJS('http://localhost:8080/game-ws'), + reconnectDelay: 5000, + onConnect: () => setIsConnected(true), + onDisconnect: () => setIsConnected(false), + debug: (str) => console.log(`[STOMP Debug] ${str}`), + }); + + client.activate(); + stompClientRef.current = client; + + return () => { + if (stompClientRef.current) { + stompClientRef.current.deactivate(); + } + }; + }, []); + + const subscribe = (destination, callback) => { + if (stompClientRef.current && isConnected) { + return stompClientRef.current.subscribe(destination, callback); + } + return null; + }; + + const publish = (destination, body) => { + if (stompClientRef.current && isConnected) { + stompClientRef.current.publish({ + destination, + body: JSON.stringify(body), + }); + } + }; + + return ( + + + {children} + + ); +}; + +export const useWebSocket = () => useContext(WebSocketContext); \ No newline at end of file diff --git a/src/pages/BattlePage.jsx b/src/pages/BattlePage.jsx index 9041439..244fc10 100644 --- a/src/pages/BattlePage.jsx +++ b/src/pages/BattlePage.jsx @@ -7,13 +7,16 @@ import SettingsModal from './components/ui/SettingsModal'; import './BattlePage.css'; import '../api/axiosConfig.js' - +if (typeof global === 'undefined') { + window.global = window; +} export default function BattlePage() { const navigate = useNavigate(); const { logout } = useUser(); const [showSettings, setShowSettings] = useState(false); + const handleLogout = () => { logout(); navigate('/login'); @@ -24,7 +27,7 @@ export default function BattlePage() {
setShowSettings(true)} />
-

Арена CodeZilla

+

Арена CodZilla

Выбери задачу, напиши оптимальный код и разгроми соперника

diff --git a/src/pages/DraftPage.jsx b/src/pages/DraftPage.jsx new file mode 100644 index 0000000..3cdc3ae --- /dev/null +++ b/src/pages/DraftPage.jsx @@ -0,0 +1,141 @@ +import React from 'react'; +import { useParams } from 'react-router-dom'; +import { useMatchSession } from './useMatchSession.js'; + +const DraftPage = () => { + + const { matchId } = useParams(); + const { sessionData, sendBan, isConnected } = useMatchSession(matchId); + + if (!isConnected) return
Установка соединения с сервером...
; + if (!sessionData) return
Загрузка данных драфта...
; + + const { remainOptions, firstUserMove } = sessionData; + + return ( +
+ +
+

Draft Session #{matchId.substring(0, 8)}

+
+ {firstUserMove ? "Ход Игрока 1" : "Ход Игрока 2"} +
+
+ +
+ +
+ {Object.entries(remainOptions).map(([category, options]) => ( +
+

{category}

+
+ {options.length > 0 ? ( + options.map((option) => ( + + )) + ) : ( +

Опций не осталось

+ )} +
+
+ ))} +
+
+ +
+

Выберите категорию и опцию, которую хотите исключить из матча.

+
+
+ ); +}; + +const styles = { + container: { + fontFamily: 'Inter, system-ui, sans-serif', + backgroundColor: '#f8fafc', + minHeight: '100vh', + padding: '20px', + color: '#1e293b' + }, + header: { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: '30px', + borderBottom: '2px solid #e2e8f0', + paddingBottom: '10px' + }, + turnIndicator: { + padding: '10px 20px', + borderRadius: '8px', + color: 'white', + fontWeight: 'bold', + transition: 'all 0.3s ease' + }, + errorBanner: { + backgroundColor: '#fef2f2', + border: '1px solid #fee2e2', + color: '#dc2626', + padding: '15px', + borderRadius: '8px', + marginBottom: '20px', + animation: 'shake 0.5s ease' + }, + grid: { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', + gap: '20px' + }, + categoryCard: { + backgroundColor: 'white', + borderRadius: '12px', + padding: '20px', + boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)', + border: '1px solid #e2e8f0' + }, + categoryTitle: { + fontSize: '1.25rem', + marginBottom: '15px', + color: '#475569', + borderLeft: '4px solid #64748b', + paddingLeft: '10px' + }, + optionsList: { + display: 'flex', + flexWrap: 'wrap', + gap: '10px' + }, + banButton: { + backgroundColor: '#fff', + border: '1px solid #e2e8f0', + padding: '8px 16px', + borderRadius: '6px', + cursor: 'pointer', + transition: 'all 0.2s', + fontSize: '0.9rem', + fontWeight: '500' + }, + emptyText: { + color: '#94a3b8', + fontStyle: 'italic' + }, + footer: { + marginTop: '40px', + textAlign: 'center', + color: '#64748b', + fontSize: '0.875rem' + } +}; + +export default DraftPage; \ No newline at end of file diff --git a/src/pages/LoginPage.jsx b/src/pages/LoginPage.jsx index e00cf71..b2825af 100644 --- a/src/pages/LoginPage.jsx +++ b/src/pages/LoginPage.jsx @@ -6,12 +6,13 @@ import '../App.css'; import '../api/axiosConfig.js'; import {api} from "../api/axiosConfig.js"; import {useUser} from "../context/UserContext.jsx"; +import toast from "react-hot-toast"; export default function LoginPage() { const [view, setView] = useState('login'); -const {login} = useUser(); + const {login} = useUser(); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [nickname, setNickname] = useState(''); @@ -35,7 +36,7 @@ const {login} = useUser(); ) const data = await response.data; - + data.email = email; const responseIcon = await api.get( '/user/icon-url' ) @@ -44,11 +45,10 @@ const {login} = useUser(); data.iconUrl = dataIcon; login(data); - alert("Успешный вход для пользователя " + data.nickname); navigate("/battle"); } catch (error) { - alert ("Ошибка авторизации: " + error.response.data.message); + toast.error("Ошибка авторизации: " + error.response.data.message); } finally { setIsLoading(false); } @@ -99,7 +99,7 @@ const {login} = useUser(); {view === 'login' && ( <> -

Вход в CodeZilla

+

Вход в CodZilla

{ expect(screen.getByRole('heading', { name: 'Регистрация' })).toBeInTheDocument(); - await user.click(screen.getByText('CodeZilla')); - expect(screen.getByRole('heading', { name: 'Вход в CodeZilla' })).toBeInTheDocument(); + await user.click(screen.getByText('CodZilla')); + expect(screen.getByRole('heading', { name: 'Вход в CodZilla' })).toBeInTheDocument(); }); it('успешный логин вызывает API и перенаправляет на /battle', async () => { @@ -65,7 +65,7 @@ describe('LoginPage', () => { renderComponent(); - const loginForm = screen.getByRole('heading', { name: /Вход в CodeZilla/i }).closest('.glass-panel'); + const loginForm = screen.getByRole('heading', { name: /Вход в CodZilla/i }).closest('.glass-panel'); const emailInput = within(loginForm).getByPlaceholderText('Email'); @@ -110,7 +110,7 @@ describe('LoginPage', () => { expect(window.alert).toHaveBeenCalledWith(expect.stringContaining("Успешная регистрация для Hero")); - expect(screen.getByRole('heading', { name: 'Вход в CodeZilla' })).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: 'Вход в CodZilla' })).toBeInTheDocument(); }); }); @@ -124,7 +124,7 @@ describe('LoginPage', () => { renderComponent(); - const loginPanel = screen.getByRole('heading', { name: /Вход в CodeZilla/i }).closest('.glass-panel'); + const loginPanel = screen.getByRole('heading', { name: /Вход в CodZilla/i }).closest('.glass-panel'); const form = within(loginPanel); await user.type(form.getByPlaceholderText('Email'), 'test@test.com'); diff --git a/src/pages/MatchListener.jsx b/src/pages/MatchListener.jsx new file mode 100644 index 0000000..886dd21 --- /dev/null +++ b/src/pages/MatchListener.jsx @@ -0,0 +1,40 @@ +import { useEffect } from 'react'; +import { useWebSocket } from '../context/WebSocketContext'; +import { useMatchStore } from './useMatchStore.js'; + +export const MatchListener = ({ matchId, children }) => { + const { isConnected, subscribe } = useWebSocket(); + + const handleIncomingMessage = useMatchStore((state) => state.handleIncomingMessage); + const handleErrorMessage = useMatchStore((state) => state.handleErrorMessage); + const resetStore = useMatchStore((state) => state.resetStore); + useEffect(() => { + if (!isConnected) return; + + const sessionSub = subscribe(`/topic/match/${matchId}`, (msg) => { + try { + const msg_j = JSON.parse(msg.body); + handleIncomingMessage(msg_j); + } catch (err) { + console.error(err); + } + }); + + const errorSub = subscribe('/user/queue/errors', (msg) => { + try { + const payload = JSON.parse(msg.body); + handleErrorMessage(payload); + } catch (err) { + console.error(err); + } + }); + + return () => { + if (sessionSub) sessionSub.unsubscribe(); + if (errorSub) errorSub.unsubscribe(); + resetStore(); + }; + }, [isConnected, matchId, handleIncomingMessage, resetStore, subscribe]); + + return children; +}; \ No newline at end of file diff --git a/src/pages/WorkspacePage.test.jsx b/src/pages/WorkspacePage.test.jsx index ca1a1d4..9d5f418 100644 --- a/src/pages/WorkspacePage.test.jsx +++ b/src/pages/WorkspacePage.test.jsx @@ -50,7 +50,7 @@ describe('WorkspacePage', () => { renderComponent(); - await user.click(screen.getByText('CodeZilla')); + await user.click(screen.getByText('CodZilla')); expect(confirmMock).toHaveBeenCalledWith('Вы точно хотите покинуть битву? Ваш прогресс может быть потерян.'); expect(mockNavigate).toHaveBeenCalledWith('/battle'); diff --git a/src/pages/components/layout/Footer.jsx b/src/pages/components/layout/Footer.jsx index 8214129..f186b96 100644 --- a/src/pages/components/layout/Footer.jsx +++ b/src/pages/components/layout/Footer.jsx @@ -1,7 +1,7 @@ export default function Footer() { return (
- © 2026 CodeZilla | ltc. Все права защищены. + © 2026 CodZilla | ltc. Все права защищены.
); } \ No newline at end of file diff --git a/src/pages/components/layout/Header.jsx b/src/pages/components/layout/Header.jsx index 4074f49..12f13f2 100644 --- a/src/pages/components/layout/Header.jsx +++ b/src/pages/components/layout/Header.jsx @@ -44,7 +44,7 @@ export default function Header({ onSettingsClick, onNavClick }) {
-

CodeZilla

+

CodZilla