summaryrefslogtreecommitdiff
path: root/front/src
diff options
context:
space:
mode:
authorphyscik <mynameisgennadiy@vk.com>2026-04-28 23:33:59 +0500
committerphyscik <mynameisgennadiy@vk.com>2026-04-28 23:33:59 +0500
commit533c04d9558bd0a575671c157ca42236f59a2c5a (patch)
tree895ec8672f9633fd6cfef85f6a12921fc5339ab3 /front/src
parentec5ef35ed47b90b9f09199d28f7885f8287815a8 (diff)
frontend auth
Diffstat (limited to 'front/src')
-rw-r--r--front/src/App.css1
-rw-r--r--front/src/App.tsx42
-rw-r--r--front/src/Authentication/ContextProvider.ts38
-rw-r--r--front/src/Authentication/LoginPage.tsx60
-rw-r--r--front/src/Authentication/Models.ts4
-rw-r--r--front/src/Emelents/Elements.css7
-rw-r--r--front/src/Emelents/Sidebar.tsx1
-rw-r--r--front/src/Emelents/Topbar.tsx12
-rw-r--r--front/src/Locales/ru_RU.ts7
-rw-r--r--front/src/index.tsx10
10 files changed, 165 insertions, 17 deletions
diff --git a/front/src/App.css b/front/src/App.css
index dcfa013..d58f45b 100644
--- a/front/src/App.css
+++ b/front/src/App.css
@@ -19,3 +19,4 @@
padding-left: 10px;
padding-right: 10px;
}
+
diff --git a/front/src/App.tsx b/front/src/App.tsx
index 2f69086..c106139 100644
--- a/front/src/App.tsx
+++ b/front/src/App.tsx
@@ -6,11 +6,15 @@ import IndexElement from './Emelents/IndexElement';
import ClassesList from './Emelents/ClassList';
import Topbar from './Emelents/Topbar';
import { AllowedLanguages, LanguageContext } from './Locales/Context';
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
import { ReadCurrentLocale, SaveCurrentLocale } from './Locales/Locales';
import LegalNote from './Emelents/LegalNote';
import WeaponsIndex from './Pages/Weapons';
import RangedWeaponPage from './Pages/RangedWeapon';
+import { AuthContext, Authentication, GetUserInfo } from './Authentication/ContextProvider';
+import { useCookies } from 'react-cookie';
+import LoginPage from './Authentication/LoginPage';
+import { User } from './Authentication/Models';
const router = createBrowserRouter([
{
@@ -29,6 +33,10 @@ const router = createBrowserRouter([
path: "/weapons/:id",
element: (<RangedWeaponPage />),
},
+ {
+ path: "/login",
+ element: (<LoginPage />),
+ },
]);
function App() {
@@ -40,20 +48,28 @@ function App() {
setLang(newLang);
}
+ const [authCookie, ] = useCookies(['X-AUTH-TOKEN']);
+ const [auth, setAuth] = useState<User | null>(null);
+ useEffect(() => {
+ GetUserInfo(authCookie['X-AUTH-TOKEN']).then(v => setAuth(v));
+ }, []);
+
return (
- <LanguageContext.Provider value={lang}>
- <div className='App'>
- <Topbar />
- <div className='AppContents'>
- <Sidebar
- setLang={SetLanguage}
- />
- <RouterProvider router={router} />
+ <AuthContext.Provider value={auth}>
+ <LanguageContext.Provider value={lang}>
+ <div className='App'>
+ <Topbar />
+ <div className='AppContents'>
+ <Sidebar
+ setLang={SetLanguage}
+ />
+ <RouterProvider router={router} />
+ </div>
+ <LegalNote />
</div>
- <LegalNote />
- </div>
- </LanguageContext.Provider>
+ </LanguageContext.Provider>
+ </AuthContext.Provider>
);
}
-export default App;
+export default App;
diff --git a/front/src/Authentication/ContextProvider.ts b/front/src/Authentication/ContextProvider.ts
new file mode 100644
index 0000000..040d22e
--- /dev/null
+++ b/front/src/Authentication/ContextProvider.ts
@@ -0,0 +1,38 @@
+import axios from "axios"
+import { createContext } from "react"
+import { BackendURL } from "../Config"
+import { User } from "./Models"
+
+export type Authentication = {
+ User: User
+ Token: string
+}
+
+const UserInfoURL = `${BackendURL}/auth`;
+
+export const AuthContext = createContext<User | null>(null);
+
+export function SaveState(data: Authentication | null, setCookie: (cookie: string) => void) {
+ if (data == null) return;
+ setCookie(data.Token);
+}
+
+export async function GetUserInfo(displayToken: string): Promise<User | null> {
+ if (!displayToken || displayToken.length == 0) return null;
+ try {
+ const { data, status } = await axios.get<User>(
+ UserInfoURL,
+ {
+ headers: {
+ Accept: "application/json",
+ Authorization: `Bearer ${displayToken}`
+ }
+ }
+ );
+ if (status != 200) return null;
+ return data;
+ } catch (err) {
+ console.log(`Failed to get user info: ${err}`);
+ return null;
+ }
+}
diff --git a/front/src/Authentication/LoginPage.tsx b/front/src/Authentication/LoginPage.tsx
new file mode 100644
index 0000000..e98c506
--- /dev/null
+++ b/front/src/Authentication/LoginPage.tsx
@@ -0,0 +1,60 @@
+import axios from "axios";
+import { useContext, useState } from "react";
+import { useNavigate } from "react-router";
+import { BackendURL } from "../Config";
+import { Authentication, SaveState } from "./ContextProvider";
+import { useCookies } from 'react-cookie';
+import { GetLocalizedString } from "../Locales/Locales";
+import { LanguageContext } from "../Locales/Context";
+
+const LoginURL = `${BackendURL}/auth/login`;
+
+function LoginPage() {
+ const lang = useContext(LanguageContext);
+
+ const [username, setUsername] = useState<string>("");
+ const [passw, setPassw] = useState<string>("");
+ const navigate = useNavigate();
+
+ function SetAuthState(newAuthState: Authentication | null) {
+ if (newAuthState) {
+ console.log(`Logging in as ${newAuthState.User}...`);
+ } else {
+ console.log(`Logging out...`);
+ }
+ SaveState(newAuthState, (cookie: string) => {
+ document.cookie = `X-AUTH-TOKEN=${cookie}; path=/;`;
+ })
+ }
+
+ return (
+ <div>
+ <h2> { GetLocalizedString("Username", lang) } </h2>
+ <input onChange={ev => setUsername(ev.target.value)} />
+ <h2> { GetLocalizedString("Password", lang) } </h2>
+ <input onChange={ev => setPassw(ev.target.value)} />
+ <button onClick={() => {
+ Login(username, passw, (data) => {
+ SetAuthState(data);
+ navigate("/");
+ window.location.reload();
+ });
+ }}> { GetLocalizedString("Log in", lang) } </button>
+ </div>
+ );
+}
+
+async function Login(username: string, passw: string, onSuccess: (data: Authentication) => void) {
+ await axios.post<Authentication>(
+ LoginURL, {
+ Username: username,
+ Password: passw
+ }
+ ).then(resp => {
+ onSuccess(resp.data);
+ }).catch(err => {
+ console.log(`Failed to send a login responce: ${err}`);
+ });
+}
+
+export default LoginPage;
diff --git a/front/src/Authentication/Models.ts b/front/src/Authentication/Models.ts
new file mode 100644
index 0000000..1918b85
--- /dev/null
+++ b/front/src/Authentication/Models.ts
@@ -0,0 +1,4 @@
+export type User = {
+ Username: string,
+ Role: "user" | "editor"
+}
diff --git a/front/src/Emelents/Elements.css b/front/src/Emelents/Elements.css
index 6f23c13..7c8c15f 100644
--- a/front/src/Emelents/Elements.css
+++ b/front/src/Emelents/Elements.css
@@ -21,9 +21,16 @@
}
.Topbar > h1 {
+ margin-top: 0px;
+ flex-direction: row;
margin-bottom: 0px;
}
+.Topbar > h1 > img {
+ height: 1em;
+ width: auto;
+}
+
.TopbarContents {
border-top: 2px solid var(--colorscheme-black);
display: flex;
diff --git a/front/src/Emelents/Sidebar.tsx b/front/src/Emelents/Sidebar.tsx
index c650145..ad565fa 100644
--- a/front/src/Emelents/Sidebar.tsx
+++ b/front/src/Emelents/Sidebar.tsx
@@ -19,6 +19,7 @@ function Sidebar({setLang}: SidebarProps) {
<SidebarListElement Href='/' Text='Home' />
<SidebarListElement Href='/classes' Text='Classes' />
<SidebarListElement Href='/weapons' Text='Weapons' />
+ <SidebarListElement Href='/login' Text='Login' />
</ul>
<button onClick={() => {
setLang("ru");
diff --git a/front/src/Emelents/Topbar.tsx b/front/src/Emelents/Topbar.tsx
index 73e0e38..284cbb9 100644
--- a/front/src/Emelents/Topbar.tsx
+++ b/front/src/Emelents/Topbar.tsx
@@ -1,4 +1,5 @@
import { useContext } from "react";
+import { AuthContext } from "../Authentication/ContextProvider";
import { LanguageContext } from "../Locales/Context";
import { GetLocalizedString } from "../Locales/Locales";
@@ -6,11 +7,20 @@ const defaultPathName = 'index';
function Topbar() {
var language = useContext(LanguageContext);
+ var user = useContext(AuthContext);
let path = getTopbarElement(window.location.pathname);
+ function getTopText() {
+ if (user == null) {
+ return <h1> View from the edge </h1>;
+ } else {
+ return <h1> <img src={"/EBM.webp"} /> { `${GetLocalizedString("Welcome", language)}, ${user.Username}` } </h1>;
+ }
+ }
+
return (
<div className="Topbar">
- <h1> View from the edge </h1>
+ { getTopText() }
<div className="TopbarContents">
<h2> {GetLocalizedString(path, language)} </h2>
</div>
diff --git a/front/src/Locales/ru_RU.ts b/front/src/Locales/ru_RU.ts
index 7802055..f10ac32 100644
--- a/front/src/Locales/ru_RU.ts
+++ b/front/src/Locales/ru_RU.ts
@@ -8,9 +8,15 @@ lang.LocalizedStrings = new Map<string, string>([
["contents", "Содержание"],
["classes list", "Список классов"],
["weapons", "Оружие"],
+ ["login", "Вход"],
["weapons index", "Список вооружения"],
["ranged weapons", "Дальнобойное оружие"],
+ ["username", "Имя пользователя"],
+ ["password", "Пароль"],
+ ["log in", "Войти"],
+ ["welcome", "Добро пожаловать"],
+
["heavy pistols", "Тяжёлые пистолеты"],
["medium pistols", "Средние пистолеты"],
@@ -45,6 +51,7 @@ lang.LocalizedStrings = new Map<string, string>([
// Origins
["corebook", "Книга игрока"],
+
]);
export default lang;
diff --git a/front/src/index.tsx b/front/src/index.tsx
index 032464f..33c9e94 100644
--- a/front/src/index.tsx
+++ b/front/src/index.tsx
@@ -3,14 +3,18 @@ import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
+import { CookiesProvider } from 'react-cookie';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
+
root.render(
- <React.StrictMode>
- <App />
- </React.StrictMode>
+ <CookiesProvider>
+ <React.StrictMode>
+ <App />
+ </React.StrictMode>
+ </CookiesProvider>
);
// If you want to start measuring performance in your app, pass a function