├── .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 |
28 | 29 | 30 | 31 | 32 | 33 | 34 |
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 |
28 | 29 | 30 | 31 | 32 | 33 | 34 |
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 | logo 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 | --------------------------------------------------------------------------------