Skip to content

Commit d79c354

Browse files
committed
Add Leaderboard page and integrate navigation from ProjectsPage
1 parent 6491b71 commit d79c354

3 files changed

Lines changed: 334 additions & 1 deletion

File tree

src/App.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import JourneyPage from './Page/JourneyPage.jsx';
2424
import AiCareer from './Page/AiCareer.jsx';
2525
import AIToolsHub from './Page/AIToolsHub.jsx';
2626
import AchieverJourneyPage from './components/AchievementJourney/IndividualJourney.js';
27+
import Leaderboard from './Page/Leaderboard.jsx';
2728
// Features - Opportunities Hub Pages
2829
import Opportunities from './Page/Opportunities.jsx';
2930
import Jobs from './Page/OpportunitiesHub/Jobs.jsx';
@@ -128,6 +129,7 @@ function App() {
128129
<Route path="/IndustryTrends" element={<IndustryTrends />} />
129130
<Route path="/AiCareer" element={<AiCareer />} />
130131
<Route path="/AIToolsHub" element={<AIToolsHub />} />
132+
<Route path="/leaderboard" element={<Leaderboard />} />
131133
{/* Features - Opportunities Hub Pages */}
132134
<Route path="/Opportunities" element={<Opportunities />} />
133135
<Route path="/Jobs" element={<Jobs />} />

src/Page/Leaderboard.jsx

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import React from 'react';
2+
import projectsData from '../DB/projects.json';
3+
import { ArrowLeft } from 'lucide-react';
4+
import { useNavigate } from 'react-router-dom';
5+
6+
// Calculate leaderboard data
7+
const leaderboard = projectsData
8+
.map((user) => ({
9+
username: user.github_username,
10+
avatar: user.Projects?.[0]?.maker_image || '',
11+
projectCount: user.Projects ? user.Projects.length : 0,
12+
}))
13+
.sort((a, b) => b.projectCount - a.projectCount);
14+
15+
const Leaderboard = () => {
16+
const navigate = useNavigate();
17+
18+
return (
19+
<div className="min-h-screen bg-gray-900 p-6 text-white">
20+
<div className="mb-8 flex items-center">
21+
<button
22+
className="flex items-center gap-2 rounded-full border border-white p-2 hover:bg-gray-700"
23+
onClick={() => navigate(-1)}
24+
>
25+
<ArrowLeft className="h-5 w-5" />
26+
<span className="hidden md:inline">Back</span>
27+
</button>
28+
<h1 className="ml-4 text-3xl font-bold">Top Project Builder Leaderboard</h1>
29+
</div>
30+
<div className="mb-8 text-center">
31+
<span className="text-lg font-semibold text-[#00a6fb]">Live Leaderboard (Global)</span>
32+
<span className="ml-2">🌍📊</span>
33+
</div>
34+
{/* Top 3 */}
35+
<div className="mb-10 flex flex-wrap justify-center gap-6">
36+
{leaderboard.slice(0, 3).map((user, idx) => (
37+
<div
38+
key={user.username}
39+
className="flex w-64 flex-col items-center rounded-xl border-2 border-[#00a6fb] bg-gray-800 p-6 shadow-lg"
40+
>
41+
<img
42+
src={user.avatar}
43+
alt={user.username}
44+
className="mb-2 h-20 w-20 rounded-full border-4 border-[#00a6fb]"
45+
/>
46+
<div className="mb-1 text-xl font-bold">{user.username}</div>
47+
<div className="mt-2 rounded-lg border border-gray-600 px-4 py-2 font-semibold text-[#00a6fb]">
48+
{user.projectCount} projects
49+
</div>
50+
</div>
51+
))}
52+
</div>
53+
{/* Table for rest */}
54+
<div className="overflow-x-auto">
55+
<table className="min-w-full rounded-lg border border-gray-700 bg-gray-800">
56+
<thead>
57+
<tr>
58+
<th className="border-b border-gray-700 px-4 py-2">Rank</th>
59+
<th className="border-b border-gray-700 px-4 py-2">Builder Name</th>
60+
<th className="border-b border-gray-700 px-4 py-2">Project Built</th>
61+
</tr>
62+
</thead>
63+
<tbody>
64+
{leaderboard.slice(3).map((user, idx) => (
65+
<tr key={user.username} className="text-center">
66+
<td className="border-b border-gray-700 px-4 py-2 font-bold">#{idx + 4}</td>
67+
<td className="flex items-center justify-center gap-2 border-b border-gray-700 px-4 py-2">
68+
<img src={user.avatar} alt={user.username} className="h-8 w-8 rounded-full" />
69+
{user.username}
70+
</td>
71+
<td className="border-b border-gray-700 px-4 py-2">{user.projectCount} projects</td>
72+
</tr>
73+
))}
74+
</tbody>
75+
</table>
76+
</div>
77+
</div>
78+
);
79+
};
80+
81+
export default Leaderboard;

src/Page/ProjectShowcase.jsx

Lines changed: 251 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Footer } from '../components/Footer/Footer';
66
import styled from 'styled-components';
77
// import Marquee from 'react-fast-marquee';
88
import { ArrowLeft } from 'lucide-react';
9+
import { useNavigate } from 'react-router-dom';
910

1011
const StyledButton = styled.button`
1112
cursor: pointer;
@@ -150,6 +151,8 @@ const ProjectsPage = () => {
150151

151152
const projectsPerPage = 9;
152153

154+
const navigate = useNavigate();
155+
153156
// ...shuffleArray and useEffect for loading projects...
154157

155158
useEffect(() => {
@@ -382,7 +385,10 @@ const ProjectsPage = () => {
382385
</div>
383386
</StyledWrapper>
384387
</div>
385-
<div className="my-8"></div>
388+
<div className="my-6"></div>
389+
<div className="mb-6 flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
390+
<StyledLeaderboardButton onClick={() => navigate('/leaderboard')} />
391+
</div>
386392
<div className="mb-6 flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
387393
<input
388394
type="text"
@@ -532,6 +538,41 @@ const LoadingSkeleton = () => {
532538
);
533539
};
534540

541+
const StyledLeaderboardButton = ({ onClick }) => (
542+
<StyledWrapper>
543+
<button type="button" className="button" onClick={onClick}>
544+
<span className="fold" />
545+
<div className="points_wrapper">
546+
<i className="point" />
547+
<i className="point" />
548+
<i className="point" />
549+
<i className="point" />
550+
<i className="point" />
551+
<i className="point" />
552+
<i className="point" />
553+
<i className="point" />
554+
<i className="point" />
555+
<i className="point" />
556+
</div>
557+
<span className="inner">
558+
<svg
559+
className="icon"
560+
fill="none"
561+
stroke="currentColor"
562+
viewBox="0 0 24 24"
563+
xmlns="http://www.w3.org/2000/svg"
564+
strokeLinecap="round"
565+
strokeLinejoin="round"
566+
strokeWidth="2.5"
567+
>
568+
<polyline points="13.18 1.37 13.18 9.64 21.45 9.64 10.82 22.63 10.82 14.36 2.55 14.36 13.18 1.37" />
569+
</svg>
570+
Top Project Builder Leaderboard
571+
</span>
572+
</button>
573+
</StyledWrapper>
574+
);
575+
535576
const StyledWrapper = styled.div`
536577
.relative {
537578
position: relative;
@@ -721,6 +762,215 @@ const StyledWrapper = styled.div`
721762
.style-VG2eL {
722763
animation-delay: 1.7s;
723764
}
765+
766+
// for the button styles
767+
768+
.button {
769+
--h-button: 48px;
770+
--w-button: 102px;
771+
--round: 0.75rem;
772+
cursor: pointer;
773+
position: relative;
774+
display: inline-flex;
775+
align-items: center;
776+
justify-content: center;
777+
overflow: hidden;
778+
transition: all 0.25s ease;
779+
background:
780+
radial-gradient(65.28% 65.28% at 50% 100%, rgba(223, 113, 255, 0.8) 0%, rgba(223, 113, 255, 0) 100%),
781+
linear-gradient(0deg, #7a5af8, #7a5af8);
782+
border-radius: var(--round);
783+
border: none;
784+
outline: none;
785+
padding: 12px 18px;
786+
}
787+
.button::before,
788+
.button::after {
789+
content: '';
790+
position: absolute;
791+
inset: var(--space);
792+
transition: all 0.5s ease-in-out;
793+
border-radius: calc(var(--round) - var(--space));
794+
z-index: 0;
795+
}
796+
.button::before {
797+
--space: 1px;
798+
background: linear-gradient(177.95deg, rgba(255, 255, 255, 0.19) 0%, rgba(255, 255, 255, 0) 100%);
799+
}
800+
.button::after {
801+
--space: 2px;
802+
background:
803+
radial-gradient(65.28% 65.28% at 50% 100%, rgba(223, 113, 255, 0.8) 0%, rgba(223, 113, 255, 0) 100%),
804+
linear-gradient(0deg, #7a5af8, #7a5af8);
805+
}
806+
.button:active {
807+
transform: scale(0.95);
808+
}
809+
810+
.fold {
811+
z-index: 1;
812+
position: absolute;
813+
top: 0;
814+
right: 0;
815+
height: 1rem;
816+
width: 1rem;
817+
display: inline-block;
818+
transition: all 0.5s ease-in-out;
819+
background: radial-gradient(100% 75% at 55%, rgba(223, 113, 255, 0.8) 0%, rgba(223, 113, 255, 0) 100%);
820+
box-shadow: 0 0 3px black;
821+
border-bottom-left-radius: 0.5rem;
822+
border-top-right-radius: var(--round);
823+
}
824+
.fold::after {
825+
content: '';
826+
position: absolute;
827+
top: 0;
828+
right: 0;
829+
width: 150%;
830+
height: 150%;
831+
transform: rotate(45deg) translateX(0%) translateY(-18px);
832+
background-color: #e8e8e8;
833+
pointer-events: none;
834+
}
835+
.button:hover .fold {
836+
margin-top: -1rem;
837+
margin-right: -1rem;
838+
}
839+
840+
.points_wrapper {
841+
overflow: hidden;
842+
width: 100%;
843+
height: 100%;
844+
pointer-events: none;
845+
position: absolute;
846+
z-index: 1;
847+
}
848+
849+
.points_wrapper .point {
850+
bottom: -10px;
851+
position: absolute;
852+
animation: floating-points infinite ease-in-out;
853+
pointer-events: none;
854+
width: 2px;
855+
height: 2px;
856+
background-color: #fff;
857+
border-radius: 9999px;
858+
}
859+
@keyframes floating-points {
860+
0% {
861+
transform: translateY(0);
862+
}
863+
85% {
864+
opacity: 0;
865+
}
866+
100% {
867+
transform: translateY(-55px);
868+
opacity: 0;
869+
}
870+
}
871+
.points_wrapper .point:nth-child(1) {
872+
left: 10%;
873+
opacity: 1;
874+
animation-duration: 2.35s;
875+
animation-delay: 0.2s;
876+
}
877+
.points_wrapper .point:nth-child(2) {
878+
left: 30%;
879+
opacity: 0.7;
880+
animation-duration: 2.5s;
881+
animation-delay: 0.5s;
882+
}
883+
.points_wrapper .point:nth-child(3) {
884+
left: 25%;
885+
opacity: 0.8;
886+
animation-duration: 2.2s;
887+
animation-delay: 0.1s;
888+
}
889+
.points_wrapper .point:nth-child(4) {
890+
left: 44%;
891+
opacity: 0.6;
892+
animation-duration: 2.05s;
893+
}
894+
.points_wrapper .point:nth-child(5) {
895+
left: 50%;
896+
opacity: 1;
897+
animation-duration: 1.9s;
898+
}
899+
.points_wrapper .point:nth-child(6) {
900+
left: 75%;
901+
opacity: 0.5;
902+
animation-duration: 1.5s;
903+
animation-delay: 1.5s;
904+
}
905+
.points_wrapper .point:nth-child(7) {
906+
left: 88%;
907+
opacity: 0.9;
908+
animation-duration: 2.2s;
909+
animation-delay: 0.2s;
910+
}
911+
.points_wrapper .point:nth-child(8) {
912+
left: 58%;
913+
opacity: 0.8;
914+
animation-duration: 2.25s;
915+
animation-delay: 0.2s;
916+
}
917+
.points_wrapper .point:nth-child(9) {
918+
left: 98%;
919+
opacity: 0.6;
920+
animation-duration: 2.6s;
921+
animation-delay: 0.1s;
922+
}
923+
.points_wrapper .point:nth-child(10) {
924+
left: 65%;
925+
opacity: 1;
926+
animation-duration: 2.5s;
927+
animation-delay: 0.2s;
928+
}
929+
930+
.inner {
931+
z-index: 2;
932+
gap: 6px;
933+
position: relative;
934+
width: auto;
935+
white-space: nowrap;
936+
color: white;
937+
display: inline-flex;
938+
align-items: center;
939+
justify-content: center;
940+
font-size: 16px;
941+
font-weight: 500;
942+
line-height: 1.5;
943+
transition: color 0.2s ease-in-out;
944+
}
945+
946+
.inner svg.icon {
947+
width: 18px;
948+
height: 18px;
949+
transition: fill 0.1s linear;
950+
}
951+
952+
.button:focus svg.icon {
953+
fill: white;
954+
}
955+
.button:hover svg.icon {
956+
fill: transparent;
957+
animation:
958+
dasharray 1s linear forwards,
959+
filled 0.1s linear forwards 0.95s;
960+
}
961+
@keyframes dasharray {
962+
from {
963+
stroke-dasharray: 0 0 0 0;
964+
}
965+
to {
966+
stroke-dasharray: 68 68 0 0;
967+
}
968+
}
969+
@keyframes filled {
970+
to {
971+
fill: white;
972+
}
973+
}
724974
`;
725975

726976
export default ProjectsPage;

0 commit comments

Comments
 (0)