Implemented a delete button for mods and fixed an issue in the backend
This commit is contained in:
parent
60e557826e
commit
5af16b593c
|
@ -14,6 +14,6 @@
|
||||||
|
|
||||||
"auth": {
|
"auth": {
|
||||||
"JWT_secret": "HGF7654EGBNKJNBJH6754356788GJHGY",
|
"JWT_secret": "HGF7654EGBNKJNBJH6754356788GJHGY",
|
||||||
"tokenExpiry": "1h"
|
"tokenExpiry": "30d"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -53,7 +53,7 @@ async function getModByName(req, res) {
|
||||||
async function deleteMod(req, res) {
|
async function deleteMod(req, res) {
|
||||||
try {
|
try {
|
||||||
// Authorize
|
// Authorize
|
||||||
authorizeModModification(req);
|
await authorizeModModification(req);
|
||||||
// Query
|
// Query
|
||||||
const name = req.params.name
|
const name = req.params.name
|
||||||
const query_result = await mod_service.deleteMod(name);
|
const query_result = await mod_service.deleteMod(name);
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
// Preact
|
// Preact
|
||||||
import { h } from 'preact';
|
import { h } from 'preact';
|
||||||
import { useState, useEffect } from 'preact/hooks';
|
import { useState, useEffect } from 'preact/hooks';
|
||||||
|
import Cookies from 'js-cookie'
|
||||||
|
import { jwtDecode } from 'jwt-decode'
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
import { createMod } from '../services/mods';
|
import { createMod } from '../services/mods';
|
||||||
|
@ -18,13 +20,113 @@ import styles from '../styles/content_creation.module.css'
|
||||||
import Button from '../components/Buttons/button';
|
import Button from '../components/Buttons/button';
|
||||||
|
|
||||||
function ModCreationPage() {
|
function ModCreationPage() {
|
||||||
|
//TODO add missing fields
|
||||||
|
|
||||||
const user = "guill" //TODO
|
const null_fields = {
|
||||||
|
name: null,
|
||||||
const mod_infos = {
|
display_name: null,
|
||||||
name: ""
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<a href="/">
|
<a href="/">
|
||||||
|
@ -33,7 +135,7 @@ function ModCreationPage() {
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.form}>
|
<form className={styles.form} onSubmit={handleSubmit}>
|
||||||
|
|
||||||
<div className={styles.topFields}>
|
<div className={styles.topFields}>
|
||||||
<div>
|
<div>
|
||||||
|
@ -41,9 +143,9 @@ function ModCreationPage() {
|
||||||
<InputField
|
<InputField
|
||||||
id="name"
|
id="name"
|
||||||
name="name"
|
name="name"
|
||||||
value={mod_infos.name}
|
value={name}
|
||||||
// onChange={handleNameChange}
|
onChange={handleNameChange}
|
||||||
// error={nameError}
|
error={field_errors.name}
|
||||||
placeholder="name"
|
placeholder="name"
|
||||||
required
|
required
|
||||||
className={styles.smallField}
|
className={styles.smallField}
|
||||||
|
@ -54,9 +156,9 @@ function ModCreationPage() {
|
||||||
<InputField
|
<InputField
|
||||||
id="display_name"
|
id="display_name"
|
||||||
name="display_name"
|
name="display_name"
|
||||||
value={mod_infos.display_name}
|
value={display_name}
|
||||||
// onChange={handleNameChange}
|
onChange={handleDisplayNameChange}
|
||||||
// error={nameError}
|
error={field_errors.display_name}
|
||||||
placeholder="display_name"
|
placeholder="display_name"
|
||||||
className={styles.smallField}
|
className={styles.smallField}
|
||||||
>
|
>
|
||||||
|
@ -67,9 +169,9 @@ function ModCreationPage() {
|
||||||
<TextArea
|
<TextArea
|
||||||
id="description"
|
id="description"
|
||||||
name="description"
|
name="description"
|
||||||
value={mod_infos.display_name}
|
value={description}
|
||||||
// onChange={handleNameChange}
|
onChange={handleDescriptionChange}
|
||||||
// error={nameError}
|
error={field_errors.description}
|
||||||
placeholder="description"
|
placeholder="description"
|
||||||
containerClass={styles.descriptionField}
|
containerClass={styles.descriptionField}
|
||||||
>
|
>
|
||||||
|
@ -81,9 +183,9 @@ function ModCreationPage() {
|
||||||
<TextArea
|
<TextArea
|
||||||
id="full_description"
|
id="full_description"
|
||||||
name="full_description"
|
name="full_description"
|
||||||
value={mod_infos.display_name}
|
value={full_description}
|
||||||
// onChange={handleNameChange}
|
onChange={handleFullDescriptionChange}
|
||||||
// error={nameError}
|
error={field_errors.mod_infos.full_description}
|
||||||
placeholder="full_description"
|
placeholder="full_description"
|
||||||
containerClass={styles.fullDescriptionField}
|
containerClass={styles.fullDescriptionField}
|
||||||
>
|
>
|
||||||
|
@ -92,17 +194,15 @@ function ModCreationPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button className={styles.createButton}>
|
<Button className={styles.createButton}>
|
||||||
Create
|
Publish
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</form>
|
||||||
|
|
||||||
|
|
||||||
<div className={styles.infosPanel}>
|
<div className={styles.infosPanel}>
|
||||||
|
|
||||||
<a href={"/users/" + user}>
|
<img src={profile} className={styles.profilePicture}></img>
|
||||||
<img src={profile} className={styles.profilePicture}></img>
|
<p className={styles.author}>{user}</p>
|
||||||
<p className={styles.author}>{user}</p>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
// Preact
|
// Preact
|
||||||
import { h } from 'preact';
|
import { h } from 'preact';
|
||||||
import { useState, useEffect } from 'preact/hooks';
|
import { useState, useEffect } from 'preact/hooks';
|
||||||
|
import Cookies from 'js-cookie'
|
||||||
|
import { jwtDecode } from 'jwt-decode'
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
import { getMod } from '../services/mods';
|
import { getMod, deleteMod } from '../services/mods';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
|
import Button from '../components/Buttons/button';
|
||||||
|
|
||||||
// Images
|
// Images
|
||||||
import logo from '../assets/logo.png'
|
import logo from '../assets/logo.png'
|
||||||
|
@ -22,23 +25,47 @@ function ModPage({name}) {
|
||||||
const [mod, setMod] = useState({})
|
const [mod, setMod] = useState({})
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [error, setError] = useState(null)
|
const [error, setError] = useState(null)
|
||||||
|
const [owner, setOwner] = useState(false);
|
||||||
|
|
||||||
// UseEffect
|
// UseEffect
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function loadItems() {
|
// Load mod informations
|
||||||
setLoading(true);
|
async function loadItems() {
|
||||||
setError(false);
|
setLoading(true);
|
||||||
try {
|
setError(false);
|
||||||
const fetched_mod = await getMod(name);
|
try {
|
||||||
setMod(fetched_mod);
|
const fetched_mod = await getMod(name);
|
||||||
} catch (err) {
|
setMod(fetched_mod);
|
||||||
setError(err.message);
|
} catch (err) {
|
||||||
} finally {
|
setError(err.message);
|
||||||
setLoading(false);
|
} 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 = (
|
const base_page = (
|
||||||
<>
|
<>
|
||||||
|
@ -86,7 +113,10 @@ function ModPage({name}) {
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<div className={styles.backgroundImage}></div>
|
<div className={styles.backgroundImage}></div>
|
||||||
<p className={styles.title}>{mod.display_name || mod.name }</p>
|
<p className={styles.title}>{mod.display_name || mod.name }</p>
|
||||||
<p className={styles.fullDescription}>{mod.mod_infos.full_description || "No description"}</p>
|
<div className={styles.fullDescription}>
|
||||||
|
{/* Sanitized by the backend */}
|
||||||
|
<p dangerouslySetInnerHTML={{ __html: (mod.mod_infos.full_description || "No description") }} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
@ -137,6 +167,22 @@ function ModPage({name}) {
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{ owner ? (
|
||||||
|
<div className={styles.panelActions}>
|
||||||
|
<Button
|
||||||
|
variant='delete'
|
||||||
|
className={styles.deleteButton}
|
||||||
|
onClick={handleDeleteMod}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<> </>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ export async function createMod(mod_data) {
|
||||||
try {
|
try {
|
||||||
const auth_token = Cookies.get('authToken');
|
const auth_token = Cookies.get('authToken');
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE_URL}/login`, {
|
const response = await fetch(`${API_BASE_URL}/mods`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
@ -17,6 +17,7 @@ export async function createMod(mod_data) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
console.error(response.body); //TODO integrate body to error
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +25,7 @@ export async function createMod(mod_data) {
|
||||||
return data;
|
return data;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch items:', error);
|
console.error('Failed to create mod:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +40,7 @@ export async function listMods(filters) {
|
||||||
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 mods:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,7 +55,33 @@ export async function getMod(mod_name) {
|
||||||
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 mod:', error);
|
||||||
throw 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -196,3 +196,23 @@
|
||||||
.version:hover {
|
.version:hover {
|
||||||
background-color: #3a3a3a;
|
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;
|
||||||
|
}
|
|
@ -54,6 +54,8 @@
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
border-radius: .5em;
|
border-radius: .5em;
|
||||||
|
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
transition: 200ms;
|
transition: 200ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue