diff --git a/backend/config/config.json b/backend/config/config.json
index a060d02..c5332f0 100644
--- a/backend/config/config.json
+++ b/backend/config/config.json
@@ -14,6 +14,6 @@
"auth": {
"JWT_secret": "HGF7654EGBNKJNBJH6754356788GJHGY",
- "tokenExpiry": "1h"
+ "tokenExpiry": "30d"
}
}
\ No newline at end of file
diff --git a/backend/src/controllers/mods.js b/backend/src/controllers/mods.js
index 840ce08..17cfe66 100644
--- a/backend/src/controllers/mods.js
+++ b/backend/src/controllers/mods.js
@@ -53,7 +53,7 @@ async function getModByName(req, res) {
async function deleteMod(req, res) {
try {
// Authorize
- authorizeModModification(req);
+ await authorizeModModification(req);
// Query
const name = req.params.name
const query_result = await mod_service.deleteMod(name);
diff --git a/frontend/src/pages/mod_creation.jsx b/frontend/src/pages/mod_creation.jsx
index 2d08fe7..3ee449a 100644
--- a/frontend/src/pages/mod_creation.jsx
+++ b/frontend/src/pages/mod_creation.jsx
@@ -1,6 +1,8 @@
// Preact
import { h } from 'preact';
import { useState, useEffect } from 'preact/hooks';
+import Cookies from 'js-cookie'
+import { jwtDecode } from 'jwt-decode'
// Functions
import { createMod } from '../services/mods';
@@ -18,13 +20,113 @@ import styles from '../styles/content_creation.module.css'
import Button from '../components/Buttons/button';
function ModCreationPage() {
+ //TODO add missing fields
- const user = "guill" //TODO
-
- const mod_infos = {
- name: ""
+ const null_fields = {
+ name: null,
+ display_name: null,
+ description: null,
+ mod_infos: {
+ full_description: null
+ }
}
+ //TODO use a service
+ let user;
+ const token = Cookies.get('authToken');
+ if (token) {
+ const decoded_token = jwtDecode(token);
+ if (decoded_token) {
+ user = decoded_token.username;
+ } else {
+ location.replace('/login')
+ }
+ } else {
+ location.replace('/login')
+ }
+
+ const [name, setName] = useState('');
+ const [display_name, setDisplayName] = useState('');
+ const [description, setDescription] = useState('');
+ const [full_description, setFullDescription] = useState('');
+
+ const [publish_status, setPublishStatus] = useState(null);
+ const [field_errors, setFieldErrors] = useState(null_fields);
+
+
+ // Handle fields changes
+
+ const handleNameChange = (event) => {
+ setName(event.target.value);
+ // Reset error
+ setFieldErrors(prevErrors => ({ ...prevErrors, name: null }));
+ };
+ const handleDisplayNameChange = (event) => {
+ setDisplayName(event.target.value);
+ setFieldErrors(prevErrors => ({ ...prevErrors, display_name: null }));
+ };
+ const handleDescriptionChange = (event) => {
+ setDescription(event.target.value);
+ setFieldErrors(prevErrors => ({ ...prevErrors, description: null }));
+ };
+ const handleFullDescriptionChange = (event) => {
+ setFullDescription(event.target.value);
+ console.debug(event.target.value);
+ setFieldErrors(prevErrors => ({ ...prevErrors, full_description: null }));
+ };
+
+ // Submission
+
+ const handleSubmit = async (event) => {
+ event.preventDefault();
+ setFieldErrors(null_fields);
+
+ setPublishStatus('publishing');
+
+ try {
+ // Gather data
+ const mod_data = {
+ name: name,
+ display_name: display_name,
+ description: description,
+ mod_infos: {
+ full_description: full_description,
+ //TODO handle all possible fields
+ "license": {
+ "type": "none"
+ },
+ "links": []
+ }
+ }
+
+ // Query
+ const response = await createMod(mod_data);
+
+ // On success
+ console.debug('Published successfully:', response);
+ setPublishStatus('success');
+ window.location.replace("/mods/" + name); //TODO no page reloading
+
+ } catch (error) {
+
+ console.error('Creation failed:', error);
+ setPublishStatus('error');
+
+ //TODO handle different codes differently
+ setFieldErrors({
+ name: ' ',
+ display_name: ' ',
+ description: ' ',
+ mod_infos: {
+ full_description: ' '
+ }
+ });
+ }
+ finally {
+ setPublishStatus(null);
+ }
+ };
+
return (
<>
@@ -33,7 +135,7 @@ function ModCreationPage() {
-
diff --git a/frontend/src/pages/mod_page.jsx b/frontend/src/pages/mod_page.jsx
index 2f47bb4..4e2320f 100644
--- a/frontend/src/pages/mod_page.jsx
+++ b/frontend/src/pages/mod_page.jsx
@@ -1,11 +1,14 @@
// Preact
import { h } from 'preact';
import { useState, useEffect } from 'preact/hooks';
+import Cookies from 'js-cookie'
+import { jwtDecode } from 'jwt-decode'
// Functions
-import { getMod } from '../services/mods';
+import { getMod, deleteMod } from '../services/mods';
// Components
+import Button from '../components/Buttons/button';
// Images
import logo from '../assets/logo.png'
@@ -22,23 +25,47 @@ function ModPage({name}) {
const [mod, setMod] = useState({})
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
+ const [owner, setOwner] = useState(false);
+
// UseEffect
- useEffect(() => {
- async function loadItems() {
- setLoading(true);
- setError(false);
- try {
- const fetched_mod = await getMod(name);
- setMod(fetched_mod);
- } catch (err) {
- setError(err.message);
- } finally {
- setLoading(false);
+ useEffect(() => {
+ // Load mod informations
+ async function loadItems() {
+ setLoading(true);
+ setError(false);
+ try {
+ const fetched_mod = await getMod(name);
+ setMod(fetched_mod);
+ } catch (err) {
+ setError(err.message);
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ loadItems();
+
+ }, []); // <-- Tells useEffect to run once after render
+
+ // Handles
+ const handleDeleteMod = async () => {
+ await deleteMod(mod.name);
+ location.replace('/dashboard');
+ };
+
+ // Load user informations
+ //TODO use a service
+ //? for some reason, doesn't work inside useEffect
+ const token = Cookies.get('authToken');
+ if (token) {
+ const decoded_token = jwtDecode(token);
+ if (decoded_token) {
+ console.debug('Here');
+ if (decoded_token.username === mod.author) {
+ setOwner(true);
}
}
-
- loadItems();
- }, []); // <-- Tells useEffect to run once after render
+ }
const base_page = (
<>
@@ -86,7 +113,10 @@ function ModPage({name}) {
{mod.display_name || mod.name }
-
{mod.mod_infos.full_description || "No description"}
+
+ {/* Sanitized by the backend */}
+
+
@@ -137,6 +167,22 @@ function ModPage({name}) {
+ { owner ? (
+
+
+
+ ) : (
+ <> >
+ )
+ }
+
+
diff --git a/frontend/src/services/mods.js b/frontend/src/services/mods.js
index 678f163..a3cea46 100644
--- a/frontend/src/services/mods.js
+++ b/frontend/src/services/mods.js
@@ -7,7 +7,7 @@ export async function createMod(mod_data) {
try {
const auth_token = Cookies.get('authToken');
- const response = await fetch(`${API_BASE_URL}/login`, {
+ const response = await fetch(`${API_BASE_URL}/mods`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -17,6 +17,7 @@ export async function createMod(mod_data) {
});
if (!response.ok) {
+ console.error(response.body); //TODO integrate body to error
throw new Error(`HTTP error! status: ${response.status}`);
}
@@ -24,7 +25,7 @@ export async function createMod(mod_data) {
return data;
} catch (error) {
- console.error('Failed to fetch items:', error);
+ console.error('Failed to create mod:', error);
throw error;
}
}
@@ -39,7 +40,7 @@ export async function listMods(filters) {
const data = await response.json();
return data;
} catch (error) {
- console.error('Failed to fetch items:', error);
+ console.error('Failed to fetch mods:', error);
throw error;
}
}
@@ -54,7 +55,33 @@ export async function getMod(mod_name) {
const data = await response.json();
return data;
} catch (error) {
- console.error('Failed to fetch items:', error);
+ console.error('Failed to fetch mod:', error);
throw error;
}
+}
+
+
+export async function deleteMod(mod_name) {
+ try {
+ const auth_token = Cookies.get('authToken');
+
+ const response = await fetch(`${API_BASE_URL}/mods/${mod_name}`, {
+ method: 'DELETE',
+ headers: {
+ 'Authorization': auth_token,
+ }
+ });
+
+ if (!response.ok) {
+ console.error(response.body); //TODO integrate body to error
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ return data;
+
+ } catch (error) {
+ console.error('Failed to delete mod:', error);
+ throw error;
+ }
}
\ No newline at end of file
diff --git a/frontend/src/styles/content_page.module.css b/frontend/src/styles/content_page.module.css
index 3ef7214..ae05839 100644
--- a/frontend/src/styles/content_page.module.css
+++ b/frontend/src/styles/content_page.module.css
@@ -195,4 +195,24 @@
.version:hover {
background-color: #3a3a3a;
+}
+
+.panelActions {
+ position: absolute;
+ bottom: 0;
+
+ margin: 0 -2rem;
+ padding: 2rem;
+
+ width: 100%;
+
+ box-sizing: border-box;
+}
+
+/*TODO handle versions overlapping*/
+.deleteButton {
+
+ width: 100%;
+ min-width: 5em;
+ font-size: 1.2rem;
}
\ No newline at end of file
diff --git a/frontend/src/styles/settings.module.css b/frontend/src/styles/settings.module.css
index 8ee11fb..c9fca03 100644
--- a/frontend/src/styles/settings.module.css
+++ b/frontend/src/styles/settings.module.css
@@ -54,6 +54,8 @@
font-size: 1.5em;
border-radius: .5em;
+ user-select: none;
+
transition: 200ms;
}