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
|
||||
import HomePage from './pages/home';
|
||||
import AboutPage from './pages/about';
|
||||
import NotFoundPage from './pages/not_found';
|
||||
import ComingSoonPage from './pages/coming_soon';
|
||||
|
||||
import ModsPage from './pages/mods';
|
||||
import ModPage from './pages/mod_page'
|
||||
|
@ -48,6 +50,9 @@ export function App() {
|
|||
|
||||
<LoginPage path='/login'></LoginPage>
|
||||
<RegisterPage path='/register'></RegisterPage>
|
||||
|
||||
<ComingSoonPage path='/soon'></ComingSoonPage>
|
||||
<NotFoundPage path='/notfound' default ></NotFoundPage>
|
||||
</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 {
|
||||
background-color: #eaeaea10;
|
||||
}
|
||||
.leftItem:active {
|
||||
background-color: #eaeaea20;
|
||||
}
|
||||
|
||||
/* WIP */
|
||||
.rightItem {
|
||||
|
|
|
@ -16,7 +16,7 @@ function AboutPage() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<a href='https://radio.oblic-parallels.fr' target="_blank">
|
||||
<a href='/' target="_blank">
|
||||
<img src={logo} class="logo img" alt="WF" />
|
||||
<p class="logo text"> radio </p>
|
||||
</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 (
|
||||
<>
|
||||
<a href='https://radio.oblic-parallels.fr' target="_blank">
|
||||
<a href='/' target="_blank">
|
||||
<img src={logo} class="logo img" alt="WF" />
|
||||
<p class="logo text"> radio </p>
|
||||
</a>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { h } from 'preact';
|
||||
import { useState } from 'preact/hooks';
|
||||
import Cookies from 'js-cookie';
|
||||
import { jwtDecode } from 'jwt-decode'
|
||||
|
||||
// Images
|
||||
import logo from '../assets/logo.png'
|
||||
|
@ -14,7 +15,7 @@ import Button from '../components/Buttons/button';
|
|||
import InputField from '../components/Fields/input_field';
|
||||
|
||||
// Functions
|
||||
import { login } from '../services/api';
|
||||
import { login } from '../services/auth';
|
||||
|
||||
|
||||
function LoginPage() {
|
||||
|
@ -41,8 +42,8 @@ function LoginPage() {
|
|||
};
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault(); // Prevent the default form submission
|
||||
setFieldErrors({ name: null, email: null, message: null });
|
||||
event.preventDefault();
|
||||
setFieldErrors({ username: null, password: null});
|
||||
|
||||
setLoginStatus('logging in');
|
||||
|
||||
|
@ -53,8 +54,12 @@ function LoginPage() {
|
|||
setLoginStatus('success');
|
||||
|
||||
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, {
|
||||
expires: 30,
|
||||
expires: decoded_token.exp, //TODO not sure if it's the right value
|
||||
path: '/',
|
||||
secure: true, // only send over https
|
||||
sameSite: 'strict'
|
||||
|
|
|
@ -3,7 +3,7 @@ import { h } from 'preact';
|
|||
import { useState, useEffect } from 'preact/hooks';
|
||||
|
||||
// Functions
|
||||
import { createMod } from '../services/api';
|
||||
import { createMod } from '../services/mods';
|
||||
|
||||
// Components
|
||||
import InputField from '../components/Fields/input_field'
|
||||
|
|
|
@ -3,7 +3,7 @@ import { h } from 'preact';
|
|||
import { useState, useEffect } from 'preact/hooks';
|
||||
|
||||
// Functions
|
||||
import { fetchMod } from '../services/api';
|
||||
import { getMod } from '../services/mods';
|
||||
|
||||
// Components
|
||||
|
||||
|
@ -28,7 +28,7 @@ function ModPage({name}) {
|
|||
setLoading(true);
|
||||
setError(false);
|
||||
try {
|
||||
const fetched_mod = await fetchMod(name);
|
||||
const fetched_mod = await getMod(name);
|
||||
setMod(fetched_mod);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
|
@ -40,22 +40,48 @@ function ModPage({name}) {
|
|||
loadItems();
|
||||
}, []); // <-- 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) {
|
||||
// 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) {
|
||||
// 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 (
|
||||
<>
|
||||
<a href="/">
|
||||
<img src={logo} class="logo img" alt="WF" />
|
||||
<p class="logo text"> mods </p>
|
||||
</a>
|
||||
|
||||
{base_page}
|
||||
<div className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.backgroundImage}></div>
|
||||
|
|
|
@ -16,7 +16,7 @@ function ModpacksPage() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<a href='https://radio.oblic-parallels.fr' target="_blank">
|
||||
<a href='/' target="_blank">
|
||||
<img src={logo} class="logoSmall img" alt="WF" />
|
||||
<p class="logoSmall text"> modpacks </p>
|
||||
</a>
|
||||
|
|
|
@ -3,7 +3,7 @@ import { h } from 'preact';
|
|||
import { useState, useEffect } from 'preact/hooks';
|
||||
|
||||
// Functions
|
||||
import { fetchMods } from '../services/api';
|
||||
import { listMods } from '../services/mods';
|
||||
|
||||
// Components
|
||||
import FiltersPanel from '../components/Filters/panel'
|
||||
|
@ -29,7 +29,7 @@ function ModsPage() {
|
|||
setLoading(true);
|
||||
setError(false);
|
||||
try {
|
||||
const fetched_mods = await fetchMods();
|
||||
const fetched_mods = await listMods();
|
||||
setMods(fetched_mods);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
|
@ -41,22 +41,49 @@ function ModsPage() {
|
|||
loadItems();
|
||||
}, []); // <-- 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) {
|
||||
// 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) {
|
||||
// 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 (
|
||||
<>
|
||||
<a href="https://radio.oblic-parallels.fr" target="_blank">
|
||||
<img src={logo} class="logo img" alt="WF" />
|
||||
<p class="logo text"> mods </p>
|
||||
</a>
|
||||
<FiltersPanel></FiltersPanel>
|
||||
{base_page}
|
||||
<div class='content-container'>
|
||||
{/* <GridCard>Test card</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 { useState } from 'preact/hooks';
|
||||
import Cookies from 'js-cookie';
|
||||
import { jwtDecode } from 'jwt-decode'
|
||||
|
||||
// Images
|
||||
import logo from '../assets/logo.png'
|
||||
import Logo from '../assets/logo.png'
|
||||
import Account from '../assets/account.svg'
|
||||
|
||||
// Styles
|
||||
import styles from '../styles/login.module.css'
|
||||
|
@ -11,31 +15,104 @@ import styles from '../styles/login.module.css'
|
|||
import Button from '../components/Buttons/button';
|
||||
import InputField from '../components/Fields/input_field';
|
||||
|
||||
// Functions
|
||||
import { register } from '../services/auth';
|
||||
|
||||
|
||||
function RegisterPage() {
|
||||
|
||||
const username_input= "";
|
||||
const display_name_input= "";
|
||||
const email_input= "";
|
||||
const password_input= "";
|
||||
// useState
|
||||
const [username, setUsername] = useState('');
|
||||
const [display_name, setDisplayName] = useState('');
|
||||
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 (
|
||||
<>
|
||||
<a href='https://radio.oblic-parallels.fr' target="_blank">
|
||||
<img src={logo} class="logo img" alt="WF" />
|
||||
<a class="logo text"> radio </a>
|
||||
<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>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<img src={Account} className={styles.loginImage} style={'cursor: pointer'}></img>
|
||||
{/* <input type="file" id="profile_picture" style="display: none;"></input> */}
|
||||
|
||||
<div className={styles.fieldsContainer}>
|
||||
{/* username */}
|
||||
<InputField
|
||||
id="username"
|
||||
name="username"
|
||||
value={username_input}
|
||||
// onChange={handleNameChange}
|
||||
// error={nameError}
|
||||
value={username}
|
||||
onChange={handleUsernameChange}
|
||||
error={fieldErrors.username}
|
||||
placeholder="username"
|
||||
required
|
||||
>
|
||||
|
@ -45,9 +122,9 @@ function RegisterPage() {
|
|||
<InputField
|
||||
id="display_name"
|
||||
name="display_name"
|
||||
value={display_name_input}
|
||||
// onChange={handleNameChange}
|
||||
// error={nameError}
|
||||
value={display_name}
|
||||
onChange={handleDisplayNameChange}
|
||||
error={fieldErrors.display_name}
|
||||
placeholder="display_name"
|
||||
required
|
||||
>
|
||||
|
@ -57,9 +134,9 @@ function RegisterPage() {
|
|||
<InputField
|
||||
id="email"
|
||||
name="email"
|
||||
value={email_input}
|
||||
// onChange={handleNameChange}
|
||||
// error={nameError}
|
||||
value={email}
|
||||
onChange={handleEmailChange}
|
||||
error={fieldErrors.email}
|
||||
placeholder="email"
|
||||
required
|
||||
>
|
||||
|
@ -69,18 +146,22 @@ function RegisterPage() {
|
|||
<InputField
|
||||
id="password"
|
||||
name="password"
|
||||
value={password_input}
|
||||
value={password}
|
||||
type="password"
|
||||
// onChange={handleNameChange}
|
||||
// error={nameError}
|
||||
onChange={handlePasswordChange}
|
||||
error={fieldErrors.password}
|
||||
placeholder="password"
|
||||
required
|
||||
>
|
||||
</InputField>
|
||||
</div>
|
||||
<div className={styles.buttonsContainer}>
|
||||
<Button className={styles.loginButton}>
|
||||
Register
|
||||
<Button
|
||||
className={styles.loginButton}
|
||||
type='submit'
|
||||
disabled={registerStatus === 'registering'}
|
||||
>
|
||||
{registerStatus === 'registering' ? 'Registering' : registerStatus === 'success' ? 'Success' : 'Register'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -44,20 +44,22 @@ function SettingsPage() {
|
|||
return (
|
||||
<>
|
||||
<a href='/'>
|
||||
<img src={Logo} class="logoSmall img" alt="WF" />
|
||||
<p class="logoSmall text"> settings </p>
|
||||
<img src={Logo} className={styles.logo} alt="WF" />
|
||||
<p className={styles.logoTitle}> 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>
|
||||
<>
|
||||
<a href='/notfound'>
|
||||
<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 {
|
||||
|
|
|
@ -32,6 +32,38 @@
|
|||
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 {
|
||||
position: fixed;
|
||||
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 {
|
||||
position: absolute;
|
||||
left: 4rem;
|
||||
|
@ -32,7 +58,11 @@
|
|||
}
|
||||
|
||||
.tab:hover {
|
||||
background-color: #3a3a3a;
|
||||
background-color: #eaeaea20;
|
||||
}
|
||||
|
||||
.tab:active {
|
||||
background-color: #eaeaea30;
|
||||
}
|
||||
|
||||
.logout {
|
||||
|
|
Loading…
Reference in a new issue