Skip to content

Commit

Permalink
Merge pull request #3 from BYU-CPC/make-leaderboards-modular
Browse files Browse the repository at this point in the history
make leaderboards modular and add progress bar
  • Loading branch information
joshbtay authored Sep 3, 2024
2 parents 50eb763 + 96f18bb commit d3066c9
Show file tree
Hide file tree
Showing 12 changed files with 177 additions and 48 deletions.
46 changes: 46 additions & 0 deletions src/components/Countdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from "react";
import { useLeaderboardIndex } from "../hooks/UseLeaderboard";
import { useDebounce } from "use-debounce";
import ProgressBar from "./ProgressBar";

const formatTime = (time: number) => {
if (time < 0) {
return "Ended";
}
const seconds = String(Math.floor((time / 1000) % 60)).padStart(2, "0");
const minutes = String(Math.floor((time / (1000 * 60)) % 60)).padStart(
2,
"0"
);
const hours = Math.floor((time / (1000 * 60 * 60)) % 24);
const days = Math.floor(time / (1000 * 60 * 60 * 24));
if (days) return `${days} days, ${hours}:${minutes}:${seconds}`;
return `${hours}:${minutes}:${seconds}`;
};

//Take in react props like classname
function Countdown({
leaderboard,
className,
}: {
leaderboard: string;
className?: string;
}) {
const { data } = useLeaderboardIndex();
const board = data?.[leaderboard];
const currentTime = useDebounce(new Date(), 500)[0];
if (!board) return null;
const timeDisplay = formatTime(board.end.getTime() - currentTime.getTime());
const progress =
(currentTime.getTime() - (board.start.getTime() ?? 0)) /
((board.end.getTime() ?? 1) - (board.start.getTime() ?? 0));
return (
<ProgressBar
progress={progress}
display={timeDisplay}
className={className}
/>
);
}

export default Countdown;
12 changes: 0 additions & 12 deletions src/components/Leaderboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,6 @@
gap: 20px;
}

.expBar {
background-color: var(--bg-color);
height: 4px;
}

.expBarFill {
background-color: #2196f3;
border-radius: 5px;
/* glow */
box-shadow: 0 0 10px #2196f399;
}

.relative {
position: relative;
}
Expand Down
12 changes: 8 additions & 4 deletions src/components/Leaderboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useSearchParams } from "react-router-dom";
import { useAllStudyProblems, useProblems } from "../hooks/UseProblem";
import { useQueries } from "@tanstack/react-query";
import { getStats } from "../score/score";
import { useLeaderboardIndex } from "../hooks/UseLeaderboard";

function formatCodeforcesId(input: string) {
const match = input.match(/^(\d+)(\D.*)$/);
Expand Down Expand Up @@ -43,15 +44,18 @@ export function Leaderboard() {
const leaderboard = params.get("leaderboard") || "all";
const { data: allProblems } = useProblems();
const { data: allStudyProblems } = useAllStudyProblems();
const { data: leaderboardIndex } = useLeaderboardIndex();
const leaderboardData = leaderboardIndex?.[leaderboard];

const calculatedUsers = useQueries({
queries: users.map((user) => ({
queryKey: [leaderboard, "score", user.id],
queryFn: async () => {
return allProblems && allStudyProblems
? getStats(user, allProblems, allStudyProblems)
: null;
return allProblems && allStudyProblems && leaderboardData
? getStats(user, allProblems, allStudyProblems, leaderboardData)
: undefined;
},
enabled: !!allProblems && !!allStudyProblems,
enabled: !!allProblems && !!allStudyProblems && !!leaderboardData,
staleTime: 1000 * 60 * 5,
})),
combine: (results) => results.map((r) => r.data).filter((a) => !!a),
Expand Down
13 changes: 10 additions & 3 deletions src/components/LeaderboardRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import DeadFlame from "../icons/DeadFlame";
import React from "react";
import { useThisWeek } from "../hooks/UseWeek";
import { UserStats } from "../score/score";
import ProgressBar from "./ProgressBar";
function WeeklyProblemBox({
solved,
allProblemsLength,
Expand All @@ -17,7 +18,7 @@ function WeeklyProblemBox({
className={`center rounded tiny transparent ${
solved === 2 ? "bg-green" : solved === 1 ? "outline-green" : ""
}`}
style={{ width: `${100 / allProblemsLength}%` }}
style={{ width: `${100 / allProblemsLength}%`, userSelect: "none" }}
>
</div>
Expand Down Expand Up @@ -106,7 +107,7 @@ export function LeaderboardRow({ userStats, rank, isMe }: LeaderboardRowProps) {
</div>
</div>
<div className="flexRow gap-12">
<div>
<div data-tooltip-id={userId + "-exp"}>
<span className="small">Lv.</span>{" "}
<span className="bold">{userStats.level.level}</span>
</div>
Expand Down Expand Up @@ -145,7 +146,13 @@ export function LeaderboardRow({ userStats, rank, isMe }: LeaderboardRowProps) {
)}
</div>
</div>
<div className="expBar w-full" data-tooltip-id={userId + "-exp"}>
<ProgressBar
progress={
userStats.level.currentExp /
(userStats.level.currentExp + userStats.level.nextLevel)
}
/>
<div className="expBar w-full">
<div
className="expBarFill h-full"
style={{
Expand Down
41 changes: 41 additions & 0 deletions src/components/ProgressBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from "react";
function ProgressBar({
progress,
className,
display,
fontSize,
}: {
progress: number;
display?: string;
className?: string;
fontSize?: string;
}) {
const size = fontSize ?? "10px";
return (
<div
className={" center " + (className ?? "")}
style={{ position: "relative" }}
>
<div
className=" "
style={{
position: "absolute",
width: `${progress * 100}%`,
minHeight: "1px",
backgroundColor: "var(--accent-color)",
background: `linear-gradient(90deg, var(--accent-color) 0%, var(--accent-color-two) 100%)`,
color: "transparent",
userSelect: "none",
fontSize: size,
zIndex: -1,
}}
>
{display ?? ""}
</div>
<div className="w-full" style={{ fontSize: size }}>
{display ?? ""}
</div>
</div>
);
}
export default ProgressBar;
7 changes: 6 additions & 1 deletion src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import React from "react";
import UserBadge from "./UserBadge";
import { useSearchParams } from "react-router-dom";
import Countdown from "./Countdown";

const Sidebar = ({ children }: React.PropsWithChildren) => {
const [params] = useSearchParams();
const leaderboard = params.get("leaderboard") ?? "all";
return (
<div className="flexCol align-center">
<div className="shadow w-full flexRow space-between space-beneath bg-secondary flex-center">
<div className="shadow w-full flexRow space-between bg-secondary flex-center">
<div className="f1" />
<a className="fg-color" href="/challenge">
<h3 className="f1">
Expand All @@ -14,6 +18,7 @@ const Sidebar = ({ children }: React.PropsWithChildren) => {
</a>
<UserBadge />
</div>
<Countdown leaderboard={leaderboard} className="w-full space-beneath " />
{children}
</div>
);
Expand Down
39 changes: 39 additions & 0 deletions src/hooks/UseLeaderboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
import { BACKEND_URL } from "./base";

export type Leaderboard = { start: Date; end: Date };

export type LeaderboardIndex = Record<string, Leaderboard>;
type LeaderboardResponse = Record<
string,
{
start: number;
end: number;
}
>;

const getLeaderboardIndex = async (): Promise<LeaderboardResponse> => {
const response = await axios.get(`${BACKEND_URL}/leaderboard`);
return response.data;
};

export const useLeaderboardIndex = () => {
const query = useQuery({
queryKey: ["leaderboardIndex"],
queryFn: () => getLeaderboardIndex(),
staleTime: 1000 * 60 * 60,
});
const transformedData = query.data
? Object.fromEntries(
Object.entries(query.data).map(([key, value]) => [
key,
{
start: new Date(value.start * 1000),
end: new Date(value.end * 1000),
},
])
)
: undefined;
return { ...query, data: transformedData };
};
1 change: 1 addition & 0 deletions src/hooks/UseProblem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ export const useAllStudyProblems = () => {
return useQuery({
queryFn: getAllStudyProblems,
queryKey: ["allStudyProblems"],
staleTime: 1000 * 60 * 60,
});
};
1 change: 0 additions & 1 deletion src/hooks/UseUser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,5 @@ export const useUsers = () => {
});
}
}
console.log(query.data);
return query.data ?? [];
};
4 changes: 1 addition & 3 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
--bg-greenish: #32a85233;
--fg-color: #333;
--accent-color: #2196f3;
--accent-color-two: #21c2f3;
--fg-accent: #555;
--fg-secondary: #0004;
--rt-transition-show-delay: 1s !important;
Expand Down Expand Up @@ -109,7 +110,6 @@ code {

.responsive-fg {
width: min(90%, 1200px);
border-radius: 5px;
}

.wrap {
Expand Down Expand Up @@ -241,7 +241,6 @@ button {
background-color: var(--bg-tertiary);
color: var(--fg-accent);
padding: 10px;
border-radius: 5px;
cursor: pointer;
}
button:disabled {
Expand All @@ -254,7 +253,6 @@ input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
padding: 10px;
border-radius: 5px;
border: 1px solid var(--fg-secondary);
background-color: var(--bg-tertiary);
color: var(--fg-color);
Expand Down
2 changes: 0 additions & 2 deletions src/pages/SignIn.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
.auth {
width: min(90%, 400px);
padding: 20px;
border-radius: 10px;
}

.sign-in-form {
Expand All @@ -34,7 +33,6 @@

.submit {
padding: 10px;
border-radius: 5px;
border: transparent;
background-color: var(--fg-color);
color: var(--bg-color);
Expand Down
Loading

0 comments on commit d3066c9

Please sign in to comment.