From a68d3282ef9cf17378b82663795f369e5d65c572 Mon Sep 17 00:00:00 2001 From: toximu Date: Wed, 11 Mar 2026 22:44:38 +0300 Subject: [PATCH 1/5] connect login and signup pages with backend AuthService --- package-lock.json | 280 ++++++++++++++++++++++++++++++++++++ package.json | 1 + src/api/axiosConfig.js | 38 +++++ src/pages/BattlePage.jsx | 5 +- src/pages/LoginPage.jsx | 301 ++++++++++++++++++++++----------------- 5 files changed, 490 insertions(+), 135 deletions(-) create mode 100644 src/api/axiosConfig.js diff --git a/package-lock.json b/package-lock.json index b8f2f21..ba128eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "@monaco-editor/react": "^4.7.0", + "axios": "^1.13.6", "lucide-react": "^0.575.0", "react": "^19.2.0", "react-dom": "^19.2.0", @@ -2086,6 +2087,23 @@ "node": ">=12" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", + "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2161,6 +2179,19 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2239,6 +2270,18 @@ "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2381,6 +2424,15 @@ "dev": true, "license": "MIT" }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -2409,6 +2461,20 @@ "@types/trusted-types": "^2.0.7" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.302", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", @@ -2429,6 +2495,24 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -2436,6 +2520,33 @@ "dev": true, "license": "MIT" }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.27.3", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", @@ -2795,6 +2906,42 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2810,6 +2957,15 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2820,6 +2976,43 @@ "node": ">=6.9.0" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2846,6 +3039,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2856,6 +3061,45 @@ "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hermes-estree": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", @@ -3206,6 +3450,15 @@ "node": ">= 18" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/mdn-data": { "version": "2.27.1", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", @@ -3213,6 +3466,27 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -3490,6 +3764,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/package.json b/package.json index 2e9e932..b744c07 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "@monaco-editor/react": "^4.7.0", + "axios": "^1.13.6", "lucide-react": "^0.575.0", "react": "^19.2.0", "react-dom": "^19.2.0", diff --git a/src/api/axiosConfig.js b/src/api/axiosConfig.js new file mode 100644 index 0000000..4058757 --- /dev/null +++ b/src/api/axiosConfig.js @@ -0,0 +1,38 @@ +import axios from 'axios'; + +const api = axios.create({ + baseURL: 'http://localhost:8080', + withCredentials: true, + headers: { + 'Content-Type': 'application/json', + }, +}); + + +api.interceptors.response.use( + (response) => response, + async (error) => { + const originalRequest = error.config; + + if (error.response?.status === 401 && !originalRequest._retry) { + originalRequest._retry = true; + + try { + + await axios.post('http://localhost:8080/auth/refresh', {}, { withCredentials: true }); + + + return api(originalRequest); + } catch (refreshError) { + + console.error("Refresh token expired"); + window.location.href = '/login'; + return Promise.reject(refreshError); + } + } + + return Promise.reject(error); + } +); + +export default api; \ No newline at end of file diff --git a/src/pages/BattlePage.jsx b/src/pages/BattlePage.jsx index 0563e01..afc3241 100644 --- a/src/pages/BattlePage.jsx +++ b/src/pages/BattlePage.jsx @@ -5,6 +5,9 @@ import Header from './components/layout/Header'; import Footer from './components/layout/Footer'; import SettingsModal from './components/ui/SettingsModal'; import './BattlePage.css'; +import '../api/axiosConfig.js' +import api from "../api/axiosConfig.js"; + export default function BattlePage() { const navigate = useNavigate(); @@ -26,7 +29,7 @@ export default function BattlePage() { Выбери задачу, напиши оптимальный код и разгроми соперника

- diff --git a/src/pages/LoginPage.jsx b/src/pages/LoginPage.jsx index 7f7dbea..3d976cb 100644 --- a/src/pages/LoginPage.jsx +++ b/src/pages/LoginPage.jsx @@ -1,142 +1,175 @@ -import { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { useUser } from '../context/UserContext'; +import {useState} from 'react'; +import {useNavigate} from 'react-router-dom'; +import {useUser} from '../context/UserContext'; import Header from './components/layout/Header'; import './LoginPage.css'; import '../App.css'; +import '../api/axiosConfig.js'; +import api from "../api/axiosConfig.js"; export default function LoginPage() { - const [view, setView] = useState('login'); - - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const [nickname, setNickname] = useState(''); - const [isLoading, setIsLoading] = useState(false); - const navigate = useNavigate(); - const { login } = useUser(); - - const handleResetToLogin = () => { - setView('login'); - }; - - const handleLogin = async (e) => { - e.preventDefault(); - setIsLoading(true); - try { - await new Promise(resolve => setTimeout(resolve, 800)); - login({ email, nickname: 'Winner' }); - navigate('/battle'); - } catch (error) { - console.error("Ошибка авторизации", error); - } finally { - setIsLoading(false); - } - }; - - const handleRegister = async (e) => { - e.preventDefault(); - setIsLoading(true); - try { - await new Promise(resolve => setTimeout(resolve, 800)); - alert('Регистрация успешна! Теперь вы можете войти.'); - setView('login'); - } catch (error) { - console.error("Ошибка регистрации", error); - } finally { - setIsLoading(false); - } - }; - - const handleForgotPassword = async (e) => { - e.preventDefault(); - setIsLoading(true); - try { - await new Promise(resolve => setTimeout(resolve, 800)); - alert('Инструкции по восстановлению отправлены на ваш email.'); - setView('login'); - } catch (error) { - console.error("Ошибка восстановления", error); - } finally { - setIsLoading(false); - } - }; - - return ( -
-
- -
-
- - {view === 'login' && ( - <> -

Вход в CodeZilla

-
- setEmail(e.target.value)} required - /> - setPassword(e.target.value)} required - /> - -
-
- setView('register')}>Регистрация - setView('forgot')}>Забыли пароль? -
- - )} - - {view === 'register' && ( - <> -

Регистрация

-
- setNickname(e.target.value)} required - /> - setEmail(e.target.value)} required - /> - setPassword(e.target.value)} required - /> - -
-
- setView('login')}>Уже есть аккаунт? Войти -
- - )} - - {view === 'forgot' && ( - <> -

Сброс пароля

-
- setEmail(e.target.value)} required - /> - -
-
- setView('login')}>Вернуться ко входу -
- - )} + const [view, setView] = useState('login'); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [nickname, setNickname] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const navigate = useNavigate(); + + const handleResetToLogin = () => { + setView('login'); + }; + + const handleLogin = async (e) => { + + if (e && e.preventDefault) e.preventDefault(); + + setIsLoading(true); + try { + const response = await api.post( + '/auth/login', + {email: email, rawPassword: password} + ) + + const data = await response.data; + alert("Успешный вход для пользователя " + data.nickname); + navigate("/battle"); + + + } catch (error) { + alert ("Ошибка авторизации: " + error.response.data.message); + } finally { + setIsLoading(false); + } + + }; + + const handleRegister = async (e) => { + if (e && e.preventDefault) e.preventDefault(); + + setIsLoading(true); + try { + const response = await fetch('http://localhost:8080/auth/signup', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + email: email, + rawPassword: password, + nickname: nickname + }), + credentials: 'include', + }); + + if (response.ok) { + const data = await response.json(); + + alert("Успешная регистрация для " + data.nickname); + setView('login'); + + } else { + const errorData = await response.json().catch(() => ({})); + alert(errorData.message); + } + } catch (error) { + alert ("Ошибка авторизации" + error); + } finally { + setIsLoading(false); + } + }; + + const handleForgotPassword = async (e) => { + e.preventDefault(); + setIsLoading(true); + try { + await new Promise(resolve => setTimeout(resolve, 800)); + alert('Инструкции по восстановлению отправлены на ваш email.'); + setView('login'); + } catch (error) { + console.error("Ошибка восстановления", error); + } finally { + setIsLoading(false); + } + }; + + return ( +
+
+ +
+
+ + {view === 'login' && ( + <> +

Вход в CodeZilla

+
+ setEmail(e.target.value)} required + /> + setPassword(e.target.value)} required + /> + +
+
+ setView('register')}>Регистрация + setView('forgot')}>Забыли пароль? +
+ + )} + + {view === 'register' && ( + <> +

Регистрация

+
+ setNickname(e.target.value)} required + /> + setEmail(e.target.value)} required + /> + setPassword(e.target.value)} required + /> + +
+
+ setView('login')}>Уже есть аккаунт? Войти +
+ + )} + + {view === 'forgot' && ( + <> +

Сброс пароля

+
+ setEmail(e.target.value)} required + /> + +
+
+ setView('login')}>Вернуться ко входу +
+ + )} + +
+
-
-
- ); + ); } \ No newline at end of file From d27c61afe2160d1a23483324fbf8d3f230bfb26e Mon Sep 17 00:00:00 2001 From: toximu Date: Wed, 11 Mar 2026 22:49:14 +0300 Subject: [PATCH 2/5] delete unused variables --- src/pages/BattlePage.jsx | 2 +- src/pages/LoginPage.jsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/BattlePage.jsx b/src/pages/BattlePage.jsx index afc3241..9041439 100644 --- a/src/pages/BattlePage.jsx +++ b/src/pages/BattlePage.jsx @@ -6,7 +6,7 @@ import Footer from './components/layout/Footer'; import SettingsModal from './components/ui/SettingsModal'; import './BattlePage.css'; import '../api/axiosConfig.js' -import api from "../api/axiosConfig.js"; + export default function BattlePage() { diff --git a/src/pages/LoginPage.jsx b/src/pages/LoginPage.jsx index 3d976cb..a0935ee 100644 --- a/src/pages/LoginPage.jsx +++ b/src/pages/LoginPage.jsx @@ -1,6 +1,5 @@ import {useState} from 'react'; import {useNavigate} from 'react-router-dom'; -import {useUser} from '../context/UserContext'; import Header from './components/layout/Header'; import './LoginPage.css'; import '../App.css'; From 496bca2e4638f68bab6d503e28cfaa6d8f042d5a Mon Sep 17 00:00:00 2001 From: toximu Date: Tue, 17 Mar 2026 16:18:20 +0300 Subject: [PATCH 3/5] change tests --- src/pages/LoginPage.jsx | 34 ++++-------- src/pages/LoginPage.test.jsx | 105 ++++++++++++++++++++++++++--------- 2 files changed, 90 insertions(+), 49 deletions(-) diff --git a/src/pages/LoginPage.jsx b/src/pages/LoginPage.jsx index a0935ee..b4cc8b8 100644 --- a/src/pages/LoginPage.jsx +++ b/src/pages/LoginPage.jsx @@ -47,32 +47,18 @@ export default function LoginPage() { if (e && e.preventDefault) e.preventDefault(); setIsLoading(true); + try { - const response = await fetch('http://localhost:8080/auth/signup', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - email: email, - rawPassword: password, - nickname: nickname - }), - credentials: 'include', - }); - - if (response.ok) { - const data = await response.json(); - - alert("Успешная регистрация для " + data.nickname); - setView('login'); - - } else { - const errorData = await response.json().catch(() => ({})); - alert(errorData.message); - } + const response = await api.post( + '/auth/signup', + {email: email, rawPassword: password} + ) + + const data = await response.data; + alert("Успешная регистрация для " + data.nickname); + setView("login"); } catch (error) { - alert ("Ошибка авторизации" + error); + alert ("Ошибка регистрации: " + error.response.data.message); } finally { setIsLoading(false); } diff --git a/src/pages/LoginPage.test.jsx b/src/pages/LoginPage.test.jsx index 8382a63..31a34cc 100644 --- a/src/pages/LoginPage.test.jsx +++ b/src/pages/LoginPage.test.jsx @@ -1,9 +1,11 @@ -import { render, screen, waitFor } from '@testing-library/react'; +import {render, screen, waitFor, within} from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { BrowserRouter } from 'react-router-dom'; import LoginPage from './LoginPage'; -import * as UserContext from '../context/UserContext'; +import api from '../api/axiosConfig'; +import * as UserContext from '../context/UserContext.jsx'; + const mockNavigate = vi.fn(); vi.mock('react-router-dom', async () => { @@ -14,13 +16,22 @@ vi.mock('react-router-dom', async () => { }; }); + +vi.mock('../api/axiosConfig', () => ({ + default: { + post: vi.fn(), + }, +})); + describe('LoginPage', () => { beforeEach(() => { vi.clearAllMocks(); - vi.spyOn(UserContext, 'useUser').mockReturnValue({ - user: null, - login: vi.fn(), - logout: vi.fn() + vi.spyOn(window, 'alert').mockImplementation(() => {}); + + vi.spyOn(UserContext, 'useUser').mockReturnValue({ + user: null, + login: vi.fn(), + logout: vi.fn() }); }); @@ -33,33 +44,45 @@ describe('LoginPage', () => { await user.click(screen.getByText('Регистрация')); expect(screen.getByRole('heading', { name: 'Регистрация' })).toBeInTheDocument(); + await user.click(screen.getByText('CodeZilla')); - expect(screen.getByRole('heading', { name: 'Вход в CodeZilla' })).toBeInTheDocument(); }); - it('успешный логин перенаправляет на /battle', async () => { + it('успешный логин вызывает API и перенаправляет на /battle', async () => { const user = userEvent.setup(); + api.post.mockResolvedValueOnce({ data: { nickname: 'Tester' } }); + renderComponent(); - await user.type(screen.getByPlaceholderText('Email'), 'test@test.com'); - await user.type(screen.getByPlaceholderText('Пароль'), '123456'); - - const loginButtons = screen.getAllByRole('button', { name: /Войти/i }); - - await user.click(loginButtons[1]); - expect(screen.getByRole('button', { name: 'Вход...' })).toBeInTheDocument(); + const loginForm = screen.getByRole('heading', { name: /Вход в CodeZilla/i }).closest('.glass-panel'); + + + const emailInput = within(loginForm).getByPlaceholderText('Email'); + const passwordInput = within(loginForm).getByPlaceholderText('Пароль'); + const submitButton = within(loginForm).getByRole('button', { name: /Войти/i }); + + + await user.type(emailInput, 'test@test.com'); + await user.type(passwordInput, '123456'); + await user.click(submitButton); await waitFor(() => { + expect(api.post).toHaveBeenCalledWith('/auth/login', { + email: 'test@test.com', + rawPassword: '123456' + }); expect(mockNavigate).toHaveBeenCalledWith('/battle'); - }, { timeout: 1500 }); + }); }); - it('регистрация показывает alert и возвращает на форму логина', async () => { + it('регистрация вызывает API, показывает alert и возвращает на форму логина', async () => { const user = userEvent.setup(); - const alertMock = vi.spyOn(window, 'alert').mockImplementation(() => {}); - + api.post.mockResolvedValueOnce({ + data: { nickname: 'Hero' } + }); + renderComponent(); await user.click(screen.getByText('Регистрация')); @@ -69,11 +92,43 @@ describe('LoginPage', () => { await user.click(screen.getByRole('button', { name: 'Зарегистрироваться' })); await waitFor(() => { - expect(alertMock).toHaveBeenCalledWith('Регистрация успешна! Теперь вы можете войти.'); - }, { timeout: 1500 }); - - expect(screen.getByRole('heading', { name: 'Вход в CodeZilla' })).toBeInTheDocument(); - - alertMock.mockRestore(); + + expect(api.post).toHaveBeenCalledWith('/auth/signup', { + email: 'test@test.com', + rawPassword: '123456' + }); + + expect(window.alert).toHaveBeenCalledWith(expect.stringContaining("Успешная регистрация для Hero")); + + expect(screen.getByRole('heading', { name: 'Вход в CodeZilla' })).toBeInTheDocument(); + }); + }); + + it('показывает ошибку, если API логина вернул ошибку', async () => { + const user = userEvent.setup(); + + api.post.mockRejectedValueOnce({ + response: { data: { message: 'Неверный пароль' } } + }); + + renderComponent(); + + + const loginPanel = screen.getByRole('heading', { name: /Вход в CodeZilla/i }).closest('.glass-panel'); + const form = within(loginPanel); + + await user.type(form.getByPlaceholderText('Email'), 'test@test.com'); + await user.type(form.getByPlaceholderText('Пароль'), 'wrong'); + + const submitButton = form.getByRole('button', { + name: /Войти/i, + selector: 'button[type="submit"]' + }); + + await user.click(submitButton); + + await waitFor(() => { + expect(window.alert).toHaveBeenCalledWith("Ошибка авторизации: Неверный пароль"); + }); }); }); \ No newline at end of file From c153d38e58f23f9b30bc22ee5ab31b7768875af4 Mon Sep 17 00:00:00 2001 From: toximu Date: Tue, 17 Mar 2026 16:35:36 +0300 Subject: [PATCH 4/5] fix register --- src/pages/LoginPage.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/LoginPage.jsx b/src/pages/LoginPage.jsx index b4cc8b8..8eb915b 100644 --- a/src/pages/LoginPage.jsx +++ b/src/pages/LoginPage.jsx @@ -51,7 +51,7 @@ export default function LoginPage() { try { const response = await api.post( '/auth/signup', - {email: email, rawPassword: password} + {email: email, rawPassword: password, nickname: nickname} ) const data = await response.data; From 0244ea07b115a573589542640eab13f33fe57698 Mon Sep 17 00:00:00 2001 From: toximu Date: Tue, 17 Mar 2026 16:44:05 +0300 Subject: [PATCH 5/5] fix register --- src/pages/LoginPage.test.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/LoginPage.test.jsx b/src/pages/LoginPage.test.jsx index 31a34cc..d0c00c6 100644 --- a/src/pages/LoginPage.test.jsx +++ b/src/pages/LoginPage.test.jsx @@ -94,6 +94,7 @@ describe('LoginPage', () => { await waitFor(() => { expect(api.post).toHaveBeenCalledWith('/auth/signup', { + nickname: 'Hero', email: 'test@test.com', rawPassword: '123456' });