Skip to content

Commit 8690156

Browse files
committed
feat(frontend): add skeleton loader components, fix tailwindcss parsing issue by adding postcss config, closes issue 68
1 parent a5b873d commit 8690156

8 files changed

Lines changed: 552 additions & 14 deletions

File tree

frontend/postcss.config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
plugins: {
3+
tailwindcss: {},
4+
autoprefixer: {},
5+
},
6+
};
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
'use client';
2+
3+
import * as React from 'react';
4+
import { cn } from '@/lib/utils';
5+
import { Skeleton, SkeletonRegion, SkeletonChart } from '@/components/ui/Skeleton';
6+
7+
export interface AnalyticsSkeletonProps {
8+
className?: string;
9+
}
10+
11+
export function ProgressDashboardSkeleton({ className }: AnalyticsSkeletonProps) {
12+
return (
13+
<SkeletonRegion
14+
aria-label="Loading analytics dashboard"
15+
className={cn('space-y-6 p-6', className)}
16+
>
17+
<div className="flex items-center justify-between">
18+
<Skeleton className="h-7 w-48" />
19+
<Skeleton className="h-4 w-28" />
20+
</div>
21+
22+
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
23+
{Array.from({ length: 3 }).map((_, i) => (
24+
<div key={i} className="rounded-lg border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-700 dark:bg-slate-800">
25+
<Skeleton className="h-3 w-32" />
26+
<div className="mt-2 flex items-center justify-between">
27+
<Skeleton className="h-8 w-16" />
28+
<Skeleton className="h-3 w-2/5 rounded-full" />
29+
</div>
30+
</div>
31+
))}
32+
</div>
33+
34+
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
35+
<div className="col-span-1 lg:col-span-2 rounded-lg border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-700 dark:bg-slate-800">
36+
<Skeleton className="h-5 w-32" />
37+
<Skeleton className="mt-3 h-64 w-full rounded-lg" />
38+
</div>
39+
<div className="col-span-1 rounded-lg border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-700 dark:bg-slate-800">
40+
<Skeleton className="h-5 w-16" />
41+
<Skeleton className="mt-3 h-64 w-full rounded-lg" />
42+
</div>
43+
</div>
44+
45+
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
46+
{Array.from({ length: 2 }).map((_, i) => (
47+
<div key={i} className="rounded-lg border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-700 dark:bg-slate-800">
48+
<Skeleton className="h-5 w-24" />
49+
<div className="mt-3 space-y-3">
50+
{Array.from({ length: 3 }).map((_, j) => (
51+
<div key={j} className="flex items-center justify-between">
52+
<Skeleton className="h-4 w-32" />
53+
<Skeleton className="h-2 w-1/3 rounded-full" />
54+
</div>
55+
))}
56+
</div>
57+
</div>
58+
))}
59+
</div>
60+
</SkeletonRegion>
61+
);
62+
}
63+
64+
export function CompletionStatsSkeleton({ className }: AnalyticsSkeletonProps) {
65+
return (
66+
<SkeletonRegion
67+
aria-label="Loading completion stats"
68+
className={cn('space-y-6', className)}
69+
>
70+
<div className="rounded-lg border border-slate-200 bg-white p-6 shadow-sm dark:border-slate-700 dark:bg-slate-800">
71+
<Skeleton className="h-5 w-32" />
72+
<div className="mt-6 grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-6">
73+
{Array.from({ length: 6 }).map((_, i) => (
74+
<div key={i} className="text-center">
75+
<Skeleton className="mx-auto h-8 w-8 rounded" />
76+
<Skeleton className="mx-auto mt-2 h-7 w-12" />
77+
<Skeleton className="mx-auto mt-1 h-3 w-16" />
78+
</div>
79+
))}
80+
</div>
81+
</div>
82+
83+
<div className="rounded-lg border border-slate-200 bg-white p-6 shadow-sm dark:border-slate-700 dark:bg-slate-800">
84+
<Skeleton className="h-5 w-32" />
85+
<div className="mt-4 space-y-4">
86+
{Array.from({ length: 3 }).map((_, i) => (
87+
<div key={i} className="rounded-lg border border-slate-100 p-4 dark:border-slate-700">
88+
<div className="flex items-start justify-between">
89+
<div className="flex-1 space-y-1">
90+
<Skeleton className="h-4 w-48" />
91+
<Skeleton className="h-3 w-32" />
92+
</div>
93+
<Skeleton className="h-5 w-20 rounded-full" />
94+
</div>
95+
<Skeleton className="mt-3 h-2 w-full rounded-full" />
96+
<div className="mt-2 flex justify-between">
97+
<Skeleton className="h-3 w-24" />
98+
<Skeleton className="h-3 w-28" />
99+
</div>
100+
</div>
101+
))}
102+
</div>
103+
</div>
104+
105+
<div className="rounded-lg border border-slate-200 bg-white p-6 shadow-sm dark:border-slate-700 dark:bg-slate-800">
106+
<div className="flex items-center justify-between">
107+
<Skeleton className="h-5 w-24" />
108+
<div className="flex gap-2">
109+
{Array.from({ length: 3 }).map((_, i) => (
110+
<Skeleton key={i} className="h-7 w-16 rounded" />
111+
))}
112+
</div>
113+
</div>
114+
<div className="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
115+
{Array.from({ length: 3 }).map((_, i) => (
116+
<div key={i} className="rounded-lg border border-slate-100 p-4 dark:border-slate-700">
117+
<div className="flex gap-3">
118+
<Skeleton className="h-8 w-8 rounded" />
119+
<div className="flex-1 space-y-1">
120+
<Skeleton className="h-4 w-28" />
121+
<Skeleton className="h-3 w-full" />
122+
<Skeleton className="h-3 w-20" />
123+
</div>
124+
</div>
125+
</div>
126+
))}
127+
</div>
128+
</div>
129+
</SkeletonRegion>
130+
);
131+
}
132+
133+
export function ProgressChartSkeleton({ className }: AnalyticsSkeletonProps) {
134+
return (
135+
<SkeletonRegion
136+
aria-label="Loading progress chart"
137+
className={cn('rounded-lg border border-slate-200 bg-white p-6 shadow-sm dark:border-slate-700 dark:bg-slate-800', className)}
138+
>
139+
<div className="flex items-center justify-between">
140+
<Skeleton className="h-5 w-32" />
141+
<div className="flex gap-2">
142+
{Array.from({ length: 4 }).map((_, i) => (
143+
<Skeleton key={i} className="h-7 w-16 rounded" />
144+
))}
145+
</div>
146+
</div>
147+
<div className="mt-6 grid grid-cols-1 gap-6 lg:grid-cols-2">
148+
{Array.from({ length: 4 }).map((_, i) => (
149+
<div key={i}>
150+
<Skeleton className="h-4 w-36" />
151+
<SkeletonChart bars={6} className="mt-2 border-0 p-0 shadow-none" />
152+
</div>
153+
))}
154+
</div>
155+
<div className="mt-6 grid grid-cols-2 gap-4 border-t border-slate-200 pt-6 md:grid-cols-4 dark:border-slate-700">
156+
{Array.from({ length: 4 }).map((_, i) => (
157+
<div key={i} className="text-center">
158+
<Skeleton className="mx-auto h-7 w-12" />
159+
<Skeleton className="mx-auto mt-1 h-3 w-16" />
160+
</div>
161+
))}
162+
</div>
163+
</SkeletonRegion>
164+
);
165+
}
166+
167+
export function TimeAnalysisSkeleton({ className }: AnalyticsSkeletonProps) {
168+
return (
169+
<SkeletonRegion
170+
aria-label="Loading time analysis"
171+
className={cn('rounded-lg border border-slate-200 bg-white p-6 shadow-sm dark:border-slate-700 dark:bg-slate-800', className)}
172+
>
173+
<Skeleton className="h-5 w-28" />
174+
<div className="mt-6 grid grid-cols-1 gap-4 md:grid-cols-3">
175+
{Array.from({ length: 3 }).map((_, i) => (
176+
<div key={i} className="flex items-center gap-4 rounded-lg p-4">
177+
<Skeleton className="h-12 w-12 rounded-full" />
178+
<div>
179+
<Skeleton className="h-3 w-20" />
180+
<Skeleton className="mt-1 h-6 w-16" />
181+
</div>
182+
</div>
183+
))}
184+
</div>
185+
<div className="mt-8 grid grid-cols-1 gap-8 lg:grid-cols-2">
186+
<div>
187+
<Skeleton className="h-4 w-48" />
188+
<Skeleton className="mt-4 h-64 w-full rounded-lg" />
189+
</div>
190+
<div>
191+
<Skeleton className="h-4 w-48" />
192+
<Skeleton className="mt-4 h-64 w-full rounded-lg" />
193+
</div>
194+
</div>
195+
</SkeletonRegion>
196+
);
197+
}
198+
199+
export default ProgressDashboardSkeleton;

frontend/src/components/Analytics/ProgressDashboard.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, {useEffect, useState} from 'react'
22
import SkillRadar from './SkillRadar'
33
import StreakTracker from './StreakTracker'
44
import GoalSetter from './GoalSetter'
5+
import { ProgressDashboardSkeleton } from './AnalyticsSkeleton'
56
// This component provides a comprehensive dashboard for users to track their learning progress, skill proficiency, and streaks. It fetches data from the backend and visualizes it in an engaging way.
67
type ProgressData = {
78
overallCompletion: number
@@ -39,7 +40,7 @@ const ProgressDashboard: React.FC = () => {
3940
return () => { mounted = false }
4041
}, [])
4142

42-
if (loading) return <div className="p-6">Loading analytics...</div>
43+
if (loading) return <ProgressDashboardSkeleton />
4344

4445
if (!data) return <div className="p-6">Unable to load analytics.</div>
4546

frontend/src/components/Collaboration/RoomLobby.tsx

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { useRouter } from 'next/navigation';
55
import { Video, Users, Calendar, Plus } from 'lucide-react';
66
import axios from 'axios';
77
import toast from 'react-hot-toast';
8+
import { SkeletonRegion } from '@/components/ui/Skeleton';
9+
import { Skeleton } from '@/components/ui/Skeleton';
810

911
interface Room {
1012
id: string;
@@ -75,10 +77,44 @@ const RoomLobby: React.FC = () => {
7577

7678
if (isLoading) {
7779
return (
78-
<div className="flex items-center justify-center min-h-screen bg-gray-900">
79-
<div className="text-center">
80-
<div className="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500 mx-auto mb-4"></div>
81-
<p className="text-white text-lg">Loading rooms...</p>
80+
<div className="min-h-screen bg-gray-900 p-8">
81+
<div className="mx-auto max-w-6xl">
82+
<SkeletonRegion aria-label="Loading rooms">
83+
<div className="mb-8">
84+
<Skeleton className="h-10 w-72 bg-slate-700" />
85+
<Skeleton className="mt-2 h-5 w-56 bg-slate-700" />
86+
</div>
87+
88+
<div className="mb-8 grid grid-cols-1 gap-4 md:grid-cols-2">
89+
<Skeleton className="h-16 w-full rounded-lg bg-slate-700" />
90+
<Skeleton className="h-16 w-full rounded-lg bg-slate-700" />
91+
</div>
92+
93+
<div>
94+
<Skeleton className="h-8 w-40 bg-slate-700" />
95+
<div className="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
96+
{Array.from({ length: 3 }).map((_, i) => (
97+
<div key={i} className="rounded-lg bg-slate-800 p-6">
98+
<div className="mb-4 flex items-start justify-between">
99+
<Skeleton className="h-6 w-40 bg-slate-700" />
100+
<Skeleton className="h-5 w-12 rounded-full bg-slate-700" />
101+
</div>
102+
<div className="space-y-2">
103+
<div className="flex items-center gap-2">
104+
<Skeleton className="h-4 w-4 rounded bg-slate-700" />
105+
<Skeleton className="h-3 w-32 bg-slate-700" />
106+
</div>
107+
<div className="flex items-center gap-2">
108+
<Skeleton className="h-4 w-4 rounded bg-slate-700" />
109+
<Skeleton className="h-3 w-40 bg-slate-700" />
110+
</div>
111+
</div>
112+
<Skeleton className="mt-4 h-10 w-full rounded-lg bg-slate-700" />
113+
</div>
114+
))}
115+
</div>
116+
</div>
117+
</SkeletonRegion>
82118
</div>
83119
</div>
84120
);

frontend/src/components/Discovery/CourseCardSkeleton.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { Skeleton } from '../ui/Skeleton';
2+
import { Skeleton, SkeletonRegion } from '../ui/Skeleton';
33
import { ViewMode } from './types';
44

55
/**
@@ -63,17 +63,14 @@ export const CourseGridSkeleton: React.FC<{
6363
count?: number;
6464
view?: ViewMode;
6565
}> = ({ count = 6, view = 'grid' }) => (
66-
<div
67-
role="status"
68-
aria-busy="true"
66+
<SkeletonRegion
6967
aria-label="Loading courses"
7068
className="contents"
7169
>
7270
{Array.from({ length: count }).map((_, index) => (
7371
<CourseCardSkeleton key={index} view={view} />
7472
))}
75-
<span className="sr-only">Loading courses</span>
76-
</div>
73+
</SkeletonRegion>
7774
);
7875

7976
export default CourseCardSkeleton;
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
'use client';
2+
3+
import * as React from 'react';
4+
import { cn } from '@/lib/utils';
5+
import { Skeleton, SkeletonRegion } from '@/components/ui/Skeleton';
6+
7+
export interface QuizPlayerSkeletonProps {
8+
className?: string;
9+
}
10+
11+
export function QuizPlayerSkeleton({ className }: QuizPlayerSkeletonProps) {
12+
return (
13+
<SkeletonRegion
14+
aria-label="Loading quiz"
15+
className={cn('max-w-4xl mx-auto w-full px-4 py-8', className)}
16+
>
17+
<div className="rounded-2xl border border-slate-200 bg-white p-6 shadow-xl dark:border-slate-800 dark:bg-slate-900">
18+
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
19+
<div className="flex items-center gap-3">
20+
<Skeleton className="h-10 w-10 rounded-xl" />
21+
<div className="space-y-2">
22+
<Skeleton className="h-5 w-40" />
23+
<Skeleton className="h-3 w-24" />
24+
</div>
25+
</div>
26+
<div className="flex items-center gap-4">
27+
<Skeleton className="h-8 w-20 rounded-lg" />
28+
<div className="hidden h-10 w-px bg-slate-200 md:block dark:bg-slate-700" />
29+
<div className="space-y-2">
30+
<Skeleton className="h-3 w-24" />
31+
<div className="flex items-center gap-2">
32+
<Skeleton className="h-2.5 w-32 rounded-full" />
33+
<Skeleton className="h-3 w-8" />
34+
</div>
35+
</div>
36+
</div>
37+
</div>
38+
<div className="mt-6 rounded-2xl bg-slate-50 p-4 dark:bg-slate-800/50">
39+
<Skeleton className="h-4 w-24" />
40+
<div className="mt-4 space-y-3">
41+
<Skeleton className="h-5 w-3/4" />
42+
<Skeleton className="h-4 w-full" />
43+
<Skeleton className="h-4 w-full" />
44+
</div>
45+
<div className="mt-6 space-y-3">
46+
<Skeleton className="h-12 w-full rounded-lg" />
47+
<Skeleton className="h-12 w-full rounded-lg" />
48+
<Skeleton className="h-12 w-full rounded-lg" />
49+
<Skeleton className="h-12 w-full rounded-lg" />
50+
</div>
51+
</div>
52+
</div>
53+
<div className="mt-6 flex items-center justify-between">
54+
<Skeleton className="h-10 w-24 rounded-xl" />
55+
<Skeleton className="h-12 w-48 rounded-2xl" />
56+
</div>
57+
<div className="mt-6 flex justify-center gap-2">
58+
{Array.from({ length: 5 }).map((_, i) => (
59+
<Skeleton key={i} className="h-2.5 w-2.5 rounded-full" />
60+
))}
61+
</div>
62+
</SkeletonRegion>
63+
);
64+
}
65+
66+
export default QuizPlayerSkeleton;

0 commit comments

Comments
 (0)