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": {
|
||||
"JWT_secret": "HGF7654EGBNKJNBJH6754356788GJHGY",
|
||||
"tokenExpiry": "1h"
|
||||
"tokenExpiry": "30d"
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
<a href="/">
|
||||
|
@ -33,7 +135,7 @@ function ModCreationPage() {
|
|||
</a>
|
||||
|
||||
<div className={styles.container}>
|
||||
<div className={styles.form}>
|
||||
<form className={styles.form} onSubmit={handleSubmit}>
|
||||
|
||||
<div className={styles.topFields}>
|
||||
<div>
|
||||
|
@ -41,9 +143,9 @@ function ModCreationPage() {
|
|||
<InputField
|
||||
id="name"
|
||||
name="name"
|
||||
value={mod_infos.name}
|
||||
// onChange={handleNameChange}
|
||||
// error={nameError}
|
||||
value={name}
|
||||
onChange={handleNameChange}
|
||||
error={field_errors.name}
|
||||
placeholder="name"
|
||||
required
|
||||
className={styles.smallField}
|
||||
|
@ -54,9 +156,9 @@ function ModCreationPage() {
|
|||
<InputField
|
||||
id="display_name"
|
||||
name="display_name"
|
||||
value={mod_infos.display_name}
|
||||
// onChange={handleNameChange}
|
||||
// error={nameError}
|
||||
value={display_name}
|
||||
onChange={handleDisplayNameChange}
|
||||
error={field_errors.display_name}
|
||||
placeholder="display_name"
|
||||
className={styles.smallField}
|
||||
>
|
||||
|
@ -67,9 +169,9 @@ function ModCreationPage() {
|
|||
<TextArea
|
||||
id="description"
|
||||
name="description"
|
||||
value={mod_infos.display_name}
|
||||
// onChange={handleNameChange}
|
||||
// error={nameError}
|
||||
value={description}
|
||||
onChange={handleDescriptionChange}
|
||||
error={field_errors.description}
|
||||
placeholder="description"
|
||||
containerClass={styles.descriptionField}
|
||||
>
|
||||
|
@ -81,9 +183,9 @@ function ModCreationPage() {
|
|||
<TextArea
|
||||
id="full_description"
|
||||
name="full_description"
|
||||
value={mod_infos.display_name}
|
||||
// onChange={handleNameChange}
|
||||
// error={nameError}
|
||||
value={full_description}
|
||||
onChange={handleFullDescriptionChange}
|
||||
error={field_errors.mod_infos.full_description}
|
||||
placeholder="full_description"
|
||||
containerClass={styles.fullDescriptionField}
|
||||
>
|
||||
|
@ -92,17 +194,15 @@ function ModCreationPage() {
|
|||
</div>
|
||||
|
||||
<Button className={styles.createButton}>
|
||||
Create
|
||||
Publish
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
<div className={styles.infosPanel}>
|
||||
|
||||
<a href={"/users/" + user}>
|
||||
<img src={profile} className={styles.profilePicture}></img>
|
||||
<p className={styles.author}>{user}</p>
|
||||
</a>
|
||||
<img src={profile} className={styles.profilePicture}></img>
|
||||
<p className={styles.author}>{user}</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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}) {
|
|||
<div className={styles.content}>
|
||||
<div className={styles.backgroundImage}></div>
|
||||
<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>
|
||||
|
||||
|
||||
|
@ -137,6 +167,22 @@ function ModPage({name}) {
|
|||
</a>
|
||||
</div>
|
||||
|
||||
{ owner ? (
|
||||
<div className={styles.panelActions}>
|
||||
<Button
|
||||
variant='delete'
|
||||
className={styles.deleteButton}
|
||||
onClick={handleDeleteMod}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<> </>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -54,6 +54,8 @@
|
|||
font-size: 1.5em;
|
||||
border-radius: .5em;
|
||||
|
||||
user-select: none;
|
||||
|
||||
transition: 200ms;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue