This commit is contained in:
Ulises Gascón
2024-02-07 12:29:54 +01:00
committed by Ulises Gascon
parent 91ba1f5054
commit 9dd233c371
143 changed files with 68070 additions and 1 deletions
+116
View File
@@ -0,0 +1,116 @@
// == Selectors ==
const whispers = document.getElementById('whispers')
const whisperCreateButton = document.getElementById('whisper-create')
const welcome = document.getElementById('welcome')
// == Event Listeners ==
whispers.addEventListener('click', event => {
if(event.target.tagName === 'BUTTON') {
const button = event.target
const article = event.target.closest('article')
const action = button.dataset.action
const id = article.dataset.id
const message = article.querySelector('p').innerText
if(action === 'edit') requestUserEdit(id, message)
if(action === 'delete') requestUserDelete(id)
}
})
whisperCreateButton.addEventListener('click', event => {
const message = prompt("What's your whisper?")
if(message) {
createWhisper(message, user.id)
.then(refreshAllUI)
}
})
// === Functions ==
// -- Utils --
function parseJwt (token) {
var base64Url = token.split('.')[1];
var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
var jsonPayload = decodeURIComponent(window.atob(base64).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return JSON.parse(jsonPayload);
}
// -- API --
const fetchAllWhispers = () => fetch('http://localhost:3000/api/v1/whisper', {
headers: {Authentication: `Bearer ${accessToken}`}
}).then((response) => response.json())
const deleteWhisper = (id) => fetch(`http://localhost:3000/api/v1/whisper/${id}`, {
method: 'DELETE',
headers: {Authentication: `Bearer ${accessToken}`}
})
const updateWhisper = (id, message) => fetch(`http://localhost:3000/api/v1/whisper/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authentication': `Bearer ${accessToken}`
},
body: JSON.stringify({ message }) })
const createWhisper = (message) => fetch('http://localhost:3000/api/v1/whisper', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authentication': `Bearer ${accessToken}`
},
body: JSON.stringify({ message }) })
// -- UI --
const controlEdition = (whisper, user) => {
if(whisper.author.id === user.id) {
return ''
} else {
return 'style="display:none;"'
}
}
const refreshWhispers = data => whispers.innerHTML = data
.reverse()
.map(whisper => {
return `
<article data-id="${whisper.id}">
<div class="actions" ${controlEdition(whisper, user)}>
<button data-action="edit">✏️</button>
<button data-action="delete">❌</button>
</div>
<p>${whisper.message}</p>
</hr>
<p class="meta">
<span class="author">By ${whisper.author.username}</span>
<span class="date">at ${new Date(whisper.creationDate).toLocaleString()}</span>
</p>
</article>`
}).join('')
const refreshAllUI = () => fetchAllWhispers().then(refreshWhispers)
const requestUserEdit = (id, message) => {
const newMessage = prompt("Edit the Whisper", message);
if(newMessage && newMessage !== message) {
updateWhisper(id, newMessage)
.then(refreshAllUI)
}
}
const requestUserDelete = (id) => {
const confirmation = confirm("Are you sure you want to delete this whisper?");
if(confirmation) {
deleteWhisper(id)
.then(refreshAllUI)
}
}
// == Initialization ==
const accessToken = localStorage.getItem('accessToken')
if(!accessToken) {
window.location.href = '/login'
}
const {data: user} = parseJwt(accessToken)
welcome.innerText = `Welcome, ${user.username} 👋`
refreshAllUI()
+76
View File
@@ -0,0 +1,76 @@
const locationPath = window.location.pathname
let accessToken = localStorage.getItem('accessToken')
if(accessToken) {
localStorage.removeItem('accessToken')
accessToken = null
}
if(locationPath === '/login'){
const login = document.getElementById('login');
login.addEventListener('submit', (event) => {
event.preventDefault();
const username = event.target.username.value;
const password = event.target.password.value;
fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username,
password
})
})
.then(response => {
if(response.status !== 200) {
throw new Error("Invalid credentials")
}
return response.json()
})
.then(({accessToken}) => {
localStorage.setItem('accessToken', accessToken);
window.location.href = '/';
})
.catch(error => {
alert(error);
})
});
}
if(locationPath === '/signup'){
const sigupForm = document.getElementById('sigup');
sigupForm.addEventListener('submit', (event) => {
event.preventDefault();
const username = event.target.username.value;
const email = event.target.email.value;
const password = event.target.password.value;
fetch('/signup', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username,
email,
password
})
})
.then(response => {
if(response.status !== 200) {
throw new Error("Error while registering the user")
}
return response.json()
})
.then(({accessToken}) => {
localStorage.setItem('accessToken', accessToken);
window.location.href = '/';
})
.catch(error => {
alert(error);
})
});
}
+27
View File
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Whispering | Home</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<nav class="menu">
<ul>
<li><a href="/about">About</a></li>
<li><a href="#">Whispering</a></li>
</ul>
</nav>
<main>
<p id="welcome"></p>
<a href="/logout">Logout</a>
<button type="button" class="block" id="whisper-create">Spread a whisper 🤭</button>
<div id="whispers"></div>
</main>
<script src="app.js"></script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

+133
View File
@@ -0,0 +1,133 @@
html,
body {
font-family: sans-serif;
margin: 0;
padding: 0;
background-color: #fff;
}
.menu ul {
list-style: none;
margin: 0;
padding: 0;
width: 100%;
height: 50px;
background-color: #343e8b;
}
.menu li {
float: right;
margin-right: 4%;
font-weight: bold;
line-height: 50px;
}
.menu li:last-child {
float: left;
margin-left: 4%;
}
.menu a {
color: white;
text-decoration: none;
}
main {
max-width: 600px;
margin: 0 auto;
padding: 20px;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
}
img {
max-width: 100%;
height: auto;
}
figcaption {
font-size: 0.8em;
}
h1 {
font-size: 2em;
line-height: 1.5em;
}
h2 {
font-size: 1.5em;
line-height: 1.5em;
}
p {
font-size: 1.2em;
line-height: 1.5em;
}
#whispers {
border-top: 1px solid #ccc;
margin-top: 20px;
min-width: 100%;
}
article {
background: #f8f6f6;
border: 2px solid #343e8b;
margin: 1.5em 10px;
padding: 0 10px;
border-radius: 5px;
display: flex;
flex-direction: column;
}
article p {
display: inline;
}
article .meta {
font-size: 14px;
}
article .actions {
margin-top: 10px;
display: flex;
justify-content: flex-end;
}
article .actions > button {
cursor: pointer;
margin: 3px;
border: none;
background-color: #f8f6f6;
}
.block {
display: block;
width: 60%;
border: none;
background-color: #343e8b;
padding: 0.5em 10px;
font-size: 16px;
cursor: pointer;
text-align: center;
color: white;
border-radius: 5px;
}
form {
display: flex;
flex-direction: column;
align-items: center;
border: #343e8b 2px solid;
width: 350px;
border-radius: 15px;
padding: 20px;
}
form input {
margin: 10px;
padding: 5px;
border: 1px solid #ccc;
border-radius: 5px;
width: 90%;
}