From 66300552692528101bb2ff302ce82ce8bfa54867 Mon Sep 17 00:00:00 2001 From: KGFCH2 Date: Sun, 31 May 2026 21:19:10 +0530 Subject: [PATCH] feat: add reusable SkeletonLoader and integrate in ContestCodeforcesPage --- .../src/components/shared/SkeletonLoader.jsx | 49 ++++++++ frontend/src/pages/ContestCodeforcesPage.jsx | 112 +++++++++++------- 2 files changed, 116 insertions(+), 45 deletions(-) create mode 100644 frontend/src/components/shared/SkeletonLoader.jsx diff --git a/frontend/src/components/shared/SkeletonLoader.jsx b/frontend/src/components/shared/SkeletonLoader.jsx new file mode 100644 index 0000000..b8f8604 --- /dev/null +++ b/frontend/src/components/shared/SkeletonLoader.jsx @@ -0,0 +1,49 @@ +import React from 'react'; + +/** + * Reusable Skeleton Loader component to provide visual loading feedback. + * @param {string} className - Optional Tailwind utility classes. + */ +export function Skeleton({ className = "" }) { + return ( +
+ ); +} + +/** + * Skeleton loader designed for loading table or list rows. + */ +export function TableRowSkeleton({ rows = 5 }) { + return ( +
+ {Array.from({ length: rows }).map((_, i) => ( +
+ + + + + +
+ ))} +
+ ); +} + +/** + * Skeleton loader designed for dashboard stat cards. + */ +export function CardSkeleton({ cards = 3 }) { + return ( +
+ {Array.from({ length: cards }).map((_, i) => ( +
+ + + +
+ ))} +
+ ); +} + +export default Skeleton; diff --git a/frontend/src/pages/ContestCodeforcesPage.jsx b/frontend/src/pages/ContestCodeforcesPage.jsx index 634cfac..57a401e 100644 --- a/frontend/src/pages/ContestCodeforcesPage.jsx +++ b/frontend/src/pages/ContestCodeforcesPage.jsx @@ -1,7 +1,16 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; +import { CardSkeleton } from "../components/shared/SkeletonLoader"; export default function ContestCodeforcesPage() { const [selectedDivision, setSelectedDivision] = useState("all"); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const timer = setTimeout(() => { + setLoading(false); + }, 800); + return () => clearTimeout(timer); + }, []); const contestSolutions = [ { @@ -33,6 +42,10 @@ export default function ContestCodeforcesPage() { }, ]; + const filteredContests = contestSolutions.filter( + (c) => selectedDivision === "all" || c.division === selectedDivision + ); + return (
{/* Hero Section */} @@ -82,7 +95,7 @@ export default function ContestCodeforcesPage() { {["all", "Div. 1", "Div. 2", "Div. 3", "Div. 4", "Educational", "Global"].map((div) => ( -
- ))} -
+ + + ))} + + )}