Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Milestone 2: API #2

Merged
merged 8 commits into from Jun 17, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 0 additions & 2 deletions .gitignore
@@ -1,5 +1,3 @@
node_modules/

.vscode/

dist/
46 changes: 46 additions & 0 deletions dist/index.html
@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Leaderboard</title>
<script defer src="index.js"></script></head>
<body>
<main>
<h1>Learderboard</h1>
<div class="flex">
<section class="flex flex-column board-records">
<div class="flex space-between align-center">
<h2>Recent scores</h2>
<button type="button" class="btn refresh-btn">
<span>Refresh</span>
<div class="lds-dots hidden">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</button>
</div>
<ul class="flex flex-column no-spacing scores"></ul>
</section>
<section class="board-form">
<form action="#" class="flex flex-column">
<h2>Add your score</h2>
<input type="text" name="user" class="field" placeholder="your name" required>
<input type="text" name="score" class="field" placeholder="your score" required>
<button type="submit" class="btn">
<span>Submit</span>
<div class="lds-dots hidden">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</button>
</form>
</section>
</div>
</main>
</body>
</html>
243 changes: 243 additions & 0 deletions dist/index.js

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions src/api.js
@@ -0,0 +1,11 @@
export const API_URL = 'https://us-central1-js-capstone-backend.cloudfunctions.net/api/games/';

export const API_NAME = 'sntakirutimana72-leaderboard';

export const postData = (url, data = {}) => fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
11 changes: 8 additions & 3 deletions src/app.js
@@ -1,7 +1,12 @@
import populateScore from './board.js';
import { getScores, postScore } from './board.js';
import { Elements } from './elements.js';

const onready = () => {
populateScore().then(() => {});
const onready = async () => {
await getScores();

Elements.form.addEventListener('submit', postScore);

Elements.refresh.addEventListener('click', getScores);
};

export default onready;
81 changes: 68 additions & 13 deletions src/board.js
@@ -1,18 +1,73 @@
import BOARD_SCORES from './storage.js';
import { API_URL, API_NAME, postData } from './api.js';
import { Elements, $select } from './elements.js';

const renderScoreTemplate = (name, score) => {
const scoreElement = document.createElement('li');
scoreElement.textContent = `${name}: ${score}`;
const getAPIKey = async () => {
let key = localStorage.getItem(API_NAME);

return scoreElement;
if (key === null) {
const response = await postData(API_URL, { name: API_NAME });
const { result } = await response.json();

key = result.replace('Game with ID: ', '').replace(' added.', '');
localStorage.setItem(API_NAME, key);
}

return key;
};

const getAPIFullURL = async () => {
const key = await getAPIKey();

return `${API_URL}${key}/scores/`;
};

const renderScore = ({ user, score }) => {
const element = document.createElement('li');
element.textContent = `${user}: ${score}`;

return element;
};

const flagTrigger = (trigger) => {
const { disabled = false } = trigger;
trigger.disabled = !disabled;

$select('span', trigger).classList.toggle('hidden');
$select('.lds-dots', trigger).classList.toggle('hidden');
};

const populateScore = () => new Promise((resolve, reject) => {
const scoreView = document.querySelector('.scores');
Object.entries(BOARD_SCORES).forEach(
([name, score]) => scoreView.appendChild(renderScoreTemplate(name, score)),
);
resolve();
});
const populateScore = ({ result: scores }) => {
const listView = Elements.scoreList;
listView.innerHTML = '';
scores.forEach((player) => listView.appendChild(renderScore(player)));

flagTrigger(Elements.refresh);
};

export async function postScore(evt) {
evt.preventDefault();
flagTrigger(Elements.submit);

const player = {
user: this.elements.user.value,
score: parseInt(this.elements.score.value, 10),
};
const response = await postData(await getAPIFullURL(), player);
const { result } = await response.json();

export default populateScore;
if (result === 'Leaderboard score created correctly.') {
this.reset();
Elements.scoreList.appendChild(renderScore(player));
}

flagTrigger(Elements.submit);
}

export const getScores = async () => {
flagTrigger(Elements.refresh);

const response = await fetch(await getAPIFullURL());
const result = await response.json();

populateScore(result);
};
25 changes: 25 additions & 0 deletions src/elements.js
@@ -0,0 +1,25 @@
export class Elements {
static get scoreList() {
return document.querySelector('.scores');
}

static get form() {
return document.querySelector('form');
}

static get refresh() {
return document.querySelector('.refresh-btn');
}

static get submit() {
return document.querySelector('button[type=\'submit\']');
}
}

/**
*
* @param {String} selector
* @param {HTMLElement} parentTree
* @returns
*/
export const $select = (selector, parentTree = document.body) => parentTree.querySelector(selector);
110 changes: 101 additions & 9 deletions src/index.css
Expand Up @@ -8,11 +8,13 @@ html {

body {
margin: 0;
background: #101020;
}

* {
font-family: 'Inter', sans-serif;
color: #272727;
color: #bcbccc;
vertical-align: middle;
}

ul {
Expand All @@ -31,6 +33,10 @@ main > div {
gap: 70px !important;
}

.hidden {
display: none !important;
}

.flex {
display: flex;
gap: 15px;
Expand All @@ -53,7 +59,7 @@ h1 {
}

.board-form {
max-width: 270px;
max-width: 250px;
}

form,
Expand All @@ -62,47 +68,133 @@ section {
}

.scores {
border: 2px solid #272727ab;
border: 2px solid #27272763;
box-shadow: 0 0 3px cyan;
flex-grow: 1;
gap: 2px !important;
}

.field {
border-radius: 4px;
padding: 8px;
background-color: #101020;
border: 1px solid #27272763;
box-sizing: border-box;
border: 2px solid #272727;
box-shadow: 0 0 4px black;
box-shadow: 0 0 4px cyan;
}

.field:focus {
outline: none;
border: 2px solid blue;
border-color: skyblue;
}

input:-webkit-autofill {
background-color: #101020 !important;
}

.btn {
border-radius: 4px;
padding: 5px 15px;
border: none;
cursor: pointer;
box-shadow: 0 0 4px rgba(0, 0, 0, 0.4);
background: #101020;
box-shadow: 0 0 4px cyan;
}

.btn:focus {
border: none;
}

input.btn {
.btn[type='submit'] {
max-width: fit-content;
align-self: flex-end;
}

.btn:disabled {
cursor: not-allowed;
}

li {
padding: 4px;
}

li:nth-child(even) {
background: rgba(219, 223, 222, 0.4);
background: #5353be36;
}

.lds-dots {
display: inline-block;
position: relative;
width: 80px;
height: 20px;
}

.lds-dots div {
position: absolute;
top: 3px;
width: 13px;
height: 13px;
border-radius: 50%;
background: cyan;
animation-timing-function: cubic-bezier(0, 1, 1, 0);
}

.lds-dots div:nth-child(1) {
left: 8px;
animation: lds-dots1 0.6s infinite;
}

.lds-dots div:nth-child(2) {
left: 8px;
animation: lds-dots2 0.6s infinite;
}

.lds-dots div:nth-child(3) {
left: 32px;
animation: lds-dots2 0.6s infinite;
}

.lds-dots div:nth-child(4) {
left: 56px;
animation: lds-dots3 0.6s infinite;
}

@keyframes lds-dots1 {
0% {
transform: scale(0);
}

100% {
transform: scale(1);
}
}

@keyframes lds-dots3 {
0% {
transform: scale(1);
}

100% {
transform: scale(0);
}
}

@keyframes lds-dots2 {
0% {
transform: translate(0, 0);
}

100% {
transform: translate(24px, 0);
}
}

@media (max-width: 610px) {
main > div {
flex-direction: column !important;
}

.board-form {
max-width: unset;
}
}