Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions client/src/components/courses/CoursesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from "../../utils/api";
import { Course, CoursesResponse } from "../../types";
import { isAuthenticated } from "../../utils/auth";
import { CardsGridSkeleton } from "../skeletons/CardsGridSkeleton";

const CoursesPage: React.FC = () => {
const [courses, setCourses] = useState<Course[]>([]);
Expand Down Expand Up @@ -277,8 +278,8 @@ const CoursesPage: React.FC = () => {

{/* Loading State */}
{loading && (
<div className="flex justify-center items-center py-12">
<div className="w-8 h-8 border-4 border-alien-green border-t-transparent rounded-full animate-spin"></div>
<div className="py-6">
<CardsGridSkeleton count={9} />
</div>
)}

Expand Down
77 changes: 73 additions & 4 deletions client/src/components/discussions/DiscussionDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { DISCUSSION_CATEGORIES } from "../../types/discussions";
import { getUserProfile } from "../../utils/api";
import MentionInput from "./MentionInput";
import { useSocket } from "../../contexts/SocketContext";
import { Skeleton } from "../ui/Skeleton";

const DiscussionDetailPage: React.FC = () => {
const { id } = useParams<{ id: string }>();
Expand Down Expand Up @@ -527,10 +528,78 @@ const DiscussionDetailPage: React.FC = () => {

if (loading) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<div className="w-16 h-16 border-4 border-alien-green border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
<p className="text-alien-green font-alien">Loading discussion...</p>
<div className="min-h-screen py-8 px-4">
<div className="max-w-4xl mx-auto">
<div className="flex items-center mb-8">
<div className="flex items-center space-x-2 text-gray-400 mr-6">
<Skeleton className="h-5 w-5 bg-white/[0.06]" />
<Skeleton className="h-4 w-40 bg-white/[0.06]" />
</div>
</div>

<div className="smoke-card p-8 mb-8 relative smoke-effect">
<div className="flex items-start space-x-6">
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between mb-4 gap-4">
<Skeleton className="h-7 w-2/3 bg-white/[0.07]" />
<Skeleton className="h-6 w-20 rounded-full bg-white/[0.06]" />
</div>

<div className="flex flex-wrap items-center gap-4 mb-6">
<Skeleton className="h-4 w-32 bg-white/[0.06]" />
<Skeleton className="h-4 w-24 bg-white/[0.06]" />
<Skeleton className="h-4 w-24 bg-white/[0.06]" />
</div>

<div className="space-y-3 mb-6">
<Skeleton className="h-4 w-full bg-white/[0.06]" />
<Skeleton className="h-4 w-11/12 bg-white/[0.06]" />
<Skeleton className="h-4 w-4/5 bg-white/[0.06]" />
</div>

<div className="flex flex-wrap gap-2">
<Skeleton className="h-6 w-20 rounded-full bg-white/[0.06]" />
<Skeleton className="h-6 w-24 rounded-full bg-white/[0.06]" />
<Skeleton className="h-6 w-16 rounded-full bg-white/[0.06]" />
</div>
</div>
</div>
</div>

<div className="smoke-card p-6 mt-10 mb-5 smoke-effect">
<Skeleton className="h-5 w-28 bg-white/[0.07] mb-4" />
<div className="space-y-4">
<Skeleton className="h-32 w-full rounded-lg bg-white/[0.05]" />
<div className="flex items-center justify-between">
<Skeleton className="h-10 w-44 rounded bg-white/[0.05]" />
<Skeleton className="h-10 w-32 rounded bg-white/[0.06]" />
</div>
</div>
</div>

<div className="space-y-6">
<Skeleton className="h-6 w-40 bg-white/[0.07]" />
{Array.from({ length: 3 }).map((_, i) => (
<div
key={i}
className="smoke-card p-6 relative smoke-effect space-y-4"
>
<div className="space-y-3">
<Skeleton className="h-4 w-full bg-white/[0.06]" />
<Skeleton className="h-4 w-5/6 bg-white/[0.06]" />
<Skeleton className="h-4 w-2/3 bg-white/[0.05]" />
</div>

<div className="flex items-center justify-between">
<Skeleton className="h-4 w-28 bg-white/[0.05]" />
<div className="flex items-center space-x-3">
<Skeleton className="h-4 w-20 bg-white/[0.05]" />
<Skeleton className="h-4 w-16 bg-white/[0.05]" />
</div>
</div>
</div>
))}
</div>
</div>
</div>
);
Expand Down
25 changes: 21 additions & 4 deletions client/src/components/discussions/DiscussionsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { getDiscussions, getPopularTags } from "../../utils/api";
import type { Discussion } from "../../types/discussions";
import { DISCUSSION_CATEGORIES } from "../../types/discussions";
import { useDebounce } from "../../hooks/useDebounce";
import { ListSkeleton } from "../skeletons/ListSkeleton";

const DiscussionsPage: React.FC = () => {
const [searchParams, setSearchParams] = useSearchParams();
Expand Down Expand Up @@ -176,10 +177,26 @@ const DiscussionsPage: React.FC = () => {

if (loading && discussions.length === 0) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<div className="w-16 h-16 border-4 border-alien-green border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
<p className="text-alien-green font-alien">Loading discussions...</p>
<div className="min-h-screen py-8 px-4">
<div className="max-w-5xl mx-auto">
<div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4">
<div>
<h1 className="text-4xl font-alien font-bold glow-text mb-2">
Discussion Forum
</h1>
<p className="text-gray-400">
Ask questions, share knowledge, and help your peers
</p>
</div>
<Link
to="/discussions/new"
className="alien-button flex items-center space-x-2 px-6 py-3"
>
<Plus size={20} />
<span>Ask Question</span>
</Link>
</div>
<ListSkeleton count={10} />
</div>
</div>
);
Expand Down
4 changes: 4 additions & 0 deletions client/src/components/layout/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import AdminNavLink from "../admin/AdminNavLink";
import { getUserProfile, logout } from "../../utils/api";
import type { User as UserType } from "../../types";
import { removeAuthToken } from "../../utils/auth";
import NotificationDropdown from "../ui/NotificationDropdown";

interface NavbarProps {
authenticated: boolean | null;
Expand Down Expand Up @@ -361,6 +362,9 @@ export default function ResponsiveNavbar({
)}
</div>

{/* ✅ Notifications */}
{authenticated && <NotificationDropdown />}

{/* Auth area (preserved) */}
{authenticated ? (
<div className="relative" ref={profileRef}>
Expand Down
17 changes: 13 additions & 4 deletions client/src/components/resources/EbooksPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { getEbooks } from "../../utils/api";
import { EbookItem } from "../../types";
import { isAuthenticated } from "../../utils/auth";
import { useDebounce } from "../../hooks/useDebounce";
import { CardsGridSkeleton } from "../skeletons/CardsGridSkeleton";

// Memoized Ebook Card Component
const EbookCard = React.memo(({ ebook }: { ebook: EbookItem }) => (
Expand Down Expand Up @@ -228,10 +229,18 @@ const EbooksPage: React.FC = () => {

if (isInitialLoad && loading) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<div className="w-16 h-16 border-4 border-alien-green border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
<p className="text-alien-green font-alien">Loading E-books...</p>
<div className="min-h-screen py-8 px-4">
<div className="max-w-7xl mx-auto">
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-8 gap-4">
<div>
<h1 className="text-4xl font-alien font-bold glow-text mb-2">
E-book Library
</h1>
<p className="text-gray-400">Explore digital books and references</p>
</div>
</div>

<CardsGridSkeleton count={9} />
</div>
</div>
);
Expand Down
28 changes: 24 additions & 4 deletions client/src/components/resources/PDFsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { getPDFs } from "../../utils/api";
import { PDFItem } from "../../types";
import { isAuthenticated } from "../../utils/auth";
import { useDebounce } from "../../hooks/useDebounce";
import { CardsGridSkeleton } from "../skeletons/CardsGridSkeleton";

// Memoized PDF Card Component
const PDFCard = React.memo(({ pdf }: { pdf: PDFItem }) => (
Expand Down Expand Up @@ -228,10 +229,29 @@ const PDFsPage: React.FC = () => {

if (isInitialLoad && loading) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<div className="w-16 h-16 border-4 border-alien-green border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
<p className="text-alien-green font-alien">Loading PDFs...</p>
<div className="min-h-screen py-8 px-4">
<div className="max-w-7xl mx-auto">
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-8 gap-4">
<div>
<h1 className="text-4xl font-alien font-bold glow-text mb-2">
PDF Collection
</h1>
<p className="text-gray-400">
Discover and download academic resources
</p>
</div>
{isAuth && isAdmin && (
<Link
to="/upload?type=pdf"
className="bg-alien-green text-royal-black px-6 py-3 rounded-lg font-semibold hover:bg-alien-green/90 transition-colors duration-300 flex items-center space-x-2 shadow-alien-glow whitespace-nowrap"
>
<Plus size={20} />
<span className="hidden sm:inline">Upload PDF</span>
<span className="sm:hidden">Upload</span>
</Link>
)}
</div>
<CardsGridSkeleton count={9} />
</div>
</div>
);
Expand Down
7 changes: 4 additions & 3 deletions client/src/components/roadmaps/RoadmapPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { getRoadmaps, toggleRoadmapBookmark } from "../../utils/api";
import { Roadmap, RoadmapsResponse } from "../../types";
import SEO from "../seo/SEO";
import { isAuthenticated } from "../../utils/auth";
import { RoadmapSkeleton } from "../skeletons/RoadmapSkeleton";

const RoadmapsPage: React.FC = () => {
const [roadmaps, setRoadmaps] = useState<Roadmap[]>([]);
Expand Down Expand Up @@ -197,11 +198,11 @@ const RoadmapsPage: React.FC = () => {

{/* Loading State */}
{loading && (
<div className="flex justify-center items-center py-12">
<div className="w-8 h-8 border-4 border-alien-green border-t-transparent rounded-full animate-spin"></div>
<div className="py-6">
<RoadmapSkeleton count={6} />
</div>
)}

{/* Roadmaps Grid */}
{!loading && (
<>
Expand Down
26 changes: 26 additions & 0 deletions client/src/components/skeletons/CardsGridSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Skeleton } from "../ui/Skeleton";

export function CardsGridSkeleton({ count = 9 }: { count?: number }) {
return (
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{Array.from({ length: count }).map((_, i) => (
<div
key={i}
className="
rounded-2xl
border border-white/10
bg-[#0B0B0B]
p-4
"
>
<Skeleton className="h-40 w-full rounded-xl bg-[#121212]" />

<div className="mt-4 space-y-2">
<Skeleton className="h-5 w-3/4 bg-[#121212]" />
<Skeleton className="h-4 w-1/2 bg-[#121212]" />
</div>
</div>
))}
</div>
);
}
19 changes: 19 additions & 0 deletions client/src/components/skeletons/ListSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Skeleton } from "../ui/Skeleton";

export function ListSkeleton({ count = 10 }: { count?: number }) {
return (
<div className="space-y-3">
{Array.from({ length: count }).map((_, i) => (
<div key={i} className="rounded-xl border border-black/10 dark:border-white/10 p-4">
<div className="flex items-center gap-3">
<Skeleton className="h-10 w-10 rounded-full" />
<div className="flex-1 space-y-2">
<Skeleton className="h-4 w-3/5" />
<Skeleton className="h-3 w-2/5" />
</div>
</div>
</div>
))}
</div>
);
}
20 changes: 20 additions & 0 deletions client/src/components/skeletons/RoadmapSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Skeleton } from "../ui/Skeleton";

export function RoadmapSkeleton({ count = 6 }: { count?: number }) {
return (
<div className="space-y-4">
{Array.from({ length: count }).map((_, i) => (
<div key={i} className="rounded-xl border border-black/10 dark:border-white/10 p-4">
<div className="flex items-start gap-3">
<Skeleton className="h-10 w-10 rounded-full" />
<div className="flex-1 space-y-2">
<Skeleton className="h-5 w-2/3" />
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-5/6" />
</div>
</div>
</div>
))}
</div>
);
}
Loading