1- import React from "react" ;
1+ import React , { useState , useEffect } from "react" ;
22import { Link } from "react-router-dom" ;
33import { Trophy , ChevronRight } from "lucide-react" ;
4- import { leaderboardData } from "../../data/leaderboard" ;
4+ import { collection , query , where , onSnapshot } from "firebase/firestore" ;
5+ import { db } from "../../lib/firebase" ;
6+ import { useAuth } from "../../context/AuthContext" ;
57import Card from "../ui/Card" ;
68
79export const RankPreview = ( ) => {
8- // Grab top 3 users
9- const topThree = leaderboardData . slice ( 0 , 3 ) ;
10+ const { user } = useAuth ( ) ;
11+ const [ topThree , setTopThree ] = useState ( [ ] ) ;
12+ const [ loading , setLoading ] = useState ( true ) ;
1013
1114 const medalColors = {
1215 1 : "bg-amber-500 text-white shadow-[0_0_15px_rgba(245,158,11,0.4)]" ,
1316 2 : "bg-slate-300 text-slate-900 border border-slate-400/30" ,
1417 3 : "bg-orange-600 text-white"
1518 } ;
1619
20+ useEffect ( ( ) => {
21+ if ( ! user ) {
22+ const timer = setTimeout ( ( ) => {
23+ setLoading ( false ) ;
24+ } , 0 ) ;
25+ return ( ) => clearTimeout ( timer ) ;
26+ }
27+
28+ const q = query (
29+ collection ( db , "users" ) ,
30+ where ( "onboardingStatus" , "==" , "complete" )
31+ ) ;
32+
33+ const unsubscribe = onSnapshot (
34+ q ,
35+ ( snapshot ) => {
36+ const users = [ ] ;
37+ snapshot . forEach ( ( doc ) => {
38+ users . push ( doc . data ( ) ) ;
39+ } ) ;
40+
41+ // Sort by totalPoints descending
42+ users . sort ( ( a , b ) => ( b . points ?. totalPoints || 0 ) - ( a . points ?. totalPoints || 0 ) ) ;
43+
44+ // Slice top 3
45+ const top3 = users . slice ( 0 , 3 ) . map ( ( u , i ) => ( {
46+ ...u ,
47+ rank : i + 1
48+ } ) ) ;
49+
50+ setTopThree ( top3 ) ;
51+ setLoading ( false ) ;
52+ } ,
53+ ( error ) => {
54+ console . error ( "RankPreview query failed:" , error ) ;
55+ setLoading ( false ) ;
56+ }
57+ ) ;
58+
59+ return ( ) => unsubscribe ( ) ;
60+ } , [ user ] ) ;
61+
1762 return (
1863 < Card className = "flex flex-col h-full" >
1964 { /* Header */ }
2065 < div className = "flex items-center justify-between pb-4 border-b border-slate-100 dark:border-slate-800" >
2166 < div >
22- < h3 className = "font-extrabold text-lg text-slate-900 dark:text-white" > Leaderboard Preview</ h3 >
67+ < h3 className = "font-extrabold text-lg text-slate-900 dark:text-white my-0 " > Leaderboard Preview</ h3 >
2368 < p className = "text-xs text-slate-400 dark:text-slate-500" > Top performers this week</ p >
2469 </ div >
2570 < Link
@@ -32,43 +77,51 @@ export const RankPreview = () => {
3277
3378 { /* Ranks Cards List */ }
3479 < div className = "flex-1 mt-6 space-y-4" >
35- { topThree . map ( ( user ) => (
36- < div
37- key = { user . rank }
38- className = "flex items-center justify-between p-3 rounded-xl border border-slate-200/40 dark:border-slate-800/40 bg-slate-50/50 dark:bg-slate-950/30 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors"
39- >
40- < div className = "flex items-center gap-3" >
41- { /* Rank Medal */ }
42- < div className = { `w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold ${ medalColors [ user . rank ] || "bg-slate-200" } ` } >
43- { user . rank === 1 ? < Trophy className = "w-3.5 h-3.5" /> : user . rank }
44- </ div >
80+ { loading ? (
81+ < div className = "h-full flex items-center justify-center py-10" >
82+ < div className = "w-5 h-5 border-2 border-violet-500 border-t-transparent rounded-full animate-spin" />
83+ </ div >
84+ ) : topThree . length > 0 ? (
85+ topThree . map ( ( user ) => (
86+ < div
87+ key = { user . uid }
88+ className = "flex items-center justify-between p-3 rounded-xl border border-slate-200/40 dark:border-slate-800/40 bg-slate-50/50 dark:bg-slate-950/30 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors"
89+ >
90+ < div className = "flex items-center gap-3" >
91+ { /* Rank Medal */ }
92+ < div className = { `w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold ${ medalColors [ user . rank ] || "bg-slate-200" } ` } >
93+ { user . rank === 1 ? < Trophy className = "w-3.5 h-3.5" /> : user . rank }
94+ </ div >
95+
96+ { /* User Info */ }
97+ < div className = "w-9 h-9 rounded-lg overflow-hidden flex-shrink-0" >
98+ < img src = { user . avatar } alt = { user . name } className = "w-full h-full object-cover" />
99+ </ div >
45100
46- { /* User Info */ }
47- < div className = "w-9 h-9 rounded-lg overflow-hidden flex-shrink-0" >
48- < img src = { user . avatar } alt = { user . name } className = "w-full h-full object-cover" />
101+ < div >
102+ < span className = "text-sm font-extrabold text-slate-900 dark:text-slate-200 block leading-tight" >
103+ { user . name }
104+ </ span >
105+ < span className = "text-[10px] font-semibold text-slate-400 dark:text-slate-500 block" >
106+ @{ user . githubUsername } • { user . githubStats ?. primaryLanguage || "JavaScript" }
107+ </ span >
108+ </ div >
49109 </ div >
50110
51- < div >
52- < span className = "text-sm font-extrabold text-slate-900 dark:text-slate-200 block leading-tight" >
53- { user . name }
111+ { /* Points / Streak */ }
112+ < div className = "text-right" >
113+ < span className = "block text-sm font-extrabold text-slate-900 dark:text-white leading-tight" >
114+ { user . points ?. totalPoints ?. toLocaleString ( ) || 0 }
54115 </ span >
55- < span className = "text-[10px] font-semibold text-slate-400 dark:text-slate-500 block " >
56- @ { user . username } • { user . language }
116+ < span className = "inline-flex items-center gap-0.5 text-[10px] font-bold text-orange-500 dark:text-orange-400 " >
117+ 🔥 { user . streak || 1 } d streak
57118 </ span >
58119 </ div >
59120 </ div >
60-
61- { /* Points / Streak */ }
62- < div className = "text-right" >
63- < span className = "block text-sm font-extrabold text-slate-900 dark:text-white leading-tight" >
64- { user . points . toLocaleString ( ) }
65- </ span >
66- < span className = "inline-flex items-center gap-0.5 text-[10px] font-bold text-orange-500 dark:text-orange-400" >
67- 🔥 { user . streak } d streak
68- </ span >
69- </ div >
70- </ div >
71- ) ) }
121+ ) )
122+ ) : (
123+ < p className = "text-xs text-center text-slate-400 py-10" > No ranked users yet</ p >
124+ ) }
72125 </ div >
73126 </ Card >
74127 ) ;
0 commit comments