Skip to content

Commit

Permalink
Merge pull request #306 from bettersg/feat/leaderboard
Browse files Browse the repository at this point in the history
added leaderboard features
  • Loading branch information
sarge1989 committed May 22, 2024
2 parents 1985692 + 003b46a commit 53f0d5f
Show file tree
Hide file tree
Showing 27 changed files with 825 additions and 44 deletions.
2 changes: 2 additions & 0 deletions checkers-app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
DashboardPage,
ViewVotePage,
MyVotesPage,
LeaderboardPage,
} from "./pages";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { useUser } from "./providers/UserContext";
Expand All @@ -16,6 +17,7 @@ export const router = createBrowserRouter([
{ path: "/", element: <DashboardPage /> },
{ path: "/votes", element: <MyVotesPage /> },
{ path: "/achievements", element: <AchievementPage /> },
{ path: "/leaderboard", element: <LeaderboardPage /> },
{
path: "/messages/:messageId/voteRequests/:voteRequestId",
element: <ViewVotePage />,
Expand Down
2 changes: 1 addition & 1 deletion checkers-app/src/components/common/BotNavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export default function NavbarDefault() {
variant="text"
className="rounded-full"
onClick={() => {
navigate("/achievements");
navigate("/leaderboard");
}}
ripple
>
Expand Down
13 changes: 13 additions & 0 deletions checkers-app/src/components/leaderboard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Typography } from "@material-tailwind/react";
import { LeaderboardTable } from "./table";

export default function Leaderboard() {
return (
<div className="left-right-padding">
<Typography color="blue-gray" className="font-normal">
Refreshes every month
</Typography>
<LeaderboardTable />
</div>
);
}
203 changes: 203 additions & 0 deletions checkers-app/src/components/leaderboard/table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import { Card, Typography } from "@material-tailwind/react";
import {
ClockIcon,
UserIcon,
TrophyIcon,
HashtagIcon,
CheckCircleIcon,
CalculatorIcon,
} from "@heroicons/react/24/solid";
import { Tooltip } from "@material-tailwind/react";
import { useEffect, useState } from "react";
import { useUser } from "../../providers/UserContext";
import Loading from "../common/Loading";
import { LeaderboardEntry } from "../../types";
import { getLeaderboard } from "../../services/api";

const iconsize = "h-4 w-4";
const COLUMNS = [
{
title: "rank",
icon: <TrophyIcon className={iconsize} />,
description: "Rank",
},
{
title: "name",
icon: <UserIcon className={iconsize} />,
description: "Name",
},
{
title: "numVotes",
icon: <HashtagIcon className={iconsize} />,
description: "Number of votes on messages that did not end in unsure",
},
{
title: "accuracy",
icon: <CheckCircleIcon className={iconsize} />,
description: "Accuracy (%) of votes on messages that did not end in unsure",
},
{
title: "averageTimeTaken",
icon: <ClockIcon className={iconsize} />,
description:
"Average time (hrs) taken on votes on messages that did not end in unsure",
},
{
title: "score",
icon: <CalculatorIcon className={iconsize} />,
description: "Score, based on both accuracy and speed",
},
];

export function LeaderboardTable() {
let lastIndex = 0;
const [isLoading, setIsLoading] = useState(false);
const { checkerDetails } = useUser();
const [abridgedLeaderboard, setAbridgedLeaderboard] = useState<
LeaderboardEntry[]
>([]);

useEffect(() => {
const fetchLeaderboard = async () => {
setIsLoading(true);
if (!checkerDetails.checkerId) {
return;
}
const leaderboard: LeaderboardEntry[] = await getLeaderboard(
checkerDetails.checkerId
);
if (leaderboard) {
setAbridgedLeaderboard(leaderboard);
setIsLoading(false);
}
};
if (checkerDetails.checkerId) {
fetchLeaderboard();
}
}, [checkerDetails.checkerId]);

if (isLoading) {
return <Loading />;
}

return (
<Card className="h-full w-full overflow-scroll">
<table className="w-full min-w-max table-auto text-left">
<thead>
<tr>
{COLUMNS.map((col) => (
<th
key={col.title}
className="border-b border-blue-gray-100 bg-blue-gray-50 p-4"
>
<Tooltip content={col.description}>{col.icon}</Tooltip>
</th>
))}
</tr>
</thead>
<tbody>
{abridgedLeaderboard.map(
(
{
id,
position,
name,
numVoted,
accuracy,
averageTimeTaken,
score,
},
index
) => {
const discontinuity = position - lastIndex > 1;
const isChecker = id === checkerDetails.checkerId;
const isLast = index === abridgedLeaderboard.length - 1;
const rowClasses = isChecker ? "bg-orange-100" : "";
const classes = isLast
? "p-4"
: "p-4 border-b border-blue-gray-50";
lastIndex = position;

return (
<>
{discontinuity && (
<tr
key={`discontinuity-${position}`}
className="bg-gray-100"
>
<td colSpan={6} className="text-center p-4 italic">
<Typography
variant="small"
color="blue-gray"
className="font-normal"
>
...
</Typography>
</td>
</tr>
)}
<tr key={name} className={rowClasses}>
<td className={classes}>
<Typography
variant="small"
color="blue-gray"
className="font-normal"
>
{position}
</Typography>
</td>
<td className={classes}>
<Typography
variant="small"
color="blue-gray"
className="font-normal"
>
{name.length > 10 ? `${name.slice(0, 10)}..` : name}
</Typography>
</td>
<td className={classes}>
<Typography
variant="small"
color="blue-gray"
className="font-normal"
>
{numVoted}
</Typography>
</td>
<td className={classes}>
<Typography
variant="small"
color="blue-gray"
className="font-normal"
>
{(accuracy * 100).toFixed(0)}
</Typography>
</td>
<td className={classes}>
<Typography
variant="small"
color="blue-gray"
className="font-normal"
>
{(averageTimeTaken / 60).toFixed(2)}
</Typography>
</td>
<td className={classes}>
<Typography
variant="small"
color="blue-gray"
className="font-bold"
>
{score.toFixed(1)}
</Typography>
</td>
</tr>
</>
);
}
)}
</tbody>
</table>
</Card>
);
}
5 changes: 1 addition & 4 deletions checkers-app/src/components/vote/CustomReply.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ export default function CustomReply(Prop: PropType) {
checkerDetails.checkerId,
customReplyText
).then(() => {
console.log("Custom reply posted successfully");
navigate("/votes");
});
}
Expand All @@ -57,9 +56,7 @@ export default function CustomReply(Prop: PropType) {
const handleWhatsappTest = () => {
if (checkerDetails.checkerId && customReplyText) {
sendWhatsappTestMessage(checkerDetails.checkerId, customReplyText)
.then((data) => {
console.log(data);
console.log("Test is successfull");
.then(() => {
setShowAlerts(false);
})
.catch((error) => {
Expand Down
10 changes: 10 additions & 0 deletions checkers-app/src/pages/LeaderboardPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Leaderboard from "../components/leaderboard";
import Layout from "../components/common/Layout";

export default function LeaderboardPage() {
return (
<Layout pageName="LEADERBOARD">
<Leaderboard />
</Layout>
);
}
1 change: 0 additions & 1 deletion checkers-app/src/pages/Onboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,6 @@ const Onboarding = () => {
}
setIsOTPValidated(true);
setShowAlerts(false);
console.log("OTP checked");
})
.catch((error) => {
console.error("Error checking OTP", error);
Expand Down
9 changes: 8 additions & 1 deletion checkers-app/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,12 @@ import AchievementPage from "./AchievementPage";
import DashboardPage from "./DashboardPage";
import ViewVotePage from "./VotingPage";
import MyVotesPage from "./MyVotesPage";
import LeaderboardPage from "./LeaderboardPage";

export { AchievementPage, DashboardPage, ViewVotePage, MyVotesPage };
export {
AchievementPage,
DashboardPage,
ViewVotePage,
MyVotesPage,
LeaderboardPage,
};
11 changes: 11 additions & 0 deletions checkers-app/src/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
Vote,
VoteSummaryApiResponse,
PendingCountApiResponse,
LeaderboardEntry,
} from "../types";
import { getAuth, connectAuthEmulator } from "firebase/auth";
import app from "../firebase";
Expand Down Expand Up @@ -189,6 +190,16 @@ export const getMessage = async (messageId: string) => {
return (await axiosInstance.get(`/api/messages/${messageId}`)).data;
};

export const getLeaderboard = async (
checkerId: string
): Promise<LeaderboardEntry[]> => {
if (!checkerId) {
throw new Error("Checker ID missing.");
}
return (await axiosInstance.get(`/api/checkers/${checkerId}/leaderboard`))
.data;
};

export const sendWhatsappTestMessage = async (
checkerId: string,
message: string
Expand Down
2 changes: 2 additions & 0 deletions checkers-app/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
PendingCountApiResponse,
AssessedInfo,
updateChecker,
LeaderboardEntry,
} from "../../functions/src/definitions/api/interfaces";

interface CheckerDetails {
Expand Down Expand Up @@ -34,4 +35,5 @@ export type {
updateChecker,
CheckerDetails,
Window,
LeaderboardEntry,
};
7 changes: 7 additions & 0 deletions functions/src/definitions/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import checkOTPHandler from "./handlers/checkOTP"
import deleteCheckerHandler from "./handlers/deleteChecker"
import postCustomReplyHandler from "./handlers/postCustomReply"
import postWhatsappTestMessageHandler from "./handlers/postWhatsappTestMessage"
import getLeaderboardHandler from "./handlers/getLeaderboard"
import { validateFirebaseIdToken } from "./middleware/validator"
import getMessageHandler from "./handlers/getMessage"

Expand Down Expand Up @@ -77,6 +78,12 @@ checkersRouter.post(
postWhatsappTestMessageHandler
)

checkersRouter.get(
"/checkers/:checkerId/leaderboard",
validateFirebaseIdToken,
getLeaderboardHandler
)

messagesRouter.get(
"/messages/:messageId/voteRequests/:voteRequestId",
validateFirebaseIdToken,
Expand Down
6 changes: 6 additions & 0 deletions functions/src/definitions/api/authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ app.post("/", async (req, res) => {
preferredPlatform: "telegram",
lastVotedTimestamp: null,
getNameMessageId: null,
leaderboardStats: {
numVoted: 0,
numCorrectVotes: 0,
totalTimeTaken: 0,
score: 0,
},
}

try {
Expand Down
Loading

0 comments on commit 53f0d5f

Please sign in to comment.