diff --git a/frontend/config/config.json b/frontend/config/config.json
new file mode 100644
index 0000000..00513e4
--- /dev/null
+++ b/frontend/config/config.json
@@ -0,0 +1,10 @@
+{
+ "port": 8100,
+
+ "backend": {
+ "address": "localhost",
+ "port": 8000,
+ "protocol": "http",
+ "path": "/"
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/app.jsx b/frontend/src/app.jsx
index 5d08b7d..816f90e 100644
--- a/frontend/src/app.jsx
+++ b/frontend/src/app.jsx
@@ -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() {
radio
diff --git a/frontend/src/pages/coming_soon.jsx b/frontend/src/pages/coming_soon.jsx new file mode 100644 index 0000000..198b13c --- /dev/null +++ b/frontend/src/pages/coming_soon.jsx @@ -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 ( + <> + +radio
+ +radio
diff --git a/frontend/src/pages/login.jsx b/frontend/src/pages/login.jsx index 423fc91..42be74f 100644 --- a/frontend/src/pages/login.jsx +++ b/frontend/src/pages/login.jsx @@ -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' diff --git a/frontend/src/pages/mod_creation.jsx b/frontend/src/pages/mod_creation.jsx index 91d219e..2d08fe7 100644 --- a/frontend/src/pages/mod_creation.jsx +++ b/frontend/src/pages/mod_creation.jsx @@ -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' diff --git a/frontend/src/pages/mod_page.jsx b/frontend/src/pages/mod_page.jsx index b49aa88..2f47bb4 100644 --- a/frontend/src/pages/mod_page.jsx +++ b/frontend/src/pages/mod_page.jsx @@ -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); @@ -39,23 +39,49 @@ function ModPage({name}) { loadItems(); }, []); // <-- Tells useEffect to run once after render + + const base_page = ( + <> + +mods
+ + > + ); if (loading) { // TODO replace by loading screen - returnLoading...
+Couldn't load this mod
+{error}
+mods
- - + {base_page}modpacks
diff --git a/frontend/src/pages/mods.jsx b/frontend/src/pages/mods.jsx index 1cb3fdb..0d916fe 100644 --- a/frontend/src/pages/mods.jsx +++ b/frontend/src/pages/mods.jsx @@ -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 = ( + <> + +mods
+ +mods
- -radio
+ +radio
settings
+settings
Global
- -User
- {user ? ( - {logout()}}> -Logout
- + <> + +User
+ + {logout()}}> +Logout
+ + > ) : '' } diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js deleted file mode 100644 index 55291bc..0000000 --- a/frontend/src/services/api.js +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/frontend/src/services/auth.js b/frontend/src/services/auth.js new file mode 100644 index 0000000..9ab7f1e --- /dev/null +++ b/frontend/src/services/auth.js @@ -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; + } +} \ No newline at end of file diff --git a/frontend/src/services/config_manager.js b/frontend/src/services/config_manager.js new file mode 100644 index 0000000..ba822a3 --- /dev/null +++ b/frontend/src/services/config_manager.js @@ -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"); + } + +} \ No newline at end of file diff --git a/frontend/src/services/mods.js b/frontend/src/services/mods.js new file mode 100644 index 0000000..9fbef21 --- /dev/null +++ b/frontend/src/services/mods.js @@ -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; + } +} \ No newline at end of file diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css index 8584284..b542145 100644 --- a/frontend/src/styles/app.css +++ b/frontend/src/styles/app.css @@ -139,6 +139,10 @@ h1 { } +.loadingContent { + color: #7a7a7a; +} + /* Page transitions */ .page { diff --git a/frontend/src/styles/home.css b/frontend/src/styles/home.css index 976b1b1..8f922e6 100644 --- a/frontend/src/styles/home.css +++ b/frontend/src/styles/home.css @@ -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; diff --git a/frontend/src/styles/settings.module.css b/frontend/src/styles/settings.module.css index 1d51359..df023ba 100644 --- a/frontend/src/styles/settings.module.css +++ b/frontend/src/styles/settings.module.css @@ -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 {