Skip to content

Commit 5b5cc51

Browse files
committed
feat: implement new route to fetch wrapped leaderboard
1 parent 4c07280 commit 5b5cc51

3 files changed

Lines changed: 44 additions & 25 deletions

File tree

backend/src/controllers/wrapped.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { db } from "../drizzle/db";
2-
import { eq } from "drizzle-orm";
2+
import { eq, ne, asc } from "drizzle-orm";
33
import { wrapped25, users } from "../drizzle/schema";
44
import type { Request, Response } from "express";
55
import { generateAllWrappedStats } from "../scripts/generateWrappedStats";
@@ -32,6 +32,22 @@ export const getWrappedStats = async (req: Request, res: Response): Promise<void
3232
}
3333
};
3434

35+
export const getWrappedLeaderboard = async (req: Request, res: Response): Promise<void> => {
36+
try {
37+
const leaderboard = await db.select({
38+
id: users.id,
39+
name: users.name,
40+
campusRank: wrapped25.campusRank,
41+
finalRating: wrapped25.finalRating
42+
}).from(wrapped25).innerJoin(users, eq(wrapped25.userId, users.id)).orderBy(asc(wrapped25.campusRank)).where(ne(wrapped25.campusRank, 0));
43+
44+
res.status(200).json({ success: true, data: leaderboard });
45+
} catch (error) {
46+
console.error("Error fetching wrapped leaderboard:", error);
47+
res.status(500).json({ success: false, message: "Internal server error." });
48+
}
49+
};
50+
3551
export const triggerWrappedStatsGeneration = async (req: Request, res: Response): Promise<void> => {
3652
try {
3753
await generateAllWrappedStats();

backend/src/routes/wrapped.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import express from "express";
22
import { requireAuth, requireCruxMember } from "../middlewares/auth";
3-
import { getWrappedStats, triggerWrappedStatsGeneration } from "../controllers/wrapped";
3+
import { getWrappedLeaderboard, getWrappedStats, triggerWrappedStatsGeneration } from "../controllers/wrapped";
44

55
const router = express.Router();
66

7-
router.get("/:userId", requireAuth, getWrappedStats);
7+
router.get("/leaderboard", requireAuth, getWrappedLeaderboard);
88
router.post("/generate", requireAuth, requireCruxMember, triggerWrappedStatsGeneration);
9+
router.get("/:userId", requireAuth, getWrappedStats);
910

1011
export default router;

frontend/src/components/Wrapped/CampusLeaderboardSlide.tsx

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,8 @@ import axios from "axios";
55
type LeaderboardUser = {
66
id: string;
77
name: string;
8-
email: string;
9-
cfHandle: string;
10-
cfRating: number;
11-
pfpUrl: string;
12-
batch: string | null;
13-
rank: number;
8+
campusRank: number;
9+
finalRating: number;
1410
};
1511

1612
interface CampusLeaderboardSlideProps {
@@ -61,14 +57,9 @@ const CampusLeaderboardSlide = ({ currentUser }: CampusLeaderboardSlideProps) =>
6157
const fetchLeaderboard = async () => {
6258
try {
6359
setIsLoading(true);
64-
const res = await axios.get("/api/leaderboard");
65-
66-
const rankedLeaderboard: LeaderboardUser[] = res.data.map((user: any, index: number) => ({
67-
...user,
68-
rank: index + 1,
69-
}));
70-
71-
setLeaderboard(rankedLeaderboard);
60+
const res = await axios.get("/api/wrapped/leaderboard", {withCredentials: true});
61+
62+
setLeaderboard(res.data.data);
7263
setIsError(false);
7364
} catch (error) {
7465
console.error("Error fetching leaderboard:", error);
@@ -142,33 +133,44 @@ const CampusLeaderboardSlide = ({ currentUser }: CampusLeaderboardSlideProps) =>
142133
transition={{ duration: 0.5 }}
143134
>
144135
<div className="flex items-center">
145-
<span className="text-xl font-bold w-8" style={{ color: user.id === currentUser.id ? 'white' : COLORS.muted }}>{user.rank}</span>
136+
<span className="text-xl font-bold w-8" style={{ color: user.id === currentUser.id ? 'white' : COLORS.muted }}>{user.campusRank}</span>
146137
<span className="font-semibold">{user.name}</span>
147138
</div>
148-
<span className="font-bold" style={{ color: COLORS.yellow }}>{user.cfRating}</span>
139+
<span className="font-bold" style={{ color: COLORS.yellow }}>{user.finalRating}</span>
149140
</motion.div>
150141
))}
151142
</div>
152143

153144
{!isUserInTop3 && currentUserRanked && (
154145
<motion.div
155146
key={currentUserRanked.id}
156-
className="flex flex-col items-center justify-between p-4 rounded-lg mt-6"
157-
style={{ backgroundColor: COLORS.userHighlight }}
147+
className="flex flex-col items-center justify-between mt-6"
158148
initial={{ opacity: 0, y: 20 }}
159149
animate={{ opacity: 1, y: 0 }}
160150
transition={{ delay: 0.6, duration: 0.5 }}
161151
>
162-
<p className="text-lg font-semibold mb-2">Your Rank</p>
163-
<div className="flex items-center justify-between w-full">
152+
<p className="text-xl font-semibold mb-2">Your Rank</p>
153+
<div className="flex items-center justify-between w-full p-4 rounded-lg" style={{backgroundColor: COLORS.userHighlight}}>
164154
<div className="flex items-center">
165-
<span className="text-2xl font-bold w-10 text-white">{currentUserRanked.rank}</span>
155+
<span className="text-2xl font-bold w-10 text-white">{currentUserRanked.campusRank}</span>
166156
<span className="text-lg font-semibold">{currentUserRanked.name}</span>
167157
</div>
168-
<span className="text-xl font-bold" style={{ color: COLORS.yellow }}>{currentUserRanked.cfRating}</span>
158+
<span className="text-xl font-bold" style={{ color: COLORS.yellow }}>{currentUserRanked.finalRating}</span>
169159
</div>
170160
</motion.div>
171161
)}
162+
163+
{!currentUserRanked && (
164+
<motion.div
165+
key={currentUser.id}
166+
className="flex flex-col items-center justify-between mt-6"
167+
initial={{ opacity: 0, y: 20 }}
168+
animate={{ opacity: 1, y: 0 }}
169+
transition={{ delay: 0.6, duration: 0.5 }}
170+
>
171+
<p className="text-xl font-semibold mb-2">2026 is the perfect year to start your leaderboard journey!</p>
172+
</motion.div>
173+
)}
172174
</motion.div>
173175
)}
174176
</AnimatePresence>

0 commit comments

Comments
 (0)