Functionnal login system, tabs for settings, fixed nested anchors warnings and a lot of other things
This commit is contained in:
parent
e558bce789
commit
42a66ddda1
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>WF radio</title>
|
||||
<title>WOLFforce radio</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"preact": "^10.26.5",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"preact": "^10.26.6",
|
||||
"preact-router": "^4.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -8,8 +8,14 @@ importers:
|
|||
|
||||
.:
|
||||
dependencies:
|
||||
js-cookie:
|
||||
specifier: ^3.0.5
|
||||
version: 3.0.5
|
||||
jwt-decode:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
preact:
|
||||
specifier: ^10.26.5
|
||||
specifier: ^10.26.6
|
||||
version: 10.26.6
|
||||
preact-router:
|
||||
specifier: ^4.1.2
|
||||
|
@ -428,8 +434,8 @@ packages:
|
|||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
|
||||
caniuse-lite@1.0.30001717:
|
||||
resolution: {integrity: sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw==}
|
||||
caniuse-lite@1.0.30001718:
|
||||
resolution: {integrity: sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==}
|
||||
|
||||
convert-source-map@2.0.0:
|
||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||
|
@ -441,8 +447,8 @@ packages:
|
|||
resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
debug@4.4.0:
|
||||
resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
|
||||
debug@4.4.1:
|
||||
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
|
@ -463,8 +469,8 @@ packages:
|
|||
domutils@3.2.2:
|
||||
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
|
||||
|
||||
electron-to-chromium@1.5.151:
|
||||
resolution: {integrity: sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA==}
|
||||
electron-to-chromium@1.5.155:
|
||||
resolution: {integrity: sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==}
|
||||
|
||||
entities@4.5.0:
|
||||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||
|
@ -507,6 +513,10 @@ packages:
|
|||
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
|
||||
hasBin: true
|
||||
|
||||
js-cookie@3.0.5:
|
||||
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
|
@ -520,6 +530,10 @@ packages:
|
|||
engines: {node: '>=6'}
|
||||
hasBin: true
|
||||
|
||||
jwt-decode@4.0.0:
|
||||
resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
kolorist@1.8.0:
|
||||
resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
|
||||
|
||||
|
@ -679,7 +693,7 @@ snapshots:
|
|||
'@babel/traverse': 7.27.1
|
||||
'@babel/types': 7.27.1
|
||||
convert-source-map: 2.0.0
|
||||
debug: 4.4.0
|
||||
debug: 4.4.1
|
||||
gensync: 1.0.0-beta.2
|
||||
json5: 2.2.3
|
||||
semver: 6.3.1
|
||||
|
@ -775,7 +789,7 @@ snapshots:
|
|||
'@babel/parser': 7.27.2
|
||||
'@babel/template': 7.27.2
|
||||
'@babel/types': 7.27.1
|
||||
debug: 4.4.0
|
||||
debug: 4.4.1
|
||||
globals: 11.12.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
@ -885,7 +899,7 @@ snapshots:
|
|||
'@prefresh/vite': 2.4.7(preact@10.26.6)(vite@6.3.5)
|
||||
'@rollup/pluginutils': 4.2.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
|
||||
vite: 6.3.5
|
||||
vite-prerender-plugin: 0.5.10(vite@6.3.5)
|
||||
|
@ -988,12 +1002,12 @@ snapshots:
|
|||
|
||||
browserslist@4.24.5:
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001717
|
||||
electron-to-chromium: 1.5.151
|
||||
caniuse-lite: 1.0.30001718
|
||||
electron-to-chromium: 1.5.155
|
||||
node-releases: 2.0.19
|
||||
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: {}
|
||||
|
||||
|
@ -1007,7 +1021,7 @@ snapshots:
|
|||
|
||||
css-what@6.1.0: {}
|
||||
|
||||
debug@4.4.0:
|
||||
debug@4.4.1:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
|
@ -1029,7 +1043,7 @@ snapshots:
|
|||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
|
||||
electron-to-chromium@1.5.151: {}
|
||||
electron-to-chromium@1.5.155: {}
|
||||
|
||||
entities@4.5.0: {}
|
||||
|
||||
|
@ -1078,12 +1092,16 @@ snapshots:
|
|||
|
||||
he@1.2.0: {}
|
||||
|
||||
js-cookie@3.0.5: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
jsesc@3.1.0: {}
|
||||
|
||||
json5@2.2.3: {}
|
||||
|
||||
jwt-decode@4.0.0: {}
|
||||
|
||||
kolorist@1.8.0: {}
|
||||
|
||||
lru-cache@5.1.1:
|
||||
|
|
|
@ -6,13 +6,19 @@ import { Router } from 'preact-router';
|
|||
|
||||
// Pages
|
||||
import HomePage from './pages/home';
|
||||
import ModsPage from './pages/mods';
|
||||
import ModpacksPage from './pages/modpacks';
|
||||
import AboutPage from './pages/about';
|
||||
import SettingsPage from './pages/settings';
|
||||
|
||||
import ModsPage from './pages/mods';
|
||||
import ModPage from './pages/mod_page'
|
||||
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
|
||||
import NavBar from './components/NavBar/navbar'
|
||||
import Button from './components/Buttons/button'
|
||||
|
@ -39,6 +45,9 @@ export function App() {
|
|||
|
||||
<ModPage path="/mods/:name"></ModPage>
|
||||
<ModCreationPage path="/create/mod" ></ModCreationPage>
|
||||
|
||||
<LoginPage path='/login'></LoginPage>
|
||||
<RegisterPage path='/register'></RegisterPage>
|
||||
</Router>
|
||||
|
||||
|
||||
|
|
1
frontend/src/assets/login.svg
Normal file
1
frontend/src/assets/login.svg
Normal 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 |
1
frontend/src/assets/login_small.svg
Normal file
1
frontend/src/assets/login_small.svg
Normal 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 |
|
@ -1,16 +1,36 @@
|
|||
// Functions
|
||||
import { h } from 'preact'
|
||||
import { useEffect, useState } from 'preact/hooks';
|
||||
import { Link } from 'preact-router/match';
|
||||
import Cookies from 'js-cookie'
|
||||
import { jwtDecode } from 'jwt-decode'
|
||||
|
||||
// Styles
|
||||
import styles from './navbar.module.css'
|
||||
|
||||
// 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}) {
|
||||
|
||||
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 (
|
||||
<nav
|
||||
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='/modpacks'> Modpacks </a>
|
||||
<a className={styles.leftItem} href='/about'> About </a>
|
||||
|
||||
<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 className={styles.rightItem} href='/login'>
|
||||
<img src={Login} width={'170%'} className={styles.login}></img>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
<a className={styles.rightItem} href='/settings'>
|
||||
<img src={userImg} width={'170%'}></img>
|
||||
</a>
|
||||
<img src={Settings} width={'170%'} className={styles.settings}></img>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{children}
|
||||
</nav>
|
||||
)
|
||||
|
|
|
@ -42,21 +42,48 @@
|
|||
|
||||
/* WIP */
|
||||
.rightItem {
|
||||
margin-left: auto;
|
||||
margin-right: 2em;
|
||||
margin-top: .3em;
|
||||
margin: 0 1em;
|
||||
|
||||
user-select: none;
|
||||
|
||||
transition: 200ms;
|
||||
}
|
||||
|
||||
.rightItem > img {
|
||||
.rightItems {
|
||||
margin-left: auto;
|
||||
margin-right: 1em;
|
||||
margin-top: .3em;
|
||||
|
||||
display: flex;
|
||||
|
||||
}
|
||||
|
||||
.settings {
|
||||
border-radius: 100rem;
|
||||
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;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.profile:hover {
|
||||
background-color: #eaeaea10;
|
||||
}
|
|
@ -18,7 +18,7 @@ function AboutPage() {
|
|||
<>
|
||||
<a href='https://radio.oblic-parallels.fr' target="_blank">
|
||||
<img src={logo} class="logo img" alt="WF" />
|
||||
<a class="logo text"> radio </a>
|
||||
<p class="logo text"> radio </p>
|
||||
</a>
|
||||
<div class='title'>About us</div>
|
||||
<div class='background'></div>
|
||||
|
|
|
@ -18,7 +18,7 @@ function HomePage() {
|
|||
<>
|
||||
<a href='https://radio.oblic-parallels.fr' target="_blank">
|
||||
<img src={logo} class="logo img" alt="WF" />
|
||||
<a class="logo text"> radio </a>
|
||||
<p class="logo text"> radio </p>
|
||||
</a>
|
||||
<div class='title'>An open place for mods</div>
|
||||
<Button className='start-button' href='/mods'>Get started</Button>
|
||||
|
|
144
frontend/src/pages/login.jsx
Normal file
144
frontend/src/pages/login.jsx
Normal 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;
|
|
@ -89,7 +89,7 @@ function ModPage({name}) {
|
|||
<circle cx="11" cy="11" r="10" stroke="#3a3a3a" stroke-width="1" fill="#1a1a1a" />
|
||||
</svg>
|
||||
<div className={styles.version}>
|
||||
v3.0
|
||||
v3.0 (latest)
|
||||
</div>
|
||||
</a>
|
||||
<a href={"/notfound"}>
|
||||
|
|
|
@ -18,7 +18,7 @@ function ModpacksPage() {
|
|||
<>
|
||||
<a href='https://radio.oblic-parallels.fr' target="_blank">
|
||||
<img src={logo} class="logoSmall img" alt="WF" />
|
||||
<a class="logoSmall text"> modpacks </a>
|
||||
<p class="logoSmall text"> modpacks </p>
|
||||
</a>
|
||||
<div class='title'>Coming soon™</div>
|
||||
<div class='background'></div>
|
||||
|
|
93
frontend/src/pages/register.jsx
Normal file
93
frontend/src/pages/register.jsx
Normal 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;
|
|
@ -1,45 +1,89 @@
|
|||
// 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() {
|
||||
|
||||
const [theme, setTheme] = useState('light');
|
||||
const [theme, setTheme] = useState('dark');
|
||||
const [notificationsEnabled, setNotificationsEnabled] = useState(true);
|
||||
const [user, setUser] = useState(null)
|
||||
|
||||
|
||||
useEffect( () => {
|
||||
const token = Cookies.get('authToken');
|
||||
if (token) {
|
||||
setUser(token);
|
||||
}
|
||||
},
|
||||
[])
|
||||
|
||||
const handleThemeChange = (event) => {
|
||||
setTheme(event.target.value);
|
||||
// Save theme preference (e.g., to local storage)
|
||||
};
|
||||
|
||||
const handleNotificationsChange = (event) => {
|
||||
setNotificationsEnabled(event.target.checked);
|
||||
// Save notification preference
|
||||
};
|
||||
const handleNotificationsChange = (event) => {
|
||||
setNotificationsEnabled(event.target.checked);
|
||||
// Save notification preference
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Settings</h1>
|
||||
<div className='container'>
|
||||
<div>
|
||||
<label htmlFor="theme">Theme:</label>
|
||||
<select id="theme" value={theme} onChange={handleThemeChange}>
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
</select>
|
||||
const logout = () => {
|
||||
Cookies.remove('authToken');
|
||||
location.replace('/');
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<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>
|
||||
<label>
|
||||
Enable Notifications:
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={notificationsEnabled}
|
||||
onChange={handleNotificationsChange}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
<div className='container'>
|
||||
<div>
|
||||
<label htmlFor="theme">Theme:</label>
|
||||
<select id="theme" value={theme} onChange={handleThemeChange}>
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
Enable Notifications:
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={notificationsEnabled}
|
||||
onChange={handleNotificationsChange}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default SettingsPage;
|
|
@ -32,6 +32,9 @@ export async function login(username, password) {
|
|||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ username: username, password: password })
|
||||
});
|
||||
if (!response.ok) {
|
||||
|
@ -40,8 +43,8 @@ export async function login(username, password) {
|
|||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch items:', error);
|
||||
return null;
|
||||
// console.error('Failed to fetch items:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -82,7 +82,7 @@
|
|||
background: url("https://resources.oblic-parallels.fr/example.jpg");
|
||||
background-size: cover;
|
||||
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;
|
||||
}
|
||||
|
@ -164,7 +164,8 @@
|
|||
margin-bottom: 2em;
|
||||
margin-left: 1em;
|
||||
|
||||
padding: .2em;
|
||||
padding: .2em .4em;
|
||||
width: fit-content;
|
||||
|
||||
color: #8a8a8a;
|
||||
font-size: 1.3em;
|
||||
|
|
41
frontend/src/styles/login.module.css
Normal file
41
frontend/src/styles/login.module.css
Normal 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;
|
||||
}
|
46
frontend/src/styles/settings.module.css
Normal file
46
frontend/src/styles/settings.module.css
Normal 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;
|
||||
}
|
Loading…
Reference in a new issue