├── .gitignore
├── dist
├── quality-level.JPG
└── index.html
├── src
├── assets
│ ├── My Logo.png
│ └── quality-level.JPG
├── .hintrc
├── modules
│ ├── date-time.js
│ └── scoreblock.js
├── index.html
├── style.css
└── index.js
├── .hintrc
├── .eslintrc.json
├── .stylelintrc.json
├── LICENSE
├── package.json
├── webpack.config.js
├── .github
└── workflows
│ └── linters.yml
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/dist/quality-level.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyars-encarta/Leaderboard-List-App/HEAD/dist/quality-level.JPG
--------------------------------------------------------------------------------
/src/assets/My Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyars-encarta/Leaderboard-List-App/HEAD/src/assets/My Logo.png
--------------------------------------------------------------------------------
/src/assets/quality-level.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyars-encarta/Leaderboard-List-App/HEAD/src/assets/quality-level.JPG
--------------------------------------------------------------------------------
/.hintrc:
--------------------------------------------------------------------------------
1 | {
2 | "connector": {
3 | "name": "local",
4 | "options": {
5 | "pattern": ["**", "!.git/**", "!node_modules/**"]
6 | }
7 | },
8 | "extends": ["development"],
9 | "formatters": ["stylish"],
10 | "hints": [
11 | "button-type",
12 | "disown-opener",
13 | "html-checker",
14 | "meta-charset-utf-8",
15 | "meta-viewport",
16 | "no-inline-styles:error"
17 | ]
18 | }
--------------------------------------------------------------------------------
/src/.hintrc:
--------------------------------------------------------------------------------
1 | {
2 | "connector": {
3 | "name": "local",
4 | "options": {
5 | "pattern": ["**", "!.git/**", "!node_modules/**"]
6 | }
7 | },
8 | "extends": ["development"],
9 | "formatters": ["stylish"],
10 | "hints": [
11 | "button-type",
12 | "disown-opener",
13 | "html-checker",
14 | "meta-charset-utf-8",
15 | "meta-viewport",
16 | "no-inline-styles:error"
17 | ]
18 | }
--------------------------------------------------------------------------------
/src/modules/date-time.js:
--------------------------------------------------------------------------------
1 | import { DateTime } from './luxon.js';
2 |
3 | const myDateTime = () => {
4 | const myDocument = document.getElementById('date-time');
5 |
6 | const dateTimeUpdate = () => {
7 | const dateTimeCurrent = DateTime.now().toLocaleString(
8 | DateTime.DATETIME_FULL_WITH_SECONDS,
9 | );
10 | myDocument.innerHTML = dateTimeCurrent;
11 | };
12 |
13 | setInterval(dateTimeUpdate, 500);
14 | };
15 |
16 | export default myDateTime;
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "jest": true
6 | },
7 | "parser": "babel-eslint",
8 | "parserOptions": {
9 | "ecmaVersion": 2018,
10 | "sourceType": "module"
11 | },
12 | "extends": ["airbnb-base"],
13 | "rules": {
14 | "no-shadow": "off",
15 | "no-param-reassign": "off",
16 | "eol-last": "off",
17 | "import/extensions": [ 1, {
18 | "js": "always", "json": "always"
19 | }]
20 | },
21 | "ignorePatterns": [
22 | "dist/",
23 | "build/"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["stylelint-config-standard"],
3 | "plugins": ["stylelint-scss", "stylelint-csstree-validator"],
4 | "rules": {
5 | "at-rule-no-unknown": [
6 | true,
7 | {
8 | "ignoreAtRules": ["tailwind", "apply", "variants", "responsive", "screen"]
9 | }
10 | ],
11 | "scss/at-rule-no-unknown": [
12 | true,
13 | {
14 | "ignoreAtRules": ["tailwind", "apply", "variants", "responsive", "screen"]
15 | }
16 | ],
17 | "csstree/validator": true
18 | },
19 | "ignoreFiles": ["build/**", "dist/**", "**/reset*.css", "**/bootstrap*.css", "**/*.js", "**/*.jsx"]
20 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Anyars Yussif
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "project-with-webpack",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "private": true,
7 | "scripts": {
8 | "build": "webpack",
9 | "dev": "webpack serve"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "devDependencies": {
15 | "@babel/core": "^7.22.9",
16 | "@babel/preset-env": "^7.22.9",
17 | "babel-eslint": "^10.1.0",
18 | "babel-loader": "^9.1.3",
19 | "css-loader": "^6.8.1",
20 | "eslint": "^7.32.0",
21 | "eslint-config-airbnb-base": "^14.2.1",
22 | "eslint-plugin-import": "^2.28.0",
23 | "eslint-plugin-react": "^7.32.2",
24 | "hint": "^7.1.10",
25 | "html-webpack-plugin": "^5.5.3",
26 | "sass": "^1.63.6",
27 | "sass-loader": "^13.3.2",
28 | "style-loader": "^3.3.3",
29 | "stylelint": "^13.13.1",
30 | "stylelint-config-standard": "^21.0.0",
31 | "stylelint-csstree-validator": "^1.9.0",
32 | "stylelint-scss": "^3.21.0",
33 | "webpack": "^5.88.2",
34 | "webpack-bundle-analyzer": "^4.9.0",
35 | "webpack-cli": "^5.1.4",
36 | "webpack-dev-server": "^4.15.1"
37 | },
38 | "dependencies": {
39 | "axios": "^1.4.0",
40 | "lodash": "^4.17.21",
41 | "luxon": "^3.3.0"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | <%= htmlWebpackPlugin.options.title %>
7 |
8 |
9 |
10 | date-time goes here
11 | Leaderboard
12 |
13 |
14 |
15 |
16 |
Recent Scores
17 | Refresh
18 |
19 |
20 |
23 |
24 |
25 |
26 |
Add your score
27 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Leaderboard List App
7 |
8 |
9 |
10 | date-time goes here
11 | Leaderboard
12 |
13 |
14 |
15 |
16 |
Recent Scores
17 | Refresh
18 |
19 |
20 |
23 |
24 |
25 |
26 |
Add your score
27 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 | // const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
4 |
5 | module.exports = {
6 | mode: 'development',
7 | entry: {
8 | bundle: path.resolve(__dirname, 'src/index.js'),
9 | },
10 | output: {
11 | path: path.resolve(__dirname, 'dist'),
12 | filename: '[name].js',
13 | clean: true,
14 | assetModuleFilename: '[name][ext]',
15 | },
16 | devtool: 'source-map',
17 | devServer: {
18 | static: {
19 | directory: path.resolve(__dirname, 'dist'),
20 | },
21 | port: 3000,
22 | open: true,
23 | hot: true,
24 | compress: true,
25 | historyApiFallback: true,
26 | },
27 | module: {
28 | rules: [
29 | {
30 | test: /\.css$/i,
31 | use: ['style-loader', 'css-loader'],
32 | },
33 | {
34 | test: /\.js$/,
35 | exclude: /node_modules/,
36 | use: {
37 | loader: 'babel-loader',
38 | options: {
39 | presets: ['@babel/preset-env'],
40 | },
41 | },
42 | },
43 | {
44 | test: /\.(png|svg|jpg|jpeg|gif)$/i,
45 | type: 'asset/resource',
46 | },
47 | ],
48 | },
49 | plugins: [
50 | new HtmlWebpackPlugin({
51 | title: 'Leaderboard List App',
52 | filename: 'index.html',
53 | template: 'src/index.html',
54 | }),
55 | // new BundleAnalyzerPlugin(),
56 | ],
57 | };
--------------------------------------------------------------------------------
/.github/workflows/linters.yml:
--------------------------------------------------------------------------------
1 | name: Linters
2 |
3 | on: pull_request
4 |
5 | env:
6 | FORCE_COLOR: 1
7 |
8 | jobs:
9 | lighthouse:
10 | name: Lighthouse
11 | runs-on: ubuntu-22.04
12 | steps:
13 | - uses: actions/checkout@v3
14 | - uses: actions/setup-node@v3
15 | with:
16 | node-version: "18.x"
17 | - name: Setup Lighthouse
18 | run: npm install -g @lhci/cli@0.11.x
19 | - name: Lighthouse Report
20 | run: lhci autorun --upload.target=temporary-public-storage --collect.staticDistDir=.
21 | webhint:
22 | name: Webhint
23 | runs-on: ubuntu-22.04
24 | steps:
25 | - uses: actions/checkout@v3
26 | - uses: actions/setup-node@v3
27 | with:
28 | node-version: "18.x"
29 | - name: Setup Webhint
30 | run: |
31 | npm install --save-dev hint@7.x
32 | [ -f .hintrc ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/html-css-js/.hintrc
33 | - name: Webhint Report
34 | run: npx hint .
35 | stylelint:
36 | name: Stylelint
37 | runs-on: ubuntu-22.04
38 | steps:
39 | - uses: actions/checkout@v3
40 | - uses: actions/setup-node@v3
41 | with:
42 | node-version: "18.x"
43 | - name: Setup Stylelint
44 | run: |
45 | npm install --save-dev stylelint@13.x stylelint-scss@3.x stylelint-config-standard@21.x stylelint-csstree-validator@1.x
46 | [ -f .stylelintrc.json ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/html-css-js/.stylelintrc.json
47 | - name: Stylelint Report
48 | run: npx stylelint "**/*.{css,scss}"
49 | eslint:
50 | name: ESLint
51 | runs-on: ubuntu-22.04
52 | steps:
53 | - uses: actions/checkout@v3
54 | - uses: actions/setup-node@v3
55 | with:
56 | node-version: "18.x"
57 | - name: Setup ESLint
58 | run: |
59 | npm install --save-dev eslint@7.x eslint-config-airbnb-base@14.x eslint-plugin-import@2.x babel-eslint@10.x
60 | [ -f .eslintrc.json ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/html-css-js/.eslintrc.json
61 | - name: ESLint Report
62 | run: npx eslint .
63 | nodechecker:
64 | name: node_modules checker
65 | runs-on: ubuntu-22.04
66 | steps:
67 | - uses: actions/checkout@v3
68 | - name: Check node_modules existence
69 | run: |
70 | if [ -d "node_modules/" ]; then echo -e "\e[1;31mThe node_modules/ folder was pushed to the repo. Please remove it from the GitHub repository and try again."; echo -e "\e[1;32mYou can set up a .gitignore file with this folder included on it to prevent this from happening in the future." && exit 1; fi
--------------------------------------------------------------------------------
/src/style.css:
--------------------------------------------------------------------------------
1 | html {
2 | min-height: 100%;
3 | }
4 |
5 | body {
6 | margin: 0 auto;
7 | width: 95%;
8 | height: 100%;
9 | box-sizing: border-box;
10 | background-image: linear-gradient(to bottom, rgb(15, 15, 15), rgba(0, 0, 0, 0.5)), url(./assets/quality-level.JPG);
11 | background-position: center;
12 | background-repeat: no-repeat;
13 | background-size: cover;
14 | }
15 |
16 | .body,
17 | input,
18 | .add-btn {
19 | font-family: 'Comic Sans MS', sans-serif;
20 | }
21 |
22 | #message,
23 | input,
24 | #refresh {
25 | font-size: 15px;
26 | }
27 |
28 | #add-btn {
29 | align-self: flex-end;
30 | }
31 |
32 | input {
33 | padding: 10px;
34 | }
35 |
36 | #refresh:hover,
37 | #add-btn:hover,
38 | input:focus {
39 | background-color: aqua;
40 | }
41 |
42 | .remove-btn {
43 | display: none;
44 | }
45 |
46 | h1,
47 | #date-time,
48 | h2 {
49 | color: rgb(8, 255, 234);
50 | text-shadow: 1px 1px #0c0c0c;
51 | font-weight: bold;
52 | }
53 |
54 | .main-section,
55 | .list-heading,
56 | form {
57 | display: flex;
58 | }
59 |
60 | .list-heading span,
61 | .add-btn {
62 | font-size: 18px;
63 | padding: 4px;
64 | cursor: pointer;
65 | background-color: #fff;
66 | box-shadow: 4px 4px 0 rgb(2, 2, 2);
67 | }
68 |
69 | #date-time {
70 | float: right;
71 | font-size: 10px;
72 | }
73 |
74 | .main-section {
75 | flex-direction: column-reverse;
76 | justify-content: center;
77 | margin: 0 auto;
78 | gap: 10px;
79 | width: 350px;
80 | }
81 |
82 | form {
83 | flex-direction: column;
84 | gap: 20px;
85 | }
86 |
87 | .list-heading {
88 | align-items: center;
89 | justify-content: space-between;
90 | }
91 |
92 | .list-heading span,
93 | .add-btn,
94 | #score-list {
95 | border: 1px solid black;
96 | }
97 |
98 | #score-list {
99 | list-style-type: none;
100 | font-size: 20px;
101 | padding: 0;
102 | background-color: rgb(8, 255, 234);
103 | border-radius: 20px;
104 | border: 2px solid rgb(8, 255, 234);
105 | }
106 |
107 | li:nth-child(even) {
108 | background-color: #f4f2f2;
109 | }
110 |
111 | /******************************* Desktop View ******************************************/
112 | @media (min-width: 768px) {
113 | body {
114 | width: 70%;
115 | }
116 |
117 | #date-time {
118 | float: right;
119 | font-size: 25px;
120 | }
121 |
122 | h1 {
123 | font-size: 60px;
124 | }
125 |
126 | .main-section {
127 | flex-direction: row;
128 | justify-content: center;
129 | margin: 0 50px 0 50px;
130 | gap: 50px;
131 | width: 900px;
132 | }
133 |
134 | .list-score-section,
135 | .add-score-section {
136 | width: 600px;
137 | }
138 |
139 | h2 {
140 | font-size: 35px;
141 | }
142 |
143 | form {
144 | flex-direction: column;
145 | gap: 20px;
146 | }
147 |
148 | .list-heading h2 {
149 | margin-right: 40px;
150 | }
151 |
152 | li,
153 | .add-btn,
154 | #refresh {
155 | font-size: 30px;
156 | }
157 |
158 | #score-list li {
159 | padding: 0 0 0 5px;
160 | }
161 |
162 | .add-score-section h2 {
163 | margin-bottom: 50px;
164 | }
165 |
166 | #message,
167 | input {
168 | font-size: 20px;
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import './style.css';
2 | import myDateTime from './modules/date-time.js';
3 | import {
4 | createGame, addYourScore, retrieveScores, getGameId,
5 | } from './modules/scoreblock.js';
6 |
7 | const submitForm = document.querySelector('.scores-form');
8 | const refreshButton = document.getElementById('refresh');
9 |
10 | const scoresContainer = document.getElementById('score-list');
11 |
12 | // Declare the gameName variable here
13 | const gameName = "Anyars' Quiz Game";
14 |
15 | const displayScores = (scores) => {
16 | scoresContainer.innerHTML = '';
17 |
18 | if (!Array.isArray(scores) || scores.length === 0) {
19 | const noScoresMessage = document.createElement('p');
20 | noScoresMessage.textContent = 'No scores to display...';
21 | scoresContainer.appendChild(noScoresMessage);
22 | noScoresMessage.style.color = 'red';
23 | } else {
24 | scores.forEach(({ user, score }, index) => {
25 | const listItem = document.createElement('li');
26 | listItem.setAttribute('id', `score-${index}`);
27 | listItem.textContent = `${user}: ${score}`;
28 | scoresContainer.appendChild(listItem);
29 | });
30 | }
31 | };
32 |
33 | const refreshScores = async () => {
34 | try {
35 | // Get the gameId from local storage
36 | let gameId = getGameId();
37 |
38 | if (!gameId) {
39 | gameId = await createGame(gameName);
40 |
41 | if (!gameId) {
42 | // console.error('Error creating/fetching game ID.');
43 | return;
44 | }
45 |
46 | localStorage.setItem('gameId', gameId);
47 | }
48 |
49 | // Pass the scoresContainer
50 | const scores = await retrieveScores(gameId, scoresContainer);
51 | displayScores(scores);
52 | } catch (error) {
53 | // console.error('Error retrieving scores:', error.message);
54 | }
55 | };
56 |
57 | const onSubmitScore = async (e) => {
58 | e.preventDefault();
59 |
60 | const userNameInput = document.getElementById('name');
61 | const scoreInput = document.getElementById('score');
62 |
63 | const userName = userNameInput.value;
64 | const score = scoreInput.value;
65 |
66 | // Validate user input
67 | if (!userName || !score || Number.isNaN(parseInt(score, 10))) {
68 | // Invalid input, handle the error appropriately
69 | const messageSpan = document.getElementById('message');
70 |
71 | messageSpan.style.display = 'block';
72 | messageSpan.style.color = 'red';
73 | messageSpan.textContent = 'Please enter a valid name and score.';
74 | userNameInput.style.border = '1px solid red';
75 | scoreInput.style.border = '1px solid red';
76 | setTimeout(() => {
77 | messageSpan.style.display = 'none';
78 | userNameInput.style.border = '';
79 | scoreInput.style.border = '';
80 | submitForm.reset();
81 | }, 5000);
82 | return;
83 | }
84 |
85 | try {
86 | let gameId = getGameId();
87 |
88 | if (!gameId) {
89 | // If the game ID is not found in local storage,
90 | // create a new game and pass the gameName variable
91 | gameId = await createGame(gameName);
92 |
93 | if (!gameId) {
94 | throw new Error('Error creating/fetching game ID.');
95 | }
96 |
97 | // Set the game ID in local storage for future use
98 | localStorage.setItem('gameId', gameId);
99 | }
100 |
101 | await addYourScore(userName, score);
102 | refreshScores();
103 | submitForm.reset();
104 | } catch (error) {
105 | // console.error('Error submitting score:', error.message);
106 | }
107 | };
108 |
109 | document.addEventListener('DOMContentLoaded', async () => {
110 | myDateTime();
111 | refreshScores();
112 |
113 | try {
114 | // First, create the game or fetch the existing game ID
115 | const gameId = await createGame(gameName);
116 |
117 | if (gameId) {
118 | // If the gameId is successfully retrieved, fetch the scores and display them and pass the
119 | // scoresContainer to retrieveScores
120 | retrieveScores(scoresContainer);
121 | } else {
122 | // console.error('Error creating/fetching game ID.');
123 | }
124 | } catch (error) {
125 | // console.error('Error creating new game:', error.message);
126 | }
127 |
128 | const submitForm = document.querySelector('.scores-form');
129 | submitForm.addEventListener('submit', onSubmitScore);
130 | });
131 |
132 | submitForm.addEventListener('submit', onSubmitScore);
133 | refreshButton.addEventListener('click', refreshScores);
134 |
135 | export default displayScores;
136 |
--------------------------------------------------------------------------------
/src/modules/scoreblock.js:
--------------------------------------------------------------------------------
1 | // Helper function to handle API errors
2 | const handleApiError = () => {
3 | throw new Error('error!');
4 | // Handle API request error
5 | // console.error('An error occurred:', error);
6 | };
7 |
8 | export const getGameId = () => localStorage.getItem('gameId');
9 |
10 | // Function to clear local storage when a new game is created
11 | export const clearLocalStorage = () => {
12 | localStorage.removeItem('gameId');
13 | localStorage.removeItem('scores');
14 | };
15 |
16 | const makeApiRequest = async (url, method, body) => {
17 | try {
18 | const response = await fetch(url, {
19 | method,
20 | headers: {
21 | 'Content-Type': 'application/json',
22 | },
23 | body: JSON.stringify(body),
24 | });
25 |
26 | if (!response.ok) {
27 | throw new Error('API request failed');
28 | }
29 |
30 | // Check if the response has JSON data, if not, return an empty object
31 | const contentType = response.headers.get('content-type');
32 | if (contentType && contentType.indexOf('application/json') !== -1) {
33 | // Parse the JSON and return the response data
34 | return response.json();
35 | }
36 | // Return an empty object if the response doesn't have JSON data
37 | return {};
38 | } catch (error) {
39 | handleApiError(error);
40 | throw error;
41 | }
42 | };
43 |
44 | export const createGame = async (gameName) => {
45 | try {
46 | let gameId = getGameId();
47 |
48 | // Use a fixed game ID if it exists in local storage
49 | const fixedGameId = 'JhRzPkDH3S1gPDwcnOFS';
50 |
51 | if (!gameId) {
52 | const response = await makeApiRequest(
53 | 'https://us-central1-js-capstone-backend.cloudfunctions.net/api/games/',
54 | 'POST',
55 | {
56 | name: gameName,
57 | gameID: fixedGameId, // Pass the fixed game ID in the API request
58 | },
59 | );
60 |
61 | if (response?.result?.startsWith('Game with ID: ')) {
62 | // Extract the gameId from the response
63 | gameId = fixedGameId;
64 | localStorage.setItem('gameId', gameId);
65 | }
66 | }
67 | return gameId;
68 | } catch (error) {
69 | handleApiError(error);
70 | throw error;
71 | }
72 | };
73 |
74 | export const retrieveScoresFromAPI = async (gameId) => {
75 | try {
76 | const response = await makeApiRequest(
77 | `https://us-central1-js-capstone-backend.cloudfunctions.net/api/games/${gameId}/scores/`,
78 | 'GET',
79 | );
80 |
81 | // Extract the scores from the response
82 | const scores = response.result;
83 |
84 | // Check if the scores variable is an array
85 | if (Array.isArray(scores)) {
86 | // Return the extracted scores if it's an array
87 | return scores;
88 | }
89 | throw new Error('Invalid scores data format.');
90 | } catch (error) {
91 | handleApiError(error);
92 | // Return an empty array if there's an error
93 | return [];
94 | }
95 | };
96 |
97 | export const retrieveScores = async () => {
98 | try {
99 | const gameId = getGameId();
100 |
101 | if (!gameId) {
102 | // If the game ID is not found in local storage, display a message
103 | // Return an empty array when the scores are not available
104 | return [];
105 | }
106 |
107 | const scores = await retrieveScoresFromAPI(gameId);
108 | return scores;
109 | } catch (error) {
110 | // Return an empty array when there's an error
111 | return [];
112 | }
113 | };
114 |
115 | export const addYourScore = async (userName, score) => {
116 | try {
117 | // Get the game ID from local storage
118 | const gameId = getGameId();
119 | if (!gameId) {
120 | throw new Error('Game ID not found. Please create a new game first.');
121 | }
122 |
123 | const parsedScore = parseInt(score, 10);
124 |
125 | if (!userName || Number.isNaN(parsedScore) || parsedScore <= 0) {
126 | return;
127 | }
128 |
129 | await makeApiRequest(
130 | `https://us-central1-js-capstone-backend.cloudfunctions.net/api/games/${gameId}/scores/`,
131 | 'POST',
132 | {
133 | user: userName,
134 | score: parsedScore,
135 | },
136 | );
137 |
138 | const scores = await retrieveScores();
139 | const updatedScores = [...scores, { user: userName, score: parsedScore }];
140 | localStorage.setItem('scores', JSON.stringify(updatedScores));
141 | } catch (error) {
142 | handleApiError(error);
143 | throw error;
144 | }
145 | };
146 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |

8 |
9 |
10 |
11 |
12 |
Leaderboard List App
13 |
14 |
15 |
16 |
17 | # 📗 Table of Contents
18 |
19 | - [📖 About the Project](#about-project)
20 | - [🛠 Built With](#built-with)
21 | - [Tech Stack](#tech-stack)
22 | - [Key Features](#key-features)
23 | - [🚀 Live Demo](#live-demo)
24 | - [💻 Getting Started](#getting-started)
25 | - [Setup](#setup)
26 | - [Prerequisites](#prerequisites)
27 | - [Install](#install)
28 | - [Usage](#usage)
29 | - [Run tests](#run-tests)
30 | - [Deployment](#deployment)
31 | - [👥 Authors](#authors)
32 | - [🔭 Future Features](#future-features)
33 | - [🤝 Contributing](#contributing)
34 | - [⭐️ Show your support](#support)
35 | - [🙏 Acknowledgements](#acknowledgements)
36 | - [❓ FAQ (OPTIONAL)](#faq)
37 | - [📝 License](#license)
38 |
39 |
40 | # 📖 Leaderboard List App
41 |
42 | > This is a Leaderboard List App project to display scores of players. The added scores are saved to the [Cloud Functions Engagement API](https://us-central1-js-capstone-backend.cloudfunctions.net/api/games/). They are also preserved to the localStorage. On app launch, the saved scores are fetched from the [Cloud Functions Engagement API](https://us-central1-js-capstone-backend.cloudfunctions.net/api/games/).
43 |
44 | ## 🛠 Built With
45 | 1. HTML
46 | 2. CSS
47 | 3. JavaScript
48 | 4. Webpack
49 | ### Tech Stack
50 |
51 | > Tech Stack to be updated soon
52 |
53 |
54 | Client
55 |
61 |
62 |
63 |
64 |
65 | ### Key Features
66 |
67 | > - Add/Submit Player Scores
68 | > - Retrieve/Display Player Scores
69 | > - Refresh Scores List
70 |
71 | (back to top)
72 |
73 |
74 |
75 | LIVE DEMO
76 |
77 | > Visit the [Live](https://anyars-encarta.github.io/Leaderboard-List-App/dist/) page of this Project.
78 |
79 | (back to top)
80 |
81 |
82 |
83 | ## 💻 Getting Started
84 |
85 | > To get a local copy up and running, follow these steps.
86 |
87 |
88 | ### Setup
89 |
90 | To setup this project, run this command:
91 |
92 | ```npm run test
93 | ```
94 | ### Prerequisites
95 |
96 | 1. A Browser (Preferably Google Chrome)
97 | 2. A Code Editor
98 | 3. Internet Connection
99 | 4. Git
100 |
101 |
102 | ### Install
103 |
104 | Install this project with Iroko.
105 |
106 | ### Usage
107 |
108 | To run the project, execute the following command:
109 |
110 | ### Run tests
111 | > npm run test
112 | ### Deployment
113 |
114 | You can deploy this project using:
115 | >1. GitHub Pages
116 |
117 | (back to top)
118 |
119 |
120 | ## 👥 Authors
121 |
122 | > Mention all of the collaborators of this project.
123 |
124 | 👤 **Anyars Yussif**
125 |
126 | - GitHub: [@anyars-encarta](https://github.com/anyars-encarta)
127 | - Twitter: [@anyarsencarta](https://twitter.com/anyarsencarta)
128 | - LinkedIn: [LinkedIn](https://www.linkedin.com/in/anyars-yussif-1a179769/)
129 |
130 |
131 | (back to top)
132 |
133 | ## 🔭 Future Features
134 |
135 | > Describe 1 - 3 features you will add to the project.
136 |
137 | - [ ] **Display Score Statistics**
138 | - [ ] **Register VIP Players**
139 | - [ ] **Add Ticketing/Reservations**
140 | - [ ] **Schedule Matches**
141 |
142 | (back to top)
143 |
144 |
145 | ## 🤝 Contributing
146 |
147 | Contributions, issues, and feature requests are welcome!
148 |
149 | (back to top)
150 |
151 |
152 |
153 | ## ⭐️ Show your support
154 |
155 | > If you like this project, please give it some starts ⭐️⭐️⭐️⭐️⭐️
156 |
157 | (back to top)
158 |
159 |
160 | ## 🙏 Acknowledgments
161 |
162 | > Special acknowledgement to [@microverseinc](https://github.com/microverseinc)
163 |
164 | (back to top)
165 |
166 |
167 | ## ❓ FAQ (OPTIONAL)
168 |
169 | - **How were the Linters utilised?**
170 |
171 | - The Linters were utilised with the help of resources provided by [@microverseinc](https://github.com/microverseinc).
172 |
173 | - **What new features should be expected in the next release of the project?**
174 |
175 | - I am currently working on adding **Display Score Statistics**, **Register VIP Players**, and **Add Ticketing/Reservations**, **Schedule Matches**.
176 |
177 | (back to top)
178 |
179 |
180 |
181 | ## 📝 License
182 |
183 | This project is [MIT](./LICENSE) licensed.
184 |
185 | (back to top)
186 |
--------------------------------------------------------------------------------