diff options
| author | physcik <mynameisgennadiy@vk.com> | 2026-04-28 23:33:59 +0500 |
|---|---|---|
| committer | physcik <mynameisgennadiy@vk.com> | 2026-04-28 23:33:59 +0500 |
| commit | 533c04d9558bd0a575671c157ca42236f59a2c5a (patch) | |
| tree | 895ec8672f9633fd6cfef85f6a12921fc5339ab3 /front/src | |
| parent | ec5ef35ed47b90b9f09199d28f7885f8287815a8 (diff) | |
frontend auth
Diffstat (limited to 'front/src')
| -rw-r--r-- | front/src/App.css | 1 | ||||
| -rw-r--r-- | front/src/App.tsx | 42 | ||||
| -rw-r--r-- | front/src/Authentication/ContextProvider.ts | 38 | ||||
| -rw-r--r-- | front/src/Authentication/LoginPage.tsx | 60 | ||||
| -rw-r--r-- | front/src/Authentication/Models.ts | 4 | ||||
| -rw-r--r-- | front/src/Emelents/Elements.css | 7 | ||||
| -rw-r--r-- | front/src/Emelents/Sidebar.tsx | 1 | ||||
| -rw-r--r-- | front/src/Emelents/Topbar.tsx | 12 | ||||
| -rw-r--r-- | front/src/Locales/ru_RU.ts | 7 | ||||
| -rw-r--r-- | front/src/index.tsx | 10 |
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 |
