Functionnal register system, 404 page, splitted services into multiple files and various fixes
This commit is contained in:
parent
42a66ddda1
commit
93bf736ca9
10
frontend/config/config.json
Normal file
10
frontend/config/config.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"port": 8100,
|
||||||
|
|
||||||
|
"backend": {
|
||||||
|
"address": "localhost",
|
||||||
|
"port": 8000,
|
||||||
|
"protocol": "http",
|
||||||
|
"path": "/"
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,8 @@ import { Router } from 'preact-router';
|
||||||
// Pages
|
// Pages
|
||||||
import HomePage from './pages/home';
|
import HomePage from './pages/home';
|
||||||
import AboutPage from './pages/about';
|
import AboutPage from './pages/about';
|
||||||
|
import NotFoundPage from './pages/not_found';
|
||||||
|
import ComingSoonPage from './pages/coming_soon';
|
||||||
|
|
||||||
import ModsPage from './pages/mods';
|
import ModsPage from './pages/mods';
|
||||||
import ModPage from './pages/mod_page'
|
import ModPage from './pages/mod_page'
|
||||||
|
@ -48,6 +50,9 @@ export function App() {
|
||||||
|
|
||||||
<LoginPage path='/login'></LoginPage>
|
<LoginPage path='/login'></LoginPage>
|
||||||
<RegisterPage path='/register'></RegisterPage>
|
<RegisterPage path='/register'></RegisterPage>
|
||||||
|
|
||||||
|
<ComingSoonPage path='/soon'></ComingSoonPage>
|
||||||
|
<NotFoundPage path='/notfound' default ></NotFoundPage>
|
||||||
</Router>
|
</Router>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { h } from 'preact';
|
||||||
|
import { useState } from 'preact/hooks';
|
||||||
|
import { searchMods } from '../services/api'; // Your API fetching function
|
||||||
|
import styles from './SearchBar.module.css'; // Optional: CSS Modules
|
||||||
|
|
||||||
|
function SearchBar({ onResults }) {
|
||||||
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
const handleInputChange = (event) => {
|
||||||
|
setSearchTerm(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = async () => {
|
||||||
|
if (!searchTerm.trim()) {
|
||||||
|
onResults([]); // Clear results if search term is empty
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const results = await searchItems(searchTerm);
|
||||||
|
onResults(results); // Pass the fetched results to the parent component
|
||||||
|
} catch (err) {
|
||||||
|
setError('Failed to fetch search results.');
|
||||||
|
onResults([]); // Clear results on error
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.searchBarContainer}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search items..."
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
className={styles.searchInput}
|
||||||
|
/>
|
||||||
|
<button onClick={handleSearch} className={styles.searchButton} disabled={loading}>
|
||||||
|
{loading ? 'Searching...' : 'Search'}
|
||||||
|
</button>
|
||||||
|
{error && <div className={styles.errorMessage}>{error}</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SearchBar;
|
|
@ -39,6 +39,9 @@
|
||||||
.leftItem:hover {
|
.leftItem:hover {
|
||||||
background-color: #eaeaea10;
|
background-color: #eaeaea10;
|
||||||
}
|
}
|
||||||
|
.leftItem:active {
|
||||||
|
background-color: #eaeaea20;
|
||||||
|
}
|
||||||
|
|
||||||
/* WIP */
|
/* WIP */
|
||||||
.rightItem {
|
.rightItem {
|
||||||
|
|
|
@ -16,7 +16,7 @@ function AboutPage() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<a href='https://radio.oblic-parallels.fr' target="_blank">
|
<a href='/' target="_blank">
|
||||||
<img src={logo} class="logo img" alt="WF" />
|
<img src={logo} class="logo img" alt="WF" />
|
||||||
<p class="logo text"> radio </p>
|
<p class="logo text"> radio </p>
|
||||||
</a>
|
</a>
|
||||||
|
|
29
frontend/src/pages/coming_soon.jsx
Normal file
29
frontend/src/pages/coming_soon.jsx
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
// Functions
|
||||||
|
import { h } from 'preact';
|
||||||
|
|
||||||
|
// Images
|
||||||
|
import logo from '../assets/logo.png'
|
||||||
|
|
||||||
|
// Styles
|
||||||
|
import '../styles/home.css'
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import Button from '../components/Buttons/button';
|
||||||
|
|
||||||
|
|
||||||
|
function ComingSoonPage() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<a href='/' target="_blank">
|
||||||
|
<img src={logo} class="logo img" alt="WF" />
|
||||||
|
<p class="logo text"> radio </p>
|
||||||
|
</a>
|
||||||
|
<div class='title'>Coming soon™</div>
|
||||||
|
<div class='background'></div>
|
||||||
|
<div class='halo'></div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ComingSoonPage;
|
1
frontend/src/pages/dashboard.jsx
Normal file
1
frontend/src/pages/dashboard.jsx
Normal file
|
@ -0,0 +1 @@
|
||||||
|
// TODO
|
|
@ -16,7 +16,7 @@ function HomePage() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<a href='https://radio.oblic-parallels.fr' target="_blank">
|
<a href='/' target="_blank">
|
||||||
<img src={logo} class="logo img" alt="WF" />
|
<img src={logo} class="logo img" alt="WF" />
|
||||||
<p class="logo text"> radio </p>
|
<p class="logo text"> radio </p>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import { h } from 'preact';
|
import { h } from 'preact';
|
||||||
import { useState } from 'preact/hooks';
|
import { useState } from 'preact/hooks';
|
||||||
import Cookies from 'js-cookie';
|
import Cookies from 'js-cookie';
|
||||||
|
import { jwtDecode } from 'jwt-decode'
|
||||||
|
|
||||||
// Images
|
// Images
|
||||||
import logo from '../assets/logo.png'
|
import logo from '../assets/logo.png'
|
||||||
|
@ -14,7 +15,7 @@ import Button from '../components/Buttons/button';
|
||||||
import InputField from '../components/Fields/input_field';
|
import InputField from '../components/Fields/input_field';
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
import { login } from '../services/api';
|
import { login } from '../services/auth';
|
||||||
|
|
||||||
|
|
||||||
function LoginPage() {
|
function LoginPage() {
|
||||||
|
@ -41,8 +42,8 @@ function LoginPage() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async (event) => {
|
const handleSubmit = async (event) => {
|
||||||
event.preventDefault(); // Prevent the default form submission
|
event.preventDefault();
|
||||||
setFieldErrors({ name: null, email: null, message: null });
|
setFieldErrors({ username: null, password: null});
|
||||||
|
|
||||||
setLoginStatus('logging in');
|
setLoginStatus('logging in');
|
||||||
|
|
||||||
|
@ -53,8 +54,12 @@ function LoginPage() {
|
||||||
setLoginStatus('success');
|
setLoginStatus('success');
|
||||||
|
|
||||||
if (response && response.token) {
|
if (response && response.token) {
|
||||||
|
const decoded_token = jwtDecode(response.token);
|
||||||
|
if (!decoded_token) {
|
||||||
|
throw new Error("Couldn't decode token");
|
||||||
|
}
|
||||||
Cookies.set('authToken', response.token, {
|
Cookies.set('authToken', response.token, {
|
||||||
expires: 30,
|
expires: decoded_token.exp, //TODO not sure if it's the right value
|
||||||
path: '/',
|
path: '/',
|
||||||
secure: true, // only send over https
|
secure: true, // only send over https
|
||||||
sameSite: 'strict'
|
sameSite: 'strict'
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { h } from 'preact';
|
||||||
import { useState, useEffect } from 'preact/hooks';
|
import { useState, useEffect } from 'preact/hooks';
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
import { createMod } from '../services/api';
|
import { createMod } from '../services/mods';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import InputField from '../components/Fields/input_field'
|
import InputField from '../components/Fields/input_field'
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { h } from 'preact';
|
||||||
import { useState, useEffect } from 'preact/hooks';
|
import { useState, useEffect } from 'preact/hooks';
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
import { fetchMod } from '../services/api';
|
import { getMod } from '../services/mods';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ function ModPage({name}) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(false);
|
setError(false);
|
||||||
try {
|
try {
|
||||||
const fetched_mod = await fetchMod(name);
|
const fetched_mod = await getMod(name);
|
||||||
setMod(fetched_mod);
|
setMod(fetched_mod);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err.message);
|
setError(err.message);
|
||||||
|
@ -39,23 +39,49 @@ function ModPage({name}) {
|
||||||
|
|
||||||
loadItems();
|
loadItems();
|
||||||
}, []); // <-- Tells useEffect to run once after render
|
}, []); // <-- Tells useEffect to run once after render
|
||||||
|
|
||||||
|
const base_page = (
|
||||||
|
<>
|
||||||
|
<a href="/">
|
||||||
|
<img src={logo} class="logo img" alt="WF" />
|
||||||
|
<p class="logo text"> mods </p>
|
||||||
|
</a>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
// TODO replace by loading screen
|
// TODO replace by loading screen
|
||||||
return <div>Loading mod</div>
|
return (
|
||||||
|
<>
|
||||||
|
{base_page}
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<p className={styles.title}>Loading...</p>
|
||||||
|
</div>
|
||||||
|
<div className={styles.infosPanel}> </div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (error) {
|
if (error) {
|
||||||
// TODO replace by popup
|
// TODO replace by popup
|
||||||
return <div>Couldn't load mod: {error}</div>
|
return (
|
||||||
|
<>
|
||||||
|
{base_page}
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<p className={styles.title}>Couldn't load this mod</p>
|
||||||
|
<p className={styles.fullDescription}>{error}</p>
|
||||||
|
</div>
|
||||||
|
<div className={styles.infosPanel}> </div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<a href="/">
|
{base_page}
|
||||||
<img src={logo} class="logo img" alt="WF" />
|
|
||||||
<p class="logo text"> mods </p>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<div className={styles.backgroundImage}></div>
|
<div className={styles.backgroundImage}></div>
|
||||||
|
|
|
@ -16,7 +16,7 @@ function ModpacksPage() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<a href='https://radio.oblic-parallels.fr' target="_blank">
|
<a href='/' target="_blank">
|
||||||
<img src={logo} class="logoSmall img" alt="WF" />
|
<img src={logo} class="logoSmall img" alt="WF" />
|
||||||
<p class="logoSmall text"> modpacks </p>
|
<p class="logoSmall text"> modpacks </p>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { h } from 'preact';
|
||||||
import { useState, useEffect } from 'preact/hooks';
|
import { useState, useEffect } from 'preact/hooks';
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
import { fetchMods } from '../services/api';
|
import { listMods } from '../services/mods';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import FiltersPanel from '../components/Filters/panel'
|
import FiltersPanel from '../components/Filters/panel'
|
||||||
|
@ -29,7 +29,7 @@ function ModsPage() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(false);
|
setError(false);
|
||||||
try {
|
try {
|
||||||
const fetched_mods = await fetchMods();
|
const fetched_mods = await listMods();
|
||||||
setMods(fetched_mods);
|
setMods(fetched_mods);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err.message);
|
setError(err.message);
|
||||||
|
@ -41,22 +41,49 @@ function ModsPage() {
|
||||||
loadItems();
|
loadItems();
|
||||||
}, []); // <-- Tells useEffect to run once after render
|
}, []); // <-- Tells useEffect to run once after render
|
||||||
|
|
||||||
|
// Base page
|
||||||
|
const base_page = (
|
||||||
|
<>
|
||||||
|
<a href="/" target="_blank">
|
||||||
|
<img src={logo} class="logo img" alt="WF" />
|
||||||
|
<p class="logo text"> mods </p>
|
||||||
|
</a>
|
||||||
|
<FiltersPanel></FiltersPanel>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
// TODO replace by loading screen
|
// TODO replace by loading screen
|
||||||
return <div>Loading mods</div>
|
return (
|
||||||
|
<>
|
||||||
|
{base_page}
|
||||||
|
<div class='content-container'>
|
||||||
|
<div class='loadingContent'>
|
||||||
|
Loading mods
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (error) {
|
if (error) {
|
||||||
// TODO replace by popup
|
// TODO replace by popup
|
||||||
return <div>Couldn't load mods: {error}</div>
|
return (
|
||||||
|
<>
|
||||||
|
{base_page}
|
||||||
|
<div class='content-container'>
|
||||||
|
<div class='loadingContent'>
|
||||||
|
Couldn't load mods: {error}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<a href="https://radio.oblic-parallels.fr" target="_blank">
|
{base_page}
|
||||||
<img src={logo} class="logo img" alt="WF" />
|
|
||||||
<p class="logo text"> mods </p>
|
|
||||||
</a>
|
|
||||||
<FiltersPanel></FiltersPanel>
|
|
||||||
<div class='content-container'>
|
<div class='content-container'>
|
||||||
{/* <GridCard>Test card</GridCard> */}
|
{/* <GridCard>Test card</GridCard> */}
|
||||||
{/* <GridCard/> */}
|
{/* <GridCard/> */}
|
||||||
|
|
30
frontend/src/pages/not_found.jsx
Normal file
30
frontend/src/pages/not_found.jsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// Functions
|
||||||
|
import { h } from 'preact';
|
||||||
|
|
||||||
|
// Images
|
||||||
|
import logo from '../assets/logo.png'
|
||||||
|
|
||||||
|
// Styles
|
||||||
|
import '../styles/home.css'
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import Button from '../components/Buttons/button';
|
||||||
|
|
||||||
|
|
||||||
|
function NotFoundPage() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<a href='/' target="_blank">
|
||||||
|
<img src={logo} class="logo img" alt="WF" />
|
||||||
|
<p class="logo text"> radio </p>
|
||||||
|
</a>
|
||||||
|
<div class='bigTitle'>404</div>
|
||||||
|
<div class='subtitle'>Page not found</div>
|
||||||
|
<div class='background'></div>
|
||||||
|
<div class='halo'></div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NotFoundPage;
|
|
@ -1,8 +1,12 @@
|
||||||
// Functions
|
// Preact
|
||||||
import { h } from 'preact';
|
import { h } from 'preact';
|
||||||
|
import { useState } from 'preact/hooks';
|
||||||
|
import Cookies from 'js-cookie';
|
||||||
|
import { jwtDecode } from 'jwt-decode'
|
||||||
|
|
||||||
// Images
|
// Images
|
||||||
import logo from '../assets/logo.png'
|
import Logo from '../assets/logo.png'
|
||||||
|
import Account from '../assets/account.svg'
|
||||||
|
|
||||||
// Styles
|
// Styles
|
||||||
import styles from '../styles/login.module.css'
|
import styles from '../styles/login.module.css'
|
||||||
|
@ -11,55 +15,128 @@ import styles from '../styles/login.module.css'
|
||||||
import Button from '../components/Buttons/button';
|
import Button from '../components/Buttons/button';
|
||||||
import InputField from '../components/Fields/input_field';
|
import InputField from '../components/Fields/input_field';
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
import { register } from '../services/auth';
|
||||||
|
|
||||||
|
|
||||||
function RegisterPage() {
|
function RegisterPage() {
|
||||||
|
|
||||||
const username_input= "";
|
// useState
|
||||||
const display_name_input= "";
|
const [username, setUsername] = useState('');
|
||||||
const email_input= "";
|
const [display_name, setDisplayName] = useState('');
|
||||||
const password_input= "";
|
const [email, setEmail] = useState('');
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const [registerStatus, setRegisterStatus] = useState(null); // To track success/error
|
||||||
|
const [fieldErrors, setFieldErrors] = useState({
|
||||||
|
username: null,
|
||||||
|
display_name: null,
|
||||||
|
email: 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 handleEmailChange = (event) => {
|
||||||
|
setEmail(event.target.value);
|
||||||
|
// Reset error
|
||||||
|
setFieldErrors(prevErrors => ({ ...prevErrors, email: null }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDisplayNameChange = (event) => {
|
||||||
|
setDisplayName(event.target.value);
|
||||||
|
// Reset error
|
||||||
|
setFieldErrors(prevErrors => ({ ...prevErrors, display_name: null }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
setFieldErrors({ username: null, password: null, email: null, display_name: null});
|
||||||
|
|
||||||
|
setRegisterStatus('registering');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await register({username: username,
|
||||||
|
display_name: display_name,
|
||||||
|
email: email,
|
||||||
|
password: password});
|
||||||
|
|
||||||
|
console.debug('Registered successfully:', response);
|
||||||
|
setRegisterStatus('success');
|
||||||
|
|
||||||
|
window.location.replace("/login");
|
||||||
|
//TODO success screen
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
console.error('Register failed:', error);
|
||||||
|
setRegisterStatus('error');
|
||||||
|
|
||||||
|
//TODO handle different codes differently
|
||||||
|
setFieldErrors({
|
||||||
|
username: ' ',
|
||||||
|
email: ' ',
|
||||||
|
display_name: ' ',
|
||||||
|
password: 'Error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
setRegisterStatus(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<a href='https://radio.oblic-parallels.fr' target="_blank">
|
<a href='/'>
|
||||||
<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 className={styles.container}>
|
<div className={styles.container}>
|
||||||
<img src={logo} className={styles.loginImage}></img>
|
<form onSubmit={handleSubmit}>
|
||||||
<form>
|
<img src={Account} className={styles.loginImage} style={'cursor: pointer'}></img>
|
||||||
|
{/* <input type="file" id="profile_picture" style="display: none;"></input> */}
|
||||||
|
|
||||||
<div className={styles.fieldsContainer}>
|
<div className={styles.fieldsContainer}>
|
||||||
{/* username */}
|
{/* username */}
|
||||||
<InputField
|
<InputField
|
||||||
id="username"
|
id="username"
|
||||||
name="username"
|
name="username"
|
||||||
value={username_input}
|
value={username}
|
||||||
// onChange={handleNameChange}
|
onChange={handleUsernameChange}
|
||||||
// error={nameError}
|
error={fieldErrors.username}
|
||||||
placeholder="username"
|
placeholder="username"
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
</InputField>
|
</InputField>
|
||||||
|
|
||||||
{/* display_name */}
|
{/* display_name */}
|
||||||
<InputField
|
<InputField
|
||||||
id="display_name"
|
id="display_name"
|
||||||
name="display_name"
|
name="display_name"
|
||||||
value={display_name_input}
|
value={display_name}
|
||||||
// onChange={handleNameChange}
|
onChange={handleDisplayNameChange}
|
||||||
// error={nameError}
|
error={fieldErrors.display_name}
|
||||||
placeholder="display_name"
|
placeholder="display_name"
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
</InputField>
|
</InputField>
|
||||||
|
|
||||||
{/* email */}
|
{/* email */}
|
||||||
<InputField
|
<InputField
|
||||||
id="email"
|
id="email"
|
||||||
name="email"
|
name="email"
|
||||||
value={email_input}
|
value={email}
|
||||||
// onChange={handleNameChange}
|
onChange={handleEmailChange}
|
||||||
// error={nameError}
|
error={fieldErrors.email}
|
||||||
placeholder="email"
|
placeholder="email"
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
|
@ -69,18 +146,22 @@ function RegisterPage() {
|
||||||
<InputField
|
<InputField
|
||||||
id="password"
|
id="password"
|
||||||
name="password"
|
name="password"
|
||||||
value={password_input}
|
value={password}
|
||||||
type="password"
|
type="password"
|
||||||
// onChange={handleNameChange}
|
onChange={handlePasswordChange}
|
||||||
// error={nameError}
|
error={fieldErrors.password}
|
||||||
placeholder="password"
|
placeholder="password"
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
</InputField>
|
</InputField>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.buttonsContainer}>
|
<div className={styles.buttonsContainer}>
|
||||||
<Button className={styles.loginButton}>
|
<Button
|
||||||
Register
|
className={styles.loginButton}
|
||||||
|
type='submit'
|
||||||
|
disabled={registerStatus === 'registering'}
|
||||||
|
>
|
||||||
|
{registerStatus === 'registering' ? 'Registering' : registerStatus === 'success' ? 'Success' : 'Register'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -44,20 +44,22 @@ function SettingsPage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<a href='/'>
|
<a href='/'>
|
||||||
<img src={Logo} class="logoSmall img" alt="WF" />
|
<img src={Logo} className={styles.logo} alt="WF" />
|
||||||
<p class="logoSmall text"> settings </p>
|
<p className={styles.logoTitle}> settings </p>
|
||||||
</a>
|
</a>
|
||||||
<div className={styles.tabsContainer}>
|
<div className={styles.tabsContainer}>
|
||||||
<a href='/settings'>
|
<a href='/settings'>
|
||||||
<p className={styles.tab}>Global</p>
|
<p className={styles.tab}>Global</p>
|
||||||
</a>
|
</a>
|
||||||
<a href='/notfound'>
|
|
||||||
<p className={styles.tab}>User</p>
|
|
||||||
</a>
|
|
||||||
{user ? (
|
{user ? (
|
||||||
<a onClick={() => {logout()}}>
|
<>
|
||||||
<p className={`${styles.tab} ${styles.logout}`}>Logout</p>
|
<a href='/notfound'>
|
||||||
</a>
|
<p className={styles.tab}>User</p>
|
||||||
|
</a>
|
||||||
|
<a onClick={() => {logout()}}>
|
||||||
|
<p className={`${styles.tab} ${styles.logout}`}>Logout</p>
|
||||||
|
</a>
|
||||||
|
</>
|
||||||
) :
|
) :
|
||||||
''
|
''
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
const API_BASE_URL = 'http://localhost:8000';
|
|
||||||
|
|
||||||
export async function fetchMods() {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${API_BASE_URL}/list/mods`);
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
const data = await response.json();
|
|
||||||
return data;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to fetch items:', error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchMod(mod_name) {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${API_BASE_URL}/mods/${mod_name}`);
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
const data = await response.json();
|
|
||||||
return data;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to fetch items:', error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
const data = await response.json();
|
|
||||||
return data;
|
|
||||||
} catch (error) {
|
|
||||||
// console.error('Failed to fetch items:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createMod(username, password) {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${API_BASE_URL}/login`, {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({ }) //TODO
|
|
||||||
});
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
const data = await response.json();
|
|
||||||
return data;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to fetch items:', error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
46
frontend/src/services/auth.js
Normal file
46
frontend/src/services/auth.js
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import Cookies from 'js-cookie';
|
||||||
|
|
||||||
|
const API_BASE_URL = 'http://localhost:8000'; //TODO use config manager instead
|
||||||
|
|
||||||
|
|
||||||
|
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) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
// console.error('Failed to fetch items:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function register(user_data) {
|
||||||
|
try {
|
||||||
|
console.debug(user_data);
|
||||||
|
console.debug(JSON.stringify(user_data));
|
||||||
|
const response = await fetch(`${API_BASE_URL}/users`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(user_data)
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
// console.error('Failed to fetch items:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
70
frontend/src/services/config_manager.js
Normal file
70
frontend/src/services/config_manager.js
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
// --- Define constants ---
|
||||||
|
|
||||||
|
// Imports
|
||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import { version } from "../../package.json";
|
||||||
|
|
||||||
|
|
||||||
|
// Var decalaration
|
||||||
|
const config_folder = "config";
|
||||||
|
const config_file_name = "config.json"
|
||||||
|
|
||||||
|
// Global variables
|
||||||
|
let config = {};
|
||||||
|
let backend_url = "";
|
||||||
|
|
||||||
|
// --- Default config ---
|
||||||
|
|
||||||
|
const default_config = {
|
||||||
|
|
||||||
|
"port": 8100,
|
||||||
|
|
||||||
|
"backend": {
|
||||||
|
"address": "localhost",
|
||||||
|
"port": 8000,
|
||||||
|
"protocol": "http",
|
||||||
|
"path": "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadConfig() {
|
||||||
|
|
||||||
|
let user_config;
|
||||||
|
|
||||||
|
// Parse
|
||||||
|
try {
|
||||||
|
// Get user config
|
||||||
|
user_config = JSON.parse(fs.readFileSync(path.resolve(path.join(config_folder, config_file_name))));
|
||||||
|
|
||||||
|
// Merge default and user configs (default values)
|
||||||
|
config = { ...default_config, ...user_config };
|
||||||
|
|
||||||
|
backend_url = config.backend.protocol + '://'
|
||||||
|
+ config.backend.address + ':'
|
||||||
|
+ config.backend.port
|
||||||
|
+ config.backend.path;
|
||||||
|
//TODO test url
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
// Error messages
|
||||||
|
console.debug("Error:", err)
|
||||||
|
console.error("Error loading configuration, using the default settings");
|
||||||
|
console.debug("Search path:", path.resolve("./"));
|
||||||
|
console.debug("Config file:", path.resolve(path.join(config_folder, config_file_name)))
|
||||||
|
|
||||||
|
config = default_config;
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBackendUrl() {
|
||||||
|
if (backend_url) {
|
||||||
|
return backend_url;
|
||||||
|
} else {
|
||||||
|
throw new Error("Backend url is not valid");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
60
frontend/src/services/mods.js
Normal file
60
frontend/src/services/mods.js
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import Cookies from 'js-cookie';
|
||||||
|
|
||||||
|
const API_BASE_URL = 'http://localhost:8000'; //TODO use config manager instead
|
||||||
|
|
||||||
|
|
||||||
|
export async function createMod(mod_data) {
|
||||||
|
try {
|
||||||
|
const auth_token = Cookies.get('authToken');
|
||||||
|
|
||||||
|
const response = await fetch(`${API_BASE_URL}/login`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': auth_token,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({...mod_data})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch items:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function listMods() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/list/mods`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch items:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function getMod(mod_name) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/mods/${mod_name}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch items:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
|
@ -139,6 +139,10 @@ h1 {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loadingContent {
|
||||||
|
color: #7a7a7a;
|
||||||
|
}
|
||||||
|
|
||||||
/* Page transitions */
|
/* Page transitions */
|
||||||
|
|
||||||
.page {
|
.page {
|
||||||
|
|
|
@ -32,6 +32,38 @@
|
||||||
letter-spacing: 0.1rem;
|
letter-spacing: 0.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bigTitle {
|
||||||
|
position: fixed;
|
||||||
|
top: 26rem;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%); /*Center (compensate size)*/
|
||||||
|
z-index: -2;
|
||||||
|
|
||||||
|
color: #eaeaea;
|
||||||
|
text-align: center;
|
||||||
|
font-family: "Inter";
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 8rem;
|
||||||
|
user-select: none;
|
||||||
|
letter-spacing: 0.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
position: fixed;
|
||||||
|
top: 34rem;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%); /*Center (compensate size)*/
|
||||||
|
z-index: -2;
|
||||||
|
|
||||||
|
color: #eaeaea;
|
||||||
|
text-align: center;
|
||||||
|
font-family: "Inter";
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 500%;
|
||||||
|
user-select: none;
|
||||||
|
letter-spacing: 0.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.start-button {
|
.start-button {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 48rem;
|
top: 48rem;
|
||||||
|
|
|
@ -1,3 +1,29 @@
|
||||||
|
.logo {
|
||||||
|
position: absolute;
|
||||||
|
top: 1.6rem;
|
||||||
|
left: 2rem;
|
||||||
|
height: 6em;
|
||||||
|
|
||||||
|
padding: 1.5em;
|
||||||
|
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logoTitle {
|
||||||
|
position: absolute;
|
||||||
|
left: 10rem;
|
||||||
|
top: 4rem;
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
color: #eaeaea;
|
||||||
|
font-family: Inter;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 2.5em;
|
||||||
|
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
.tabsContainer {
|
.tabsContainer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 4rem;
|
left: 4rem;
|
||||||
|
@ -32,7 +58,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab:hover {
|
.tab:hover {
|
||||||
background-color: #3a3a3a;
|
background-color: #eaeaea20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab:active {
|
||||||
|
background-color: #eaeaea30;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logout {
|
.logout {
|
||||||
|
|
Loading…
Reference in a new issue