Functionnal login system, tabs for settings, fixed nested anchors warnings and a lot of other things

This commit is contained in:
Gu://em_ 2025-05-16 01:46:51 +02:00
parent e558bce789
commit 42a66ddda1
19 changed files with 532 additions and 66 deletions

View file

@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WF radio</title> <title>WOLFforce radio</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View file

@ -9,7 +9,9 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"preact": "^10.26.5", "js-cookie": "^3.0.5",
"jwt-decode": "^4.0.0",
"preact": "^10.26.6",
"preact-router": "^4.1.2" "preact-router": "^4.1.2"
}, },
"devDependencies": { "devDependencies": {

View file

@ -8,8 +8,14 @@ importers:
.: .:
dependencies: dependencies:
js-cookie:
specifier: ^3.0.5
version: 3.0.5
jwt-decode:
specifier: ^4.0.0
version: 4.0.0
preact: preact:
specifier: ^10.26.5 specifier: ^10.26.6
version: 10.26.6 version: 10.26.6
preact-router: preact-router:
specifier: ^4.1.2 specifier: ^4.1.2
@ -428,8 +434,8 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true hasBin: true
caniuse-lite@1.0.30001717: caniuse-lite@1.0.30001718:
resolution: {integrity: sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw==} resolution: {integrity: sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==}
convert-source-map@2.0.0: convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
@ -441,8 +447,8 @@ packages:
resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
debug@4.4.0: debug@4.4.1:
resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
engines: {node: '>=6.0'} engines: {node: '>=6.0'}
peerDependencies: peerDependencies:
supports-color: '*' supports-color: '*'
@ -463,8 +469,8 @@ packages:
domutils@3.2.2: domutils@3.2.2:
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
electron-to-chromium@1.5.151: electron-to-chromium@1.5.155:
resolution: {integrity: sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA==} resolution: {integrity: sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==}
entities@4.5.0: entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
@ -507,6 +513,10 @@ packages:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
hasBin: true hasBin: true
js-cookie@3.0.5:
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
engines: {node: '>=14'}
js-tokens@4.0.0: js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@ -520,6 +530,10 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
hasBin: true hasBin: true
jwt-decode@4.0.0:
resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==}
engines: {node: '>=18'}
kolorist@1.8.0: kolorist@1.8.0:
resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
@ -679,7 +693,7 @@ snapshots:
'@babel/traverse': 7.27.1 '@babel/traverse': 7.27.1
'@babel/types': 7.27.1 '@babel/types': 7.27.1
convert-source-map: 2.0.0 convert-source-map: 2.0.0
debug: 4.4.0 debug: 4.4.1
gensync: 1.0.0-beta.2 gensync: 1.0.0-beta.2
json5: 2.2.3 json5: 2.2.3
semver: 6.3.1 semver: 6.3.1
@ -775,7 +789,7 @@ snapshots:
'@babel/parser': 7.27.2 '@babel/parser': 7.27.2
'@babel/template': 7.27.2 '@babel/template': 7.27.2
'@babel/types': 7.27.1 '@babel/types': 7.27.1
debug: 4.4.0 debug: 4.4.1
globals: 11.12.0 globals: 11.12.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -885,7 +899,7 @@ snapshots:
'@prefresh/vite': 2.4.7(preact@10.26.6)(vite@6.3.5) '@prefresh/vite': 2.4.7(preact@10.26.6)(vite@6.3.5)
'@rollup/pluginutils': 4.2.1 '@rollup/pluginutils': 4.2.1
babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.27.1) babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.27.1)
debug: 4.4.0 debug: 4.4.1
kolorist: 1.8.0 kolorist: 1.8.0
vite: 6.3.5 vite: 6.3.5
vite-prerender-plugin: 0.5.10(vite@6.3.5) vite-prerender-plugin: 0.5.10(vite@6.3.5)
@ -988,12 +1002,12 @@ snapshots:
browserslist@4.24.5: browserslist@4.24.5:
dependencies: dependencies:
caniuse-lite: 1.0.30001717 caniuse-lite: 1.0.30001718
electron-to-chromium: 1.5.151 electron-to-chromium: 1.5.155
node-releases: 2.0.19 node-releases: 2.0.19
update-browserslist-db: 1.1.3(browserslist@4.24.5) update-browserslist-db: 1.1.3(browserslist@4.24.5)
caniuse-lite@1.0.30001717: {} caniuse-lite@1.0.30001718: {}
convert-source-map@2.0.0: {} convert-source-map@2.0.0: {}
@ -1007,7 +1021,7 @@ snapshots:
css-what@6.1.0: {} css-what@6.1.0: {}
debug@4.4.0: debug@4.4.1:
dependencies: dependencies:
ms: 2.1.3 ms: 2.1.3
@ -1029,7 +1043,7 @@ snapshots:
domelementtype: 2.3.0 domelementtype: 2.3.0
domhandler: 5.0.3 domhandler: 5.0.3
electron-to-chromium@1.5.151: {} electron-to-chromium@1.5.155: {}
entities@4.5.0: {} entities@4.5.0: {}
@ -1078,12 +1092,16 @@ snapshots:
he@1.2.0: {} he@1.2.0: {}
js-cookie@3.0.5: {}
js-tokens@4.0.0: {} js-tokens@4.0.0: {}
jsesc@3.1.0: {} jsesc@3.1.0: {}
json5@2.2.3: {} json5@2.2.3: {}
jwt-decode@4.0.0: {}
kolorist@1.8.0: {} kolorist@1.8.0: {}
lru-cache@5.1.1: lru-cache@5.1.1:

View file

@ -6,13 +6,19 @@ import { Router } from 'preact-router';
// Pages // Pages
import HomePage from './pages/home'; import HomePage from './pages/home';
import ModsPage from './pages/mods';
import ModpacksPage from './pages/modpacks';
import AboutPage from './pages/about'; import AboutPage from './pages/about';
import SettingsPage from './pages/settings';
import ModsPage from './pages/mods';
import ModPage from './pages/mod_page' import ModPage from './pages/mod_page'
import ModCreationPage from './pages/mod_creation' import ModCreationPage from './pages/mod_creation'
import ModpacksPage from './pages/modpacks';
import LoginPage from './pages/login';
import RegisterPage from './pages/register';
import SettingsPage from './pages/settings';
// Components // Components
import NavBar from './components/NavBar/navbar' import NavBar from './components/NavBar/navbar'
import Button from './components/Buttons/button' import Button from './components/Buttons/button'
@ -39,6 +45,9 @@ export function App() {
<ModPage path="/mods/:name"></ModPage> <ModPage path="/mods/:name"></ModPage>
<ModCreationPage path="/create/mod" ></ModCreationPage> <ModCreationPage path="/create/mod" ></ModCreationPage>
<LoginPage path='/login'></LoginPage>
<RegisterPage path='/register'></RegisterPage>
</Router> </Router>

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#e3e3e3"><path d="M480.23-140v-45.39h282.08q4.61 0 8.46-3.84 3.84-3.85 3.84-8.46v-564.62q0-4.61-3.84-8.46-3.85-3.84-8.46-3.84H480.23V-820h282.08q23.53 0 40.61 17.08T820-762.31v564.62q0 23.53-17.08 40.61T762.31-140H480.23Zm-35.77-187.69-33-32.23 97.39-97.39H140v-45.38h367.62l-97.39-97.39 32.62-32.61 153.3 153.5-151.69 151.5Z"/></svg>

After

Width:  |  Height:  |  Size: 432 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M479.62-140v-60h268.07q4.62 0 8.46-3.85 3.85-3.84 3.85-8.46v-535.38q0-4.62-3.85-8.46-3.84-3.85-8.46-3.85H479.62v-60h268.07Q778-820 799-799q21 21 21 51.31v535.38Q820-182 799-161q-21 21-51.31 21H479.62Zm-54.23-169.23-41.54-43.39L481.23-450H140v-60h341.23l-97.38-97.38 41.54-43.39L596.15-480 425.39-309.23Z"/></svg>

After

Width:  |  Height:  |  Size: 428 B

View file

@ -1,16 +1,36 @@
// Functions // Functions
import { h } from 'preact' import { h } from 'preact'
import { useEffect, useState } from 'preact/hooks';
import { Link } from 'preact-router/match'; import { Link } from 'preact-router/match';
import Cookies from 'js-cookie'
import { jwtDecode } from 'jwt-decode'
// Styles // Styles
import styles from './navbar.module.css' import styles from './navbar.module.css'
// Images // Images
import userImg from '../../assets/settings.svg' import Settings from '../../assets/settings.svg'
import Login from '../../assets/login_small.svg'
import Account from '../../assets/account.svg'
function NavBar({ children, className, ...rest}) { function NavBar({ children, className, ...rest}) {
const [user, setUser] = useState(null);
useEffect(() => {
const token = Cookies.get('authToken');
if (token) {
const decoded_token = jwtDecode(token);
if (decoded_token) {
setUser({ username: decoded_token.username});
}
else {
console.warn('Cannot decode authToken');
}
}
}, []);
return ( return (
<nav <nav
className={styles.navbar} className={styles.navbar}
@ -20,9 +40,25 @@ function NavBar({ children, className, ...rest}) {
<a className={styles.leftItem} href='/mods'> Mods </a> <a className={styles.leftItem} href='/mods'> Mods </a>
<a className={styles.leftItem} href='/modpacks'> Modpacks </a> <a className={styles.leftItem} href='/modpacks'> Modpacks </a>
<a className={styles.leftItem} href='/about'> About </a> <a className={styles.leftItem} href='/about'> About </a>
<a className={styles.rightItem} href='/settings'>
<img src={userImg} width={'170%'}></img> <div className={styles.rightItems}>
{/* Display login button, or profile picture if connected */}
{user ? (
<a className={styles.rightItem} href={'/users/' + user.username}>
<img src={Account} width={'170%'} className={styles.profile}></img>
</a> </a>
) : (
<a className={styles.rightItem} href='/login'>
<img src={Login} width={'170%'} className={styles.login}></img>
</a>
)
}
<a className={styles.rightItem} href='/settings'>
<img src={Settings} width={'170%'} className={styles.settings}></img>
</a>
</div>
{children} {children}
</nav> </nav>
) )

View file

@ -42,21 +42,48 @@
/* WIP */ /* WIP */
.rightItem { .rightItem {
margin-left: auto; margin: 0 1em;
margin-right: 2em;
margin-top: .3em;
user-select: none; user-select: none;
transition: 200ms; transition: 200ms;
} }
.rightItem > img { .rightItems {
margin-left: auto;
margin-right: 1em;
margin-top: .3em;
display: flex;
}
.settings {
border-radius: 100rem; border-radius: 100rem;
transition: 600ms; transition: 600ms;
} }
.rightItem > img:hover { .profile {
border-radius: 100rem;
transition: 300ms;
}
.login {
border-radius: .5rem;
transition: 600ms;
margin-top: .06em;
height: 80%;
}
.login:hover {
background-color: #eaeaea10;
}
.settings:hover {
background-color: #eaeaea10; background-color: #eaeaea10;
transform: rotate(180deg); transform: rotate(180deg);
} }
.profile:hover {
background-color: #eaeaea10;
}

View file

@ -18,7 +18,7 @@ function AboutPage() {
<> <>
<a href='https://radio.oblic-parallels.fr' target="_blank"> <a href='https://radio.oblic-parallels.fr' target="_blank">
<img src={logo} class="logo img" alt="WF" /> <img src={logo} class="logo img" alt="WF" />
<a class="logo text"> radio </a> <p class="logo text"> radio </p>
</a> </a>
<div class='title'>About us</div> <div class='title'>About us</div>
<div class='background'></div> <div class='background'></div>

View file

@ -18,7 +18,7 @@ function HomePage() {
<> <>
<a href='https://radio.oblic-parallels.fr' target="_blank"> <a href='https://radio.oblic-parallels.fr' target="_blank">
<img src={logo} class="logo img" alt="WF" /> <img src={logo} class="logo img" alt="WF" />
<a class="logo text"> radio </a> <p class="logo text"> radio </p>
</a> </a>
<div class='title'>An open place for mods</div> <div class='title'>An open place for mods</div>
<Button className='start-button' href='/mods'>Get started</Button> <Button className='start-button' href='/mods'>Get started</Button>

View file

@ -0,0 +1,144 @@
// Preact
import { h } from 'preact';
import { useState } from 'preact/hooks';
import Cookies from 'js-cookie';
// Images
import logo from '../assets/logo.png'
// Styles
import styles from '../styles/login.module.css'
// Components
import Button from '../components/Buttons/button';
import InputField from '../components/Fields/input_field';
// Functions
import { login } from '../services/api';
function LoginPage() {
// useState
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [loginStatus, setLoginStatus] = useState(null); // To track success/error
const [fieldErrors, setFieldErrors] = useState({
username: null,
password: null
});
const handleUsernameChange = (event) => {
setUsername(event.target.value);
// Reset error
setFieldErrors(prevErrors => ({ ...prevErrors, username: null }));
};
const handlePasswordChange = (event) => {
setPassword(event.target.value);
// Reset error
setFieldErrors(prevErrors => ({ ...prevErrors, password: null }));
};
const handleSubmit = async (event) => {
event.preventDefault(); // Prevent the default form submission
setFieldErrors({ name: null, email: null, message: null });
setLoginStatus('logging in');
try {
const response = await login(username, password);
console.debug('Logged in successfully:', response);
setLoginStatus('success');
if (response && response.token) {
Cookies.set('authToken', response.token, {
expires: 30,
path: '/',
secure: true, // only send over https
sameSite: 'strict'
})
window.location.replace("/mods");
} else {
console.warn("Couldn't retrieve token from api response");
}
} catch (error) {
console.error('Login failed:', error);
setLoginStatus('error');
//TODO handle different codes differently
setFieldErrors({
username: ' ',
password: 'Wrong username or password.',
});
}
finally {
setLoginStatus(null);
}
};
return (
<>
<a href='/'>
<img src={logo} class="logo img" alt="WF" />
<p class="logo text"> radio </p>
</a>
<div className={styles.container}>
<img src={logo} className={styles.loginImage}></img>
<form onSubmit={handleSubmit}>
<div className={styles.fieldsContainer}>
{/* username */}
<InputField
id="username"
name="username"
value={username}
onChange={handleUsernameChange}
error={fieldErrors.username}
placeholder="username"
required
>
</InputField>
{/* password */}
<InputField
id="password"
name="password"
value={password}
type="password"
onChange={handlePasswordChange}
error={fieldErrors.password}
placeholder="password"
required
>
</InputField>
</div>
<div className={styles.buttonsContainer}>
<Button
className={styles.loginButton}
type='submit'
disabled={loginStatus === 'logging in'}
>
{loginStatus === 'logging in' ? 'Logging in' : 'Login'}
</Button>
<Button
className={styles.loginButton}
variant={'secondary'}
href='/register'
>
Create an account
</Button>
</div>
</form>
</div>
</>
);
}
export default LoginPage;

View file

@ -89,7 +89,7 @@ function ModPage({name}) {
<circle cx="11" cy="11" r="10" stroke="#3a3a3a" stroke-width="1" fill="#1a1a1a" /> <circle cx="11" cy="11" r="10" stroke="#3a3a3a" stroke-width="1" fill="#1a1a1a" />
</svg> </svg>
<div className={styles.version}> <div className={styles.version}>
v3.0 v3.0 (latest)
</div> </div>
</a> </a>
<a href={"/notfound"}> <a href={"/notfound"}>

View file

@ -18,7 +18,7 @@ function ModpacksPage() {
<> <>
<a href='https://radio.oblic-parallels.fr' target="_blank"> <a href='https://radio.oblic-parallels.fr' target="_blank">
<img src={logo} class="logoSmall img" alt="WF" /> <img src={logo} class="logoSmall img" alt="WF" />
<a class="logoSmall text"> modpacks </a> <p class="logoSmall text"> modpacks </p>
</a> </a>
<div class='title'>Coming soon</div> <div class='title'>Coming soon</div>
<div class='background'></div> <div class='background'></div>

View file

@ -0,0 +1,93 @@
// Functions
import { h } from 'preact';
// Images
import logo from '../assets/logo.png'
// Styles
import styles from '../styles/login.module.css'
// Components
import Button from '../components/Buttons/button';
import InputField from '../components/Fields/input_field';
function RegisterPage() {
const username_input= "";
const display_name_input= "";
const email_input= "";
const password_input= "";
return (
<>
<a href='https://radio.oblic-parallels.fr' target="_blank">
<img src={logo} class="logo img" alt="WF" />
<a class="logo text"> radio </a>
</a>
<div className={styles.container}>
<img src={logo} className={styles.loginImage}></img>
<form>
<div className={styles.fieldsContainer}>
{/* username */}
<InputField
id="username"
name="username"
value={username_input}
// onChange={handleNameChange}
// error={nameError}
placeholder="username"
required
>
</InputField>
{/* display_name */}
<InputField
id="display_name"
name="display_name"
value={display_name_input}
// onChange={handleNameChange}
// error={nameError}
placeholder="display_name"
required
>
</InputField>
{/* email */}
<InputField
id="email"
name="email"
value={email_input}
// onChange={handleNameChange}
// error={nameError}
placeholder="email"
required
>
</InputField>
{/* password */}
<InputField
id="password"
name="password"
value={password_input}
type="password"
// onChange={handleNameChange}
// error={nameError}
placeholder="password"
required
>
</InputField>
</div>
<div className={styles.buttonsContainer}>
<Button className={styles.loginButton}>
Register
</Button>
</div>
</form>
</div>
</>
);
}
export default RegisterPage;

View file

@ -1,10 +1,30 @@
// Preact
import { h } from 'preact'; import { h } from 'preact';
import { useState } from 'preact/hooks'; // If you need state for settings import { useState, useEffect } from 'preact/hooks'; // If you need state for settings
import Cookies from 'js-cookie'
import { jwtDecode } from 'jwt-decode'
// Styles
import styles from '../styles/settings.module.css'
// Images
import Logo from '../assets/logo.png'
function SettingsPage() { function SettingsPage() {
const [theme, setTheme] = useState('light'); const [theme, setTheme] = useState('dark');
const [notificationsEnabled, setNotificationsEnabled] = useState(true); const [notificationsEnabled, setNotificationsEnabled] = useState(true);
const [user, setUser] = useState(null)
useEffect( () => {
const token = Cookies.get('authToken');
if (token) {
setUser(token);
}
},
[])
const handleThemeChange = (event) => { const handleThemeChange = (event) => {
setTheme(event.target.value); setTheme(event.target.value);
@ -16,9 +36,33 @@ function SettingsPage() {
// Save notification preference // Save notification preference
}; };
const logout = () => {
Cookies.remove('authToken');
location.replace('/');
}
return ( return (
<> <>
<h1>Settings</h1> <a href='/'>
<img src={Logo} class="logoSmall img" alt="WF" />
<p class="logoSmall text"> settings </p>
</a>
<div className={styles.tabsContainer}>
<a href='/settings'>
<p className={styles.tab}>Global</p>
</a>
<a href='/notfound'>
<p className={styles.tab}>User</p>
</a>
{user ? (
<a onClick={() => {logout()}}>
<p className={`${styles.tab} ${styles.logout}`}>Logout</p>
</a>
) :
''
}
</div>
<div className='container'> <div className='container'>
<div> <div>
<label htmlFor="theme">Theme:</label> <label htmlFor="theme">Theme:</label>

View file

@ -32,6 +32,9 @@ export async function login(username, password) {
try { try {
const response = await fetch(`${API_BASE_URL}/login`, { const response = await fetch(`${API_BASE_URL}/login`, {
method: 'POST', method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username: username, password: password }) body: JSON.stringify({ username: username, password: password })
}); });
if (!response.ok) { if (!response.ok) {
@ -40,8 +43,8 @@ export async function login(username, password) {
const data = await response.json(); const data = await response.json();
return data; return data;
} catch (error) { } catch (error) {
console.error('Failed to fetch items:', error); // console.error('Failed to fetch items:', error);
return null; throw error;
} }
} }

View file

@ -82,7 +82,7 @@
background: url("https://resources.oblic-parallels.fr/example.jpg"); background: url("https://resources.oblic-parallels.fr/example.jpg");
background-size: cover; background-size: cover;
border-top-right-radius: .4rem; border-top-right-radius: .4rem;
-webkit-mask-image: radial-gradient(ellipse at top right, black 0%, #00000010 50%, transparent 70%); mask-image: radial-gradient(ellipse at top right, black 0%, #00000010 50%, transparent 70%);
z-index: 0; z-index: 0;
} }
@ -164,7 +164,8 @@
margin-bottom: 2em; margin-bottom: 2em;
margin-left: 1em; margin-left: 1em;
padding: .2em; padding: .2em .4em;
width: fit-content;
color: #8a8a8a; color: #8a8a8a;
font-size: 1.3em; font-size: 1.3em;

View file

@ -0,0 +1,41 @@
.container {
position: absolute;
left: 35vw;
right: 35vw;
top: 11rem;
bottom: 4rem;
min-width: 20em;
min-height: 25em;
padding: 2em;
background-color: #1a1a1a;
border: #3a3a3a solid .1rem;
border-radius: .5rem;
overflow: scroll;
}
.loginImage {
display: block;
margin: 1em auto;
width: 10rem;
/* padding: 5em; */
}
.buttonsContainer {
padding: 0 15% 5vh 15%;
}
.fieldsContainer {
/* margin-top: 5vh; */
padding: 5vh 10%;
}
.loginButton {
width: 100%;
padding: .5em 0;
font-size: 1.2em;
margin-bottom: 1em;
}

View file

@ -0,0 +1,46 @@
.tabsContainer {
position: absolute;
left: 4rem;
top: 11rem;
bottom: 4rem;
/* right: 97rem; */
width: 15rem;
border-radius: .5rem;
/* background-color: #1a1a1a; */
}
.tab {
position: relative;
left: 4rem;
right: 0;
/* transform: translate(-50% , -50%); */
margin: 1em 0;
padding: .2em .4em;
width: fit-content;
color: #8a8a8a;
font-size: 1.5em;
border-radius: .5em;
transition: 200ms;
}
.tab:hover {
background-color: #3a3a3a;
}
.logout {
color: #c24040;
cursor: pointer;
}
.logout:hover {
color: #eaeaea;
background-color: #bc3939;
}