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" />
|
<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>
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
||||||
|
|
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
|
// 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>
|
||||||
|
|
||||||
|
<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'>
|
<a className={styles.rightItem} href='/settings'>
|
||||||
<img src={userImg} width={'170%'}></img>
|
<img src={Settings} width={'170%'} className={styles.settings}></img>
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
</nav>
|
</nav>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
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" />
|
<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"}>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
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 { 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);
|
||||||
// Save theme preference (e.g., to local storage)
|
// Save theme preference (e.g., to local storage)
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNotificationsChange = (event) => {
|
const handleNotificationsChange = (event) => {
|
||||||
setNotificationsEnabled(event.target.checked);
|
setNotificationsEnabled(event.target.checked);
|
||||||
// Save notification preference
|
// Save notification preference
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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>
|
||||||
|
) :
|
||||||
|
''
|
||||||
|
}
|
||||||
|
|
||||||
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>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className='container'>
|
||||||
<label>
|
<div>
|
||||||
Enable Notifications:
|
<label htmlFor="theme">Theme:</label>
|
||||||
<input
|
<select id="theme" value={theme} onChange={handleThemeChange}>
|
||||||
type="checkbox"
|
<option value="light">Light</option>
|
||||||
checked={notificationsEnabled}
|
<option value="dark">Dark</option>
|
||||||
onChange={handleNotificationsChange}
|
</select>
|
||||||
/>
|
</div>
|
||||||
</label>
|
<div>
|
||||||
|
<label>
|
||||||
|
Enable Notifications:
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={notificationsEnabled}
|
||||||
|
onChange={handleNotificationsChange}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
</>
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SettingsPage;
|
export default SettingsPage;
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
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