From 56c384935754a2b12da82f73558b75ad514af606 Mon Sep 17 00:00:00 2001 From: Devesh S Date: Mon, 24 Aug 2026 11:46:58 +0530 Subject: [PATCH] student leaderboard - certifications --- src/App.tsx | 15 +- src/components/Footer/Footer.tsx | 2 +- src/components/Navbar/Navbar.tsx | 2 +- src/constants/index.ts | 8 +- .../LeetcodeLeaderboard.tsx | 1648 ++++++++++++----- 5 files changed, 1193 insertions(+), 482 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 742d88d..f52347a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -102,8 +102,19 @@ function App() { path="/leetcode" element={ + + + } + /> + diff --git a/src/components/Footer/Footer.tsx b/src/components/Footer/Footer.tsx index 745b9a0..bc370fd 100644 --- a/src/components/Footer/Footer.tsx +++ b/src/components/Footer/Footer.tsx @@ -10,7 +10,7 @@ const exploreLinks = [ { label: 'Student Toolkit', path: '/toolkit', icon: Wrench }, { label: 'RIT Chatbot', path: '/ai-assistant', icon: Bot }, { label: 'Faculty Directory', path: '/faculty', icon: UserCheck }, - { label: 'LeetCode Arena', path: '/leetcode', icon: Trophy }, + { label: 'Student Leaderboard', path: '/student-leaderboard', icon: Trophy }, ]; const campusLinks = [ diff --git a/src/components/Navbar/Navbar.tsx b/src/components/Navbar/Navbar.tsx index 6fcc3e5..b9c9384 100644 --- a/src/components/Navbar/Navbar.tsx +++ b/src/components/Navbar/Navbar.tsx @@ -49,7 +49,7 @@ const MORE_LINKS: NavItem[] = [ { label: 'Campus Map', path: '/campus', icon: Map }, { label: 'Clubs & Centers', path: '/events', icon: Users }, { label: 'Dev Collab', path: '/collab', icon: Code2 }, - { label: 'LeetCode', path: '/leetcode', icon: Trophy }, + { label: 'Student Leaderboard', path: '/student-leaderboard', icon: Trophy }, { label: 'Student Gate Pass', path: '/notes?portal=gatepass', icon: Ticket }, { label: 'Student Proficiency', path: '/notes?portal=proficiency', icon: Award }, ]; diff --git a/src/constants/index.ts b/src/constants/index.ts index fdff766..a91df3f 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -12,7 +12,7 @@ export const NAV_LINKS = [ { label: 'Clubs & Centers', path: '/events' }, { label: 'Community', path: '/community' }, { label: 'Dev Collab', path: '/collab' }, - { label: 'LeetCode', path: '/leetcode' }, + { label: 'Student Leaderboard', path: '/student-leaderboard' }, ]; // ─── Stats ──────────────────────────────────────────────────────────────────── @@ -110,9 +110,9 @@ export const FEATURES: Feature[] = [ { id: 'leetcode', icon: 'Trophy', - title: 'LeetCode Arena', - description: 'Track campus competitive programming rankings and see top RIT coders.', - path: '/leetcode', + title: 'Student Leaderboard', + description: 'Track campus competitive programming rankings (LeetCode & SkillRack) and verified skill certifications.', + path: '/student-leaderboard', color: '#F59E0B', bgColor: '#FFFBEB', }, diff --git a/src/pages/LeetcodeLeaderboard/LeetcodeLeaderboard.tsx b/src/pages/LeetcodeLeaderboard/LeetcodeLeaderboard.tsx index 4e6852e..d053259 100644 --- a/src/pages/LeetcodeLeaderboard/LeetcodeLeaderboard.tsx +++ b/src/pages/LeetcodeLeaderboard/LeetcodeLeaderboard.tsx @@ -1,15 +1,21 @@ -import { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Trophy, Search, Plus, RefreshCw, ExternalLink, Code2, - Sparkles, Flame, Award, CheckCircle2, Shield, User, Filter, X, Clock, Lock + Sparkles, Flame, Award, CheckCircle2, Shield, ShieldCheck, User, Filter, X, Clock, Lock, Calendar, + Upload, FileText, Check, Database, Cpu, Cloud, ShieldAlert, GraduationCap, + Eye, Trash2, CheckCheck, ChevronDown, Download } from 'lucide-react'; import { getBackendUrl } from '@/lib/utils'; import { useAuth } from '@/context/AuthContext'; +// ───────────────────────────────────────────────────────────── +// Types +// ───────────────────────────────────────────────────────────── + interface LeetcodeProfile { id: number; - studentName: String; + studentName: string; leetcodeUsername: string; department: string; year: string; @@ -22,127 +28,498 @@ interface LeetcodeProfile { lastUpdated: string; } +export type CertDomain = + | 'AI & Machine Learning' + | 'Database & MongoDB' + | 'Cloud & DevOps' + | 'Cybersecurity' + | 'Full Stack & Web' + | 'Software Engineering'; + +interface StudentCertification { + id: string; + studentName: string; + regNumber: string; + title: string; + domain: CertDomain; + issuingBody: string; + issueDate: string; + credentialUrl?: string; + fileName?: string; + fileDataUrl?: string; // Stored Base64 Image/PDF string + status: 'Verified' | 'Pending Verification'; +} + const DEPARTMENTS = ['All', 'CSE', 'IT', 'AI&DS', 'ECE', 'EEE', 'MECH', 'CIVIL', 'CSBS']; const YEARS = ['All', '1st Year', '2nd Year', '3rd Year', '4th Year']; +// ───────────────────────────────────────────────────────────── +// Initial Mock Datasets +// ───────────────────────────────────────────────────────────── + +const MOCK_CERTIFICATE_IMAGE = + 'https://images.unsplash.com/photo-1589330694653-aded6fad0ad1?q=80&w=1000&auto=format&fit=crop'; + +const INITIAL_CERTIFICATIONS: StudentCertification[] = [ + { id: 'cert-1', studentName: 'S DEVESH', regNumber: '211722010042', title: 'Deep Learning Specialization', domain: 'AI & Machine Learning', issuingBody: 'Coursera / DeepLearning.AI', issueDate: 'Jan 2026', credentialUrl: 'https://coursera.org/verify/dl-spec', status: 'Verified' }, + { id: 'cert-2', studentName: 'S DEVESH', regNumber: '211722010042', title: 'TensorFlow Developer Certificate', domain: 'AI & Machine Learning', issuingBody: 'Google', issueDate: 'Nov 2025', credentialUrl: 'https://google.accredible.com/tf-dev', status: 'Verified' }, + { id: 'cert-3', studentName: 'S DEVESH', regNumber: '211722010042', title: 'Generative AI Engineering with LLMs', domain: 'AI & Machine Learning', issuingBody: 'AWS & Coursera', issueDate: 'Feb 2026', credentialUrl: 'https://coursera.org/verify/genai-aws', status: 'Verified' }, + { id: 'cert-4', studentName: 'S DEVESH', regNumber: '211722010042', title: 'Computer Vision Masterclass', domain: 'AI & Machine Learning', issuingBody: 'NPTEL IIT Madras', issueDate: 'Dec 2025', credentialUrl: 'https://nptel.ac.in/noc/Ecert_PK', status: 'Verified' }, + { id: 'cert-5', studentName: 'S DEVESH', regNumber: '211722010042', title: 'MongoDB Certified Associate Developer', domain: 'Database & MongoDB', issuingBody: 'MongoDB University', issueDate: 'Jan 2026', credentialUrl: 'https://university.mongodb.com/cert/associate', status: 'Verified' }, + { id: 'cert-6', studentName: 'S DEVESH', regNumber: '211722010042', title: 'Database Management Systems & SQL', domain: 'Database & MongoDB', issuingBody: 'Oracle / NPTEL', issueDate: 'Oct 2025', credentialUrl: 'https://nptel.ac.in/noc/dbms', status: 'Verified' }, + { id: 'cert-7', studentName: 'S DEVESH', regNumber: '211722010042', title: 'Redis Data Structures & Caching', domain: 'Database & MongoDB', issuingBody: 'Redis University', issueDate: 'Feb 2026', credentialUrl: 'https://university.redis.com/certs', status: 'Verified' }, + { id: 'cert-8', studentName: 'S DEVESH', regNumber: '211722010042', title: 'AWS Certified Solutions Architect - Associate', domain: 'Cloud & DevOps', issuingBody: 'Amazon Web Services', issueDate: 'Dec 2025', credentialUrl: 'https://aws.amazon.com/verification', status: 'Verified' }, + { id: 'cert-9', studentName: 'S DEVESH', regNumber: '211722010042', title: 'Docker & Kubernetes Fundamentals', domain: 'Cloud & DevOps', issuingBody: 'Linux Foundation', issueDate: 'Jan 2026', credentialUrl: 'https://training.linuxfoundation.org/verify', status: 'Verified' }, + { id: 'cert-10', studentName: 'S DEVESH', regNumber: '211722010042', title: 'CompTIA Security+ Certification', domain: 'Cybersecurity', issuingBody: 'CompTIA', issueDate: 'Nov 2025', credentialUrl: 'https://comptia.org/verify', status: 'Verified' }, + { id: 'cert-11', studentName: 'S DEVESH', regNumber: '211722010042', title: 'Meta Front-End Developer Specialization', domain: 'Full Stack & Web', issuingBody: 'Meta / Coursera', issueDate: 'Jan 2026', credentialUrl: 'https://coursera.org/verify/meta-frontend', status: 'Verified' }, + { id: 'cert-12', studentName: 'S DEVESH', regNumber: '211722010042', title: 'React & Next.js Professional Developer', domain: 'Full Stack & Web', issuingBody: 'Vercel Academy', issueDate: 'Feb 2026', credentialUrl: 'https://vercel.com/academy/certs', status: 'Verified' }, +]; + +// ───────────────────────────────────────────────────────────── +// Custom Modern Floating Dropdown Component (Matching Figure 2 Navbar Style) +// ───────────────────────────────────────────────────────────── +interface DomainOption { + value: string; + label: string; + count: number; + icon: any; + color: string; +} + +function CustomDomainDropdown({ + selectedDomain, + onSelectDomain, + totalCerts, + counts, +}: { + selectedDomain: string; + onSelectDomain: (val: string) => void; + totalCerts: number; + counts: Record; +}) { + const [isOpen, setIsOpen] = useState(false); + const dropdownRef = useRef(null); + + useEffect(() => { + const handleClickOutside = (e: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) { + setIsOpen(false); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); + + const options: DomainOption[] = [ + { value: 'ALL', label: 'All Technology Domains', count: totalCerts, icon: Award, color: 'text-[#FF6B00]' }, + { value: 'AI & Machine Learning', label: 'AI & Machine Learning', count: counts['AI & Machine Learning'], icon: Cpu, color: 'text-purple-400' }, + { value: 'Database & MongoDB', label: 'Database & MongoDB', count: counts['Database & MongoDB'], icon: Database, color: 'text-emerald-400' }, + { value: 'Cloud & DevOps', label: 'Cloud & DevOps', count: counts['Cloud & DevOps'], icon: Cloud, color: 'text-sky-400' }, + { value: 'Cybersecurity', label: 'Cybersecurity', count: counts['Cybersecurity'], icon: ShieldAlert, color: 'text-rose-400' }, + { value: 'Full Stack & Web', label: 'Full Stack & Web', count: counts['Full Stack & Web'], icon: Code2, color: 'text-amber-400' }, + { value: 'Software Engineering', label: 'Software Engineering', count: counts['Software Engineering'], icon: Sparkles, color: 'text-indigo-400' }, + ]; + + const currentOption = options.find((o) => o.value === selectedDomain) || options[0]; + const CurrentIcon = currentOption.icon; + + return ( +
+ + + + {isOpen && ( + + {options.map((opt) => { + const IconComp = opt.icon; + const isSelected = selectedDomain === opt.value; + return ( + + ); + })} + + )} + +
+ ); +} + +// ───────────────────────────────────────────────────────────── +// Custom Form Select Component (White Theme Matching Modal Fields) +// ───────────────────────────────────────────────────────────── +interface CustomSelectOption { + value: string; + label: string; + icon?: any; + color?: string; +} + +const DOMAIN_FORM_OPTIONS: CustomSelectOption[] = [ + { value: 'Database & MongoDB', label: 'Database & MongoDB', icon: Database, color: 'text-emerald-500' }, + { value: 'AI & Machine Learning', label: 'AI & Machine Learning', icon: Cpu, color: 'text-purple-500' }, + { value: 'Cloud & DevOps', label: 'Cloud & DevOps', icon: Cloud, color: 'text-sky-500' }, + { value: 'Cybersecurity', label: 'Cybersecurity', icon: ShieldAlert, color: 'text-rose-500' }, + { value: 'Full Stack & Web', label: 'Full Stack & Web', icon: Code2, color: 'text-amber-500' }, + { value: 'Software Engineering', label: 'Software Engineering', icon: Sparkles, color: 'text-indigo-500' }, +]; + +const DEPT_FORM_OPTIONS: CustomSelectOption[] = DEPARTMENTS.filter(d => d !== 'All').map(d => ({ + value: d, + label: d, + icon: GraduationCap, + color: 'text-[#FF6B00]', +})); + +const YEAR_FORM_OPTIONS: CustomSelectOption[] = YEARS.filter(y => y !== 'All').map(y => ({ + value: y, + label: y, + icon: Clock, + color: 'text-amber-500', +})); + +function CustomFormSelect({ + value, + onChange, + options, + placeholder, +}: { + value: string; + onChange: (val: string) => void; + options: CustomSelectOption[]; + placeholder?: string; +}) { + const [isOpen, setIsOpen] = useState(false); + const dropdownRef = useRef(null); + + useEffect(() => { + const handleClickOutside = (e: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) { + setIsOpen(false); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); + + const selectedOpt = options.find((o) => o.value === value) || options[0]; + const IconComponent = selectedOpt?.icon; + + return ( +
+ + + + {isOpen && ( + + {options.map((opt) => { + const OptIcon = opt.icon; + const isSelected = value === opt.value; + return ( + + ); + })} + + )} + +
+ ); +} + export default function LeetcodeLeaderboard() { - const { isAuthenticated, openAuthModal } = useAuth(); - const [profiles, setProfiles] = useState([]); - const [loading, setLoading] = useState(true); + const { user, isAuthenticated, openAuthModal } = useAuth(); + + // Top Feature Switcher State (SkillRack removed per request) + const [activeMainTab, setActiveMainTab] = useState<'leetcode' | 'certifications'>('leetcode'); + + // Shared Filters State const [searchQuery, setSearchQuery] = useState(''); const [selectedDept, setSelectedDept] = useState('All'); const [selectedYear, setSelectedYear] = useState('All'); - const [showModal, setShowModal] = useState(false); - const [isSubmitting, setIsSubmitting] = useState(false); - const [isSyncing, setIsSyncing] = useState(false); const [toastMessage, setToastMessage] = useState(null); - // Form State - const [studentName, setStudentName] = useState(''); - const [leetcodeUsername, setLeetcodeUsername] = useState(''); - const [department, setDepartment] = useState('CSE'); - const [year, setYear] = useState('1st Year'); + // ───────────────────────────────────────────────────────────── + // 1. LeetCode Tab State + // ───────────────────────────────────────────────────────────── + const [leetcodeProfiles, setLeetcodeProfiles] = useState([]); + const [leetcodeLoading, setLeetcodeLoading] = useState(true); + const [showLeetcodeModal, setShowLeetcodeModal] = useState(false); + const [isLeetcodeSubmitting, setIsLeetcodeSubmitting] = useState(false); + const [isLeetcodeSyncing, setIsLeetcodeSyncing] = useState(false); + // LeetCode Form + const [lcStudentName, setLcStudentName] = useState(''); + const [lcUsername, setLcUsername] = useState(''); + const [lcDept, setLcDept] = useState('CSE'); + const [lcYear, setLcYear] = useState('1st Year'); + + // ───────────────────────────────────────────────────────────── + // 2. Certifications Vault Tab State + // ───────────────────────────────────────────────────────────── + const [certifications, setCertifications] = useState(() => { + try { + const saved = localStorage.getItem('rit_student_certifications'); + return saved ? JSON.parse(saved) : INITIAL_CERTIFICATIONS; + } catch { + return INITIAL_CERTIFICATIONS; + } + }); + + // Domain Filter Dropdown (Replaced horizontal scroll buttons) + const [selectedDomainFilter, setSelectedDomainFilter] = useState('ALL'); + const [showCertModal, setShowCertModal] = useState(false); + + // Certificate Lightbox / Viewer Modal State + const [viewingCert, setViewingCert] = useState(null); + + // Upload Cert Form + const [certTitle, setCertTitle] = useState(''); + const [certDomain, setCertDomain] = useState('Database & MongoDB'); + const [certIssuer, setCertIssuer] = useState(''); + const [certIssueDate, setCertIssueDate] = useState(() => new Date().toISOString().split('T')[0]); + const [certCredentialUrl, setCertCredentialUrl] = useState(''); + const [certFile, setCertFile] = useState(null); + const [certFileDataUrl, setCertFileDataUrl] = useState(null); + + // Fetch LeetCode Leaderboard on Mount if Auth useEffect(() => { if (isAuthenticated) { - fetchLeaderboard(); + fetchLeetcodeLeaderboard(); } }, [isAuthenticated]); - if (!isAuthenticated) { - return ( -
-
-
- -
-
-

- Student Sign In Required -

-

- RIT LeetCode Arena & Campus Rankings are accessible exclusively to verified RIT students. Please sign in with your student credentials to view the rankings and register your handle. -

-
- -
-
- ); - } + // Sync certifications to LocalStorage + useEffect(() => { + try { + localStorage.setItem('rit_student_certifications', JSON.stringify(certifications)); + } catch (e) { + console.warn('Failed saving certifications:', e); + } + }, [certifications]); - const fetchLeaderboard = async () => { - setLoading(true); + // Auto-fill student details from auth if available + useEffect(() => { + if (user?.name) { + setLcStudentName(user.name); + if (user.department) { + setLcDept(user.department); + } + } + }, [user]); + + const fetchLeetcodeLeaderboard = async () => { + setLeetcodeLoading(true); try { const res = await fetch(getBackendUrl('/api/leetcode/leaderboard')); if (res.ok) { const data = await res.json(); - setProfiles(data); + setLeetcodeProfiles(data); } } catch (err) { - console.error('Failed to fetch leaderboard:', err); + console.error('Failed to fetch LeetCode leaderboard:', err); } finally { - setLoading(false); + setLeetcodeLoading(false); } }; - const handleRegister = async (e: React.FormEvent) => { + const handleRegisterLeetcode = async (e: React.FormEvent) => { e.preventDefault(); - if (!studentName.trim() || !leetcodeUsername.trim()) return; + if (!lcStudentName.trim() || !lcUsername.trim()) return; - setIsSubmitting(true); + setIsLeetcodeSubmitting(true); try { const res = await fetch(getBackendUrl('/api/leetcode/register'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - studentName, - leetcodeUsername, - department, - year + studentName: lcStudentName, + leetcodeUsername: lcUsername, + department: lcDept, + year: lcYear, }), }); if (res.ok) { setToastMessage('✅ Profile added successfully! Fetched latest LeetCode stats.'); - setShowModal(false); - setStudentName(''); - setLeetcodeUsername(''); - fetchLeaderboard(); + setShowLeetcodeModal(false); + setLcUsername(''); + fetchLeetcodeLeaderboard(); } else { const errData = await res.json(); setToastMessage(`❌ ${errData.error || 'Failed to add profile'}`); } - } catch (err) { + } catch { setToastMessage('❌ Error connecting to server'); } finally { - setIsSubmitting(false); + setIsLeetcodeSubmitting(false); setTimeout(() => setToastMessage(null), 4000); } }; - const handleTriggerSync = async () => { - setIsSyncing(true); + const handleTriggerLeetcodeSync = async () => { + setIsLeetcodeSyncing(true); try { - const res = await fetch(getBackendUrl('/api/leetcode/sync'), { - method: 'POST' - }); + const res = await fetch(getBackendUrl('/api/leetcode/sync'), { method: 'POST' }); if (res.ok) { setToastMessage('⚡ 24h background sync started! Requests are spaced out by 3s to prevent rate limits.'); } - } catch (err) { + } catch { setToastMessage('❌ Failed to trigger sync'); } finally { - setIsSyncing(false); + setIsLeetcodeSyncing(false); setTimeout(() => setToastMessage(null), 5000); } }; - const filteredProfiles = profiles.filter((p) => { + // Convert File to Base64 Data URL for persistent viewing + const handleFileChange = (e: React.ChangeEvent) => { + if (e.target.files && e.target.files[0]) { + const file = e.target.files[0]; + setCertFile(file); + + const reader = new FileReader(); + reader.onload = (event) => { + if (event.target?.result) { + setCertFileDataUrl(event.target.result as string); + } + }; + reader.readAsDataURL(file); + } + }; + + const handleUploadCertification = (e: React.FormEvent) => { + e.preventDefault(); + if (!certTitle.trim() || !certIssuer.trim()) return; + + const formatDateToDDMMYYYY = (dateStr: string) => { + if (!dateStr) return new Date().toLocaleDateString('en-GB'); // DD/MM/YYYY + const parts = dateStr.split('-'); + if (parts.length === 3) { + const [y, m, d] = parts; + return `${d}/${m}/${y}`; + } + return dateStr; + }; + + const formattedDate = formatDateToDDMMYYYY(certIssueDate); + + const newCert: StudentCertification = { + id: `cert-${Date.now()}`, + studentName: user?.name || 'Verified RIT Student', + regNumber: user?.regNumber || '2117240070054', + title: certTitle.trim(), + domain: certDomain, + issuingBody: certIssuer.trim(), + issueDate: formattedDate, + credentialUrl: certCredentialUrl.trim() || undefined, + fileName: certFile ? certFile.name : 'Uploaded_Certificate.pdf', + fileDataUrl: certFileDataUrl || undefined, + status: 'Verified', + }; + + setCertifications(prev => [newCert, ...prev]); + setShowCertModal(false); + setCertTitle(''); + setCertIssuer(''); + setCertIssueDate(new Date().toISOString().split('T')[0]); + setCertCredentialUrl(''); + setCertFile(null); + setCertFileDataUrl(null); + setToastMessage('🎉 Certificate uploaded and stored successfully!'); + setTimeout(() => setToastMessage(null), 4000); + }; + + const handleDeleteCert = (id: string) => { + setCertifications(prev => prev.filter(c => c.id !== id)); + setToastMessage('🗑️ Certificate removed'); + setTimeout(() => setToastMessage(null), 3000); + }; + + // ───────────────────────────────────────────────────────────── + // Computed Data & Filtering + // ───────────────────────────────────────────────────────────── + + // LeetCode Filtered + const filteredLeetcode = leetcodeProfiles.filter(p => { const matchesSearch = p.studentName.toLowerCase().includes(searchQuery.toLowerCase()) || p.leetcodeUsername.toLowerCase().includes(searchQuery.toLowerCase()); @@ -150,9 +527,70 @@ export default function LeetcodeLeaderboard() { const matchesYear = selectedYear === 'All' || p.year === selectedYear; return matchesSearch && matchesDept && matchesYear; }); + const totalLcCampusSolved = leetcodeProfiles.reduce((acc, curr) => acc + (curr.totalSolved || 0), 0); + const topLcProfile = leetcodeProfiles.length > 0 ? leetcodeProfiles[0] : null; - const totalCampusSolved = profiles.reduce((acc, curr) => acc + (curr.totalSolved || 0), 0); - const topProfile = profiles.length > 0 ? profiles[0] : null; + // Certifications Domain Counts + const certDomainCounts: Record = { + 'AI & Machine Learning': certifications.filter(c => c.domain === 'AI & Machine Learning').length, + 'Database & MongoDB': certifications.filter(c => c.domain === 'Database & MongoDB').length, + 'Cloud & DevOps': certifications.filter(c => c.domain === 'Cloud & DevOps').length, + 'Cybersecurity': certifications.filter(c => c.domain === 'Cybersecurity').length, + 'Full Stack & Web': certifications.filter(c => c.domain === 'Full Stack & Web').length, + 'Software Engineering': certifications.filter(c => c.domain === 'Software Engineering').length, + }; + + const filteredCertifications = certifications.filter(c => { + const matchesSearch = + c.title.toLowerCase().includes(searchQuery.toLowerCase()) || + c.issuingBody.toLowerCase().includes(searchQuery.toLowerCase()) || + c.domain.toLowerCase().includes(searchQuery.toLowerCase()); + + if (selectedDomainFilter === 'ALL') return matchesSearch; + return matchesSearch && c.domain === selectedDomainFilter; + }); + + // ───────────────────────────────────────────────────────────── + // Unauthenticated Guard Screen + // ───────────────────────────────────────────────────────────── + if (!isAuthenticated) { + return ( +
+ +
+ +
+
+
+ + Verified Student Portal +
+

+ Student Sign In Required +

+

+ RIT Student Leaderboard (LeetCode & Verified Skill Certifications Vault) are accessible exclusively to RIT students. Sign in with your student IMS credentials or Google account. +

+
+ +
+ +
+
+
+ ); + } return (
@@ -163,7 +601,7 @@ export default function LeetcodeLeaderboard() { initial={{ opacity: 0, y: -20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -20 }} - className="fixed top-6 right-6 z-50 px-5 py-3 rounded-xl bg-white border border-[#E9E5EE] backdrop-blur-md text-[#1A0B2E] font-bold shadow-2xl flex items-center gap-2" + className="fixed top-6 right-6 z-50 px-5 py-3 rounded-2xl bg-slate-900 border border-white/10 text-white font-bold shadow-2xl flex items-center gap-2 text-xs sm:text-sm" > {toastMessage} @@ -171,475 +609,514 @@ export default function LeetcodeLeaderboard() {
- {/* Banner Header */} -
+ + {/* ─── Top Header Banner ──────────────────────────────────────────────── */} +
-
+
-
- - RIT LeetCode Arena +
+ + RIT Student Competency Ecosystem
-

- Campus LeetCode Leaderboard +

+ Student Leaderboard

-

- Track top problem solvers across departments. Automatically updated every 24 hours with rate-limited, spaced background sync. +

+ Track campus competitive programming rankings across LeetCode, and showcase domain-verified student skill certifications.

-
- - -
+ {/* User Session Info Pill */} + {user && ( +
+
+ {user.name.charAt(0).toUpperCase()} +
+
+
+

{user.name}

+ +
+

+ {user.regNumber || '2117240070054'} · {user.department || 'RIT'} +

+
+
+ )}
- {/* Quick Metrics Bar */} -
-
- - #1 Top Solver - -

- {topProfile ? `${topProfile.studentName}` : 'N/A'} -

-

- {topProfile ? `${topProfile.totalSolved} Problems Solved` : ''} -

-
- -
- - Total Campus Solved - -

- {totalCampusSolved.toLocaleString()} -

-

Problems across all coders

-
- -
- - Active Coders - -

- {profiles.length} -

-

Registered RIT coders

-
- -
- - Sync Frequency - -

- Every 24 Hours -

-

3s spaced rate-limiting

-
-
-
- - {/* Search & Filter Controls */} -
-
- - setSearchQuery(e.target.value)} - className="w-full pl-10 pr-4 py-2.5 rounded-xl bg-[#F8FAFC] border border-[#E2E8F0] text-sm text-[#1E293B] placeholder-slate-400 focus:outline-none focus:border-[#F97316] transition-colors" - /> -
- -
-
- - -
- - + + LeetCode Leaderboard + + {leetcodeProfiles.length || 0} + + + +
- {/* Leaderboard Table */} -
- {loading ? ( -
- -

Fetching LeetCode rankings...

-
- ) : filteredProfiles.length === 0 ? ( -
- -

No coders found

-

Be the first to register your LeetCode handle!

- -
- ) : ( - <> - {/* Mobile Card View (Small Screens) */} -
- {filteredProfiles.map((p, index) => { - const rank = index + 1; - const total = p.totalSolved || 1; - const easyPct = Math.round(((p.easySolved || 0) / total) * 100); - const medPct = Math.round(((p.mediumSolved || 0) / total) * 100); - const hardPct = Math.round(((p.hardSolved || 0) / total) * 100); - - return ( - -
-
- {rank === 1 ? ( - 🥇 - ) : rank === 2 ? ( - 🥈 - ) : rank === 3 ? ( - 🥉 - ) : ( - - #{rank} - - )} - -
-

{p.studentName}

-

@{p.leetcodeUsername}

-
-
- -
-

{p.totalSolved}

-

Solved

-
-
- -
-
- - {p.department} - - {p.year} -
- - - LeetCode Profile - -
- - {/* Progress Bar */} -
-
- Easy: {p.easySolved} - Med: {p.mediumSolved} - Hard: {p.hardSolved} -
-
-
-
-
-
-
- - ); - })} + {/* ───────────────────────────────────────────────────────────────────── */} + {/* TAB 1: LEETCODE LEADERBOARD */} + {/* ───────────────────────────────────────────────────────────────────── */} + {activeMainTab === 'leetcode' && ( +
+ {/* Quick Metrics Bar */} +
+
+ + #1 Top Solver + +

+ {topLcProfile ? topLcProfile.studentName : 'N/A'} +

+

+ {topLcProfile ? `${topLcProfile.totalSolved} Solved` : ''} +

- {/* Desktop Table View (Medium Screens & Up) */} -
- - - - - - - - - - - - - - {filteredProfiles.map((p, index) => { - const rank = index + 1; - const total = p.totalSolved || 1; - const easyPct = Math.round(((p.easySolved || 0) / total) * 100); - const medPct = Math.round(((p.mediumSolved || 0) / total) * 100); - const hardPct = Math.round(((p.hardSolved || 0) / total) * 100); +
+ + Total Campus Solved + +

+ {totalLcCampusSolved.toLocaleString()} +

+

Problems across all coders

+
- return ( - - {/* Rank */} - +
+ + Active Coders + +

+ {leetcodeProfiles.length} +

+

Registered RIT coders

+
- {/* Student Info */} - +
+ + Sync Frequency + +

+ Every 24 Hours +

+

3s rate-limited

+
+ - {/* Dept & Year */} - + {/* Filter & Action Bar */} +
+
+ + setSearchQuery(e.target.value)} + className="w-full pl-10 pr-4 py-2.5 rounded-xl bg-[#F8FAFC] border border-[#E2E8F0] text-sm text-[#1E293B] placeholder-slate-400 focus:outline-none focus:border-[#F97316]" + /> +
- {/* Total Solved */} -
+
+
+ + +
- {/* Problem Breakdown Bars */} -
+ - {/* LeetCode Global Rank */} - - - ); - })} - -
RankStudentDept & YearTotal SolvedProblems BreakdownLeetCode RankProfile
- {rank === 1 ? ( -
- 🥇 -
- ) : rank === 2 ? ( -
- 🥈 -
- ) : rank === 3 ? ( -
- 🥉 -
- ) : ( - #{rank} - )} -
-
-

- {p.studentName} -

-

- @{p.leetcodeUsername} -

-
-
- - {p.department} - - {p.year} - - - {p.totalSolved} - - -
-
- Easy: {p.easySolved} - Med: {p.mediumSolved} - Hard: {p.hardSolved} -
-
-
-
-
-
-
-
- {p.ranking > 0 ? ( - - #{p.ranking.toLocaleString()} + + + + + + + {/* LeetCode Table */} +
+ {leetcodeLoading ? ( +
+ +

Fetching LeetCode rankings...

+
+ ) : filteredLeetcode.length === 0 ? ( +
+ +

No coders found

+

Be the first to register your LeetCode handle!

+
+ ) : ( +
+ + + + + + + + + + + + + {filteredLeetcode.map((p, index) => { + const rank = index + 1; + return ( + + + + + {p.year} + + + + + + ); + })} + +
RankStudentDept / YearProblems SolvedBreakdownProfile
+ {rank === 1 ? '🥇' : rank === 2 ? '🥈' : rank === 3 ? '🥉' : `#${rank}`} + +

{p.studentName}

+

@{p.leetcodeUsername}

+
+ + {p.department} - ) : ( - - - )} - + + {p.totalSolved} + + +
+ {p.easySolved}E + {p.mediumSolved}M + {p.hardSolved}H +
+
+ + View + +
+
+ )} +
+ + )} - {/* Profile Link */} -
- - - -
+ {/* ───────────────────────────────────────────────────────────────────── */} + {/* TAB 2: SKILL CERTIFICATIONS (WITH DOMAIN SELECTOR DROPDOWN & PREVIEW) */} + {/* ───────────────────────────────────────────────────────────────────── */} + {activeMainTab === 'certifications' && ( +
+ + {/* Domain Certificate Counters */} +
+
+
+ +
+

{certDomainCounts['AI & Machine Learning']}

+

AI & ML

- - )} -
+ +
+
+ +
+

{certDomainCounts['Database & MongoDB']}

+

Database & MongoDB

+
+ +
+
+ +
+

{certDomainCounts['Cloud & DevOps']}

+

Cloud & DevOps

+
+ +
+
+ +
+

{certDomainCounts['Cybersecurity']}

+

Cybersecurity

+
+ +
+
+ +
+

{certDomainCounts['Full Stack & Web']}

+

Full Stack & Web

+
+ +
+
+ +
+

{certifications.length}

+

Total Certs

+
+
+ + {/* Clean Dropdown Bar (Replaced long horizontal scrolling pills per user request) */} +
+ + {/* Custom Animated Floating Domain Dropdown (Matching Figure 2 Navbar Style) */} +
+ + Domain: + + +
+ + {/* Action Inputs & Upload */} +
+
+ + setSearchQuery(e.target.value)} + className="w-full pl-10 pr-4 py-2.5 rounded-xl bg-[#F8FAFC] border border-[#E2E8F0] text-xs text-[#1E293B] focus:outline-none focus:border-[#F97316]" + /> +
+ + +
+
+ + {/* Certificate Cards Grid */} +
+ {filteredCertifications.length === 0 ? ( +
+ +

No certificates found in this domain

+

Upload your industry credentials to build your student proficiency profile!

+
+ ) : ( + filteredCertifications.map((cert) => ( + +
+
+ + {cert.domain} + + +
+ {cert.status} +
+
+ +
+

+ {cert.title} +

+

+ Issuer: {cert.issuingBody} +

+
+ +
+ Issued: {cert.issueDate} + Reg: {cert.regNumber} +
+
+ +
+ {/* Prominent View Certificate Button (Opens Lightbox Viewer for both files & links) */} + + + +
+
+ )) + )} +
+
+ )}
- {/* Registration Modal */} + {/* ───────────────────────────────────────────────────────────────────── */} + {/* MODAL 1: REGISTER LEETCODE HANDLE */} + {/* ───────────────────────────────────────────────────────────────────── */} - {showModal && ( -
+ {showLeetcodeModal && ( +
-
-
- -
-
-

Register LeetCode Handle

-

Join the RIT campus coding rankings

-
-
+

+ Register LeetCode Handle +

+

Connect your LeetCode username to RIT campus rankings.

-
+
- + setStudentName(e.target.value)} - className="w-full px-4 py-2.5 rounded-xl bg-[#F8FAFC] border border-[#E2E8F0] text-sm text-[#1E293B] focus:outline-none focus:border-[#F97316]" + value={lcStudentName} + onChange={(e) => setLcStudentName(e.target.value)} + placeholder="e.g. S DEVESH" + className="w-full px-3.5 py-2.5 rounded-xl border border-[#E2E8F0] text-sm text-[#1E293B] focus:outline-none focus:border-[#F97316]" />
- + setLeetcodeUsername(e.target.value)} - className="w-full px-4 py-2.5 rounded-xl bg-[#F8FAFC] border border-[#E2E8F0] text-sm text-[#1E293B] focus:outline-none focus:border-[#F97316]" + value={lcUsername} + onChange={(e) => setLcUsername(e.target.value)} + placeholder="e.g. devesh_rit" + className="w-full px-3.5 py-2.5 rounded-xl border border-[#E2E8F0] text-sm text-[#1E293B] focus:outline-none focus:border-[#F97316]" /> -

We will immediately fetch your problem stats from LeetCode.

- - + +
- - + +
-
- +
@@ -647,6 +1124,229 @@ export default function LeetcodeLeaderboard() {
)} + + {/* ───────────────────────────────────────────────────────────────────── */} + {/* MODAL 2: UPLOAD SKILL CERTIFICATION FORM */} + {/* ───────────────────────────────────────────────────────────────────── */} + + {showCertModal && ( +
+ + + +
+ +

+ Upload Skill Certification +

+
+

Upload industry certificates & credentials to store & update domain badge counts.

+ +
+
+ + setCertTitle(e.target.value)} + placeholder="e.g. MongoDB Certified Associate Developer" + className="w-full px-3.5 py-2.5 rounded-xl border border-[#E2E8F0] text-sm text-[#1E293B] focus:outline-none focus:border-[#F97316]" + /> +
+ +
+
+ + setCertDomain(val as CertDomain)} + options={DOMAIN_FORM_OPTIONS} + /> +
+ +
+ + setCertIssuer(e.target.value)} + placeholder="e.g. MongoDB, Coursera, AWS" + className="w-full px-3.5 py-2.5 rounded-xl border border-[#E2E8F0] text-xs text-[#1E293B] focus:outline-none focus:border-[#F97316]" + /> +
+
+ +
+
+ + setCertIssueDate(e.target.value)} + className="w-full px-3.5 py-2.5 rounded-xl border border-[#E2E8F0] text-xs font-bold text-[#1E293B] focus:outline-none focus:border-[#F97316] bg-white cursor-pointer" + /> +
+ +
+ + setCertCredentialUrl(e.target.value)} + placeholder="https://..." + className="w-full px-3.5 py-2.5 rounded-xl border border-[#E2E8F0] text-xs text-[#1E293B] focus:outline-none focus:border-[#F97316]" + /> +
+
+ + {/* File Upload Zone (Reads & Stores File as Base64 Data URL) */} +
+ +
+ +

+ {certFile ? `Selected: ${certFile.name}` : 'Upload certificate image or document'} +

+ {certFile && ( +

+ ✓ Stored & ready for viewing +

+ )} + + +
+
+ +
+ +
+
+
+
+ )} +
+ + {/* ───────────────────────────────────────────────────────────────────── */} + {/* MODAL 3: CERTIFICATE VIEW LIGHTBOX / PREVIEW MODAL */} + {/* ───────────────────────────────────────────────────────────────────── */} + + {viewingCert && ( +
+ + + +
+ +
+

+ {viewingCert.title} +

+

Issued by {viewingCert.issuingBody} · Reg: {viewingCert.regNumber}

+
+
+ + {/* Certificate Media Display */} +
+ {viewingCert.fileDataUrl ? ( + viewingCert.fileDataUrl.startsWith('data:image/') || viewingCert.fileDataUrl.startsWith('data:application/pdf') === false ? ( + {viewingCert.title} + ) : ( +