Skip to content

Commit 13d6384

Browse files
committed
badges UI
1 parent 4a0b444 commit 13d6384

39 files changed

+711
-423
lines changed

client/index.html

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@
55
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
77

8-
<link href="https://fonts.googleapis.com/css?family=Quicksand" rel="stylesheet" />
9-
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet" />
10-
<link href="https://sdk-style.s3.amazonaws.com/styles-2.0.0.css" rel="stylesheet" />
8+
<link
9+
href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Quicksand:wght@300..700&display=swap"
10+
rel="stylesheet"
11+
/>
12+
<link
13+
href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap"
14+
rel="stylesheet"
15+
/>
16+
<link href="https://sdk-style.s3.amazonaws.com/styles-3.0.2.css" rel="stylesheet" />
1117

1218
<title>Race</title>
1319
</head>

client/src/App.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { useCallback, useContext, useEffect, useMemo, useState } from "react";
22
import { Route, Routes, useSearchParams } from "react-router-dom";
33

4-
// pagrs
4+
// pages
55
import Home from "@pages/Home";
6-
import Leaderboard from "@pages/Leaderboard";
6+
import LeaderboardPage from "@pages/LeaderboardPage";
77
import Error from "@pages/Error";
88

99
// context
@@ -89,7 +89,7 @@ const App = () => {
8989
return (
9090
<Routes>
9191
<Route path="/start" element={<Home />} />
92-
<Route path="/leaderboard" element={<Leaderboard />} />
92+
<Route path="/leaderboard" element={<LeaderboardPage />} />
9393
<Route path="*" element={<Error />} />
9494
</Routes>
9595
);

client/src/components/Admin/AdminGear.jsx

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import PropTypes from "prop-types";
2+
3+
export const AdminIconButton = ({ setShowSettings, showSettings }) => {
4+
return (
5+
<div className="icon-btn mb-4 text-right" onClick={() => setShowSettings(showSettings)}>
6+
{showSettings ? "←" : "⛭"}
7+
</div>
8+
);
9+
};
10+
11+
AdminIconButton.propTypes = {
12+
setShowSettings: PropTypes.func,
13+
showSettings: PropTypes.bool,
14+
};
15+
16+
export default AdminIconButton;
Lines changed: 40 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,62 @@
1-
import { useState, useContext } from "react";
2-
import PropTypes from "prop-types";
1+
import { useContext, useState } from "react";
32

43
// components
5-
import BackArrow from "./BackArrow";
6-
import ResetGameButton from "../ResetGame/ResetGameButton";
7-
import ResetGameModal from "../ResetGame/ResetGameModal";
8-
import SwitchRaceTrackModal from "../SwitchRace/SwitchRaceTrackModal";
9-
import Footer from "../Shared/Footer";
4+
import { ConfirmationModal, Footer } from "@components";
105

116
// context
12-
import { GlobalStateContext } from "@context/GlobalContext";
7+
import { GlobalDispatchContext } from "@context/GlobalContext";
8+
import { RESET_GAME, SCREEN_MANAGER, SET_ERROR } from "@context/types";
9+
10+
// utils
11+
import { backendAPI, getErrorMessage } from "@utils";
12+
13+
export const AdminView = () => {
14+
const dispatch = useContext(GlobalDispatchContext);
1315

14-
function AdminView({ setShowSettings }) {
15-
const { tracks } = useContext(GlobalStateContext);
16-
const [message, setMessage] = useState(false);
1716
const [showResetGameModal, setShowResetGameModal] = useState(false);
18-
const [showTrackModal, setShowTrackModal] = useState(false);
19-
const [selectedTrack, setSelectedTrack] = useState(null);
2017

2118
function handleToggleShowResetGameModal() {
2219
setShowResetGameModal(!showResetGameModal);
2320
}
2421

25-
function handleToggleShowTrackModal(track) {
26-
setSelectedTrack(track);
27-
setShowTrackModal(!showTrackModal);
28-
}
29-
30-
function handleTrackSelect(track) {
31-
setSelectedTrack(track.id);
32-
setShowTrackModal(true);
33-
}
22+
const handleResetGame = async () => {
23+
await backendAPI
24+
.post("/race/reset-game")
25+
.then(() => {
26+
dispatch({ type: RESET_GAME });
27+
dispatch({ type: SCREEN_MANAGER.SHOW_HOME_SCREEN });
28+
})
29+
.catch((error) => {
30+
dispatch({
31+
type: SET_ERROR,
32+
payload: { error: getErrorMessage("resetting", error) },
33+
});
34+
})
35+
.finally(() => {
36+
handleToggleShowResetGameModal();
37+
});
38+
};
3439

3540
return (
3641
<>
42+
<h2 className="text-white">Settings</h2>
43+
44+
<Footer>
45+
<button className="btn-primary" onClick={handleToggleShowResetGameModal}>
46+
Reset Game
47+
</button>
48+
</Footer>
49+
3750
{showResetGameModal && (
38-
<ResetGameModal handleToggleShowModal={handleToggleShowResetGameModal} setMessage={setMessage} />
39-
)}
40-
{showTrackModal && selectedTrack && (
41-
<SwitchRaceTrackModal
42-
track={tracks?.find((track) => track.id === selectedTrack)}
43-
handleToggleShowModal={() => handleToggleShowTrackModal(null)}
44-
setMessage={setMessage}
51+
<ConfirmationModal
52+
title="Reset Game"
53+
message="If you reset the game, the leaderboard will be removed. Are you sure that you would like to continue?"
54+
handleOnConfirm={handleResetGame}
55+
handleToggleShowConfirmationModal={handleToggleShowResetGameModal}
4556
/>
4657
)}
47-
<BackArrow setShowSettings={setShowSettings} />
48-
<div className="px-4 pb-20">
49-
<div className="text-center pb-8">
50-
<h2>Settings</h2>
51-
<p className="pt-4">Select a track to change the current one.</p>
52-
<p>{message}</p>
53-
</div>
54-
{tracks?.map((track) => (
55-
<div
56-
key={track.id}
57-
className={`mb-2 ${selectedTrack === track.id ? "selected" : ""}`}
58-
onClick={() => handleTrackSelect(track)}
59-
>
60-
<div className="card small">
61-
<div className="card-image" style={{ height: "auto" }}>
62-
<img src={track?.thumbnail} alt={track.name} />
63-
</div>
64-
<div className="card-details">
65-
<h4 className="card-title h4">{track.name}</h4>
66-
</div>
67-
</div>
68-
</div>
69-
))}
70-
</div>
71-
<Footer>
72-
<ResetGameButton handleToggleShowModal={handleToggleShowResetGameModal} />
73-
</Footer>
7458
</>
7559
);
76-
}
77-
78-
AdminView.propTypes = {
79-
setShowSettings: PropTypes.func,
8060
};
8161

8262
export default AdminView;

client/src/components/Admin/BackArrow.jsx

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { useContext } from "react";
2+
3+
// components
4+
import { BackButton } from "@components";
5+
6+
// context
7+
import { GlobalDispatchContext, GlobalStateContext } from "@context/GlobalContext";
8+
import { SCREEN_MANAGER } from "@context/types";
9+
10+
export const BadgesScreen = () => {
11+
const dispatch = useContext(GlobalDispatchContext);
12+
const { badges, visitorInventory } = useContext(GlobalStateContext);
13+
14+
return (
15+
<>
16+
<BackButton onClick={() => dispatch({ type: SCREEN_MANAGER.SHOW_HOME_SCREEN })} />
17+
18+
<div className="grid gap-4 text-center">
19+
<h2 className="text-white">
20+
<strong>Badges</strong>
21+
</h2>
22+
23+
<div className="grid grid-cols-3 gap-6 pt-4">
24+
{Object.values(badges).map((badge) => {
25+
const hasBadge = visitorInventory && Object.keys(visitorInventory).includes(badge.name);
26+
const style = hasBadge ? {} : { filter: "grayscale(1)" };
27+
return (
28+
<div className="tooltip" key={badge.id}>
29+
<span className="tooltip-content">{badge.name}</span>
30+
<img src={badge.icon} alt={badge.name} style={style} />
31+
</div>
32+
);
33+
})}
34+
</div>
35+
</div>
36+
</>
37+
);
38+
};
39+
40+
export default BadgesScreen;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { useContext } from "react";
2+
3+
// components
4+
import { Footer } from "@components";
5+
6+
// context
7+
import { GlobalDispatchContext, GlobalStateContext } from "@context/GlobalContext";
8+
import { SCREEN_MANAGER } from "@context/types";
9+
10+
export const Leaderboard = () => {
11+
const dispatch = useContext(GlobalDispatchContext);
12+
const { leaderboard, highScore } = useContext(GlobalStateContext);
13+
14+
return (
15+
<>
16+
<div className="grid gap-4 text-center">
17+
<div className="icon">🏆</div>
18+
<h2 className="text-white">
19+
<strong>Leaderboard</strong>
20+
</h2>
21+
<div className="card-primary grid gap-2">
22+
<h3>Personal Best</h3>
23+
<p>{highScore || "No highScore available"}</p>
24+
</div>
25+
<div className="card-outline">
26+
{leaderboard?.length > 0 ? (
27+
<table>
28+
<thead>
29+
<tr>
30+
<th style={{ minWidth: "20px" }}></th>
31+
<th className="text-left" style={{ minWidth: "200px" }}>
32+
Name
33+
</th>
34+
<th>Time</th>
35+
</tr>
36+
</thead>
37+
<tbody>
38+
{leaderboard?.map((item, index) => {
39+
return (
40+
<tr key={index}>
41+
<td className="text-left">{index + 1}</td>
42+
<td className="text-left">{item.displayName}</td>
43+
<td>{item.highScore}</td>
44+
</tr>
45+
);
46+
})}
47+
</tbody>
48+
</table>
49+
) : (
50+
<p className="text-center">There are no race finishes yet.</p>
51+
)}
52+
</div>
53+
</div>
54+
55+
<Footer>
56+
<button className="btn-primary" onClick={() => dispatch({ type: SCREEN_MANAGER.SHOW_ON_YOUR_MARK_SCREEN })}>
57+
Start Race
58+
</button>
59+
</Footer>
60+
</>
61+
);
62+
};
63+
64+
export default Leaderboard;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { useContext } from "react";
2+
3+
// components
4+
import { BackButton, Leaderboard } from "@components";
5+
6+
// context
7+
import { GlobalDispatchContext } from "@context/GlobalContext";
8+
import { SCREEN_MANAGER } from "@context/types";
9+
10+
export const LeaderboardScreen = () => {
11+
const dispatch = useContext(GlobalDispatchContext);
12+
13+
return (
14+
<>
15+
<BackButton onClick={() => dispatch({ type: SCREEN_MANAGER.SHOW_HOME_SCREEN })} />
16+
17+
<Leaderboard />
18+
</>
19+
);
20+
};
21+
22+
export default LeaderboardScreen;

0 commit comments

Comments
 (0)