WIP
This commit is contained in:
committed by
Ulises Gascon
parent
91ba1f5054
commit
9dd233c371
@@ -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()
|
||||
@@ -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);
|
||||
})
|
||||
});
|
||||
|
||||
}
|
||||
@@ -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 |
@@ -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%;
|
||||
}
|
||||
Reference in New Issue
Block a user