Skip to content

Commit cfdb9e5

Browse files
Merge pull request #367 from Code102SoftwareProject/hero-section-stat-data
feat: implement hero stats API endpoint and integrate stats fetching …
2 parents ce48535 + e27bbd0 commit cfdb9e5

2 files changed

Lines changed: 111 additions & 7 deletions

File tree

src/app/api/stats/hero/route.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { NextResponse } from 'next/server';
2+
import dbConnect from '@/lib/db';
3+
import User from '@/lib/models/userSchema';
4+
import SkillList from '@/lib/models/skillList';
5+
import SkillMatch from '@/lib/models/skillMatch';
6+
import { Feedback } from '@/lib/models/feedbackSchema';
7+
8+
export const dynamic = 'force-dynamic';
9+
10+
export async function GET() {
11+
try {
12+
await dbConnect();
13+
14+
// Active learners
15+
const activeLearners = await User.countDocuments({ isDeleted: { $ne: true }, isBlocked: { $ne: true } });
16+
17+
// Skills available (unique skill names across all categories)
18+
const skillLists = await SkillList.find({}).select('skills');
19+
const skillSet = new Set();
20+
skillLists.forEach(list => {
21+
list.skills.forEach(skill => {
22+
skillSet.add(skill.name);
23+
});
24+
});
25+
const skillsAvailable = skillSet.size;
26+
27+
// Successful matches and total matches
28+
const successfulMatches = await SkillMatch.countDocuments({ status: 'completed' });
29+
const totalMatches = await SkillMatch.countDocuments({});
30+
31+
// Satisfaction rate (average rating)
32+
const feedbacks = await Feedback.find({ rating: { $exists: true } }).select('rating');
33+
let satisfactionRate = 0;
34+
if (feedbacks.length > 0) {
35+
const avg = feedbacks.reduce((sum, f) => sum + (f.rating || 0), 0) / feedbacks.length;
36+
satisfactionRate = Math.round(avg * 20); // Convert 1-5 to %
37+
}
38+
39+
return NextResponse.json({
40+
success: true,
41+
data: {
42+
activeLearners,
43+
skillsAvailable,
44+
successfulMatches,
45+
totalMatches,
46+
satisfactionRate
47+
}
48+
});
49+
} catch (error) {
50+
return NextResponse.json({ success: false, message: 'Failed to fetch hero stats' }, { status: 500 });
51+
}
52+
}

src/app/page.tsx

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,24 @@ const EnhancedHeroSection = () => {
2525
const router = useRouter();
2626
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
2727
const [isVisible, setIsVisible] = useState(false);
28+
const [stats, setStats] = useState<{
29+
activeLearners: number | null;
30+
skillsAvailable: number | null;
31+
successfulMatches: number | null;
32+
totalMatches: number | null;
33+
satisfactionRate: number | null;
34+
}>({
35+
activeLearners: null,
36+
skillsAvailable: null,
37+
successfulMatches: null,
38+
totalMatches: null,
39+
satisfactionRate: null,
40+
});
41+
const [loadingStats, setLoadingStats] = useState(true);
2842

2943
useEffect(() => {
3044
setIsVisible(true);
31-
45+
3246
const handleMouseMove = (e: MouseEvent) => {
3347
setMousePosition({
3448
x: (e.clientX / window.innerWidth) * 100,
@@ -40,6 +54,25 @@ const EnhancedHeroSection = () => {
4054
return () => window.removeEventListener('mousemove', handleMouseMove);
4155
}, []);
4256

57+
useEffect(() => {
58+
// Fetch hero stats from backend
59+
const fetchStats = async () => {
60+
try {
61+
setLoadingStats(true);
62+
const res = await fetch('/api/stats/hero');
63+
const data = await res.json();
64+
if (data.success && data.data) {
65+
setStats(data.data);
66+
}
67+
} catch (err) {
68+
// Optionally handle error
69+
} finally {
70+
setLoadingStats(false);
71+
}
72+
};
73+
fetchStats();
74+
}, []);
75+
4376
return (
4477
<section className="relative min-h-screen flex items-center justify-center overflow-hidden bg-gradient-to-br from-[#006699] via-blue-700 to-indigo-900">
4578
{/* Animated Background Elements */}
@@ -144,13 +177,32 @@ const EnhancedHeroSection = () => {
144177
{/* Stats */}
145178
<div className={`grid grid-cols-2 md:grid-cols-4 gap-8 transform transition-all duration-1000 delay-800 ${isVisible ? 'translate-y-0 opacity-100' : 'translate-y-10 opacity-0'}`}>
146179
{[
147-
{ number: '50K+', label: 'Active Learners', icon: Users },
148-
{ number: '1000+', label: 'Skills Available', icon: BookOpen },
149-
{ number: '25K+', label: 'Successful Matches', icon: Star },
150-
{ number: '95%', label: 'Satisfaction Rate', icon: Award }
180+
{
181+
number: loadingStats || stats.activeLearners === null ? '...' : stats.activeLearners.toLocaleString(),
182+
label: 'Active Learners',
183+
icon: Users
184+
},
185+
{
186+
number: loadingStats || stats.skillsAvailable === null ? '...' : stats.skillsAvailable.toLocaleString(),
187+
label: 'Skills Available',
188+
icon: BookOpen
189+
},
190+
{
191+
number:
192+
loadingStats || stats.totalMatches === null
193+
? '...'
194+
: stats.totalMatches.toLocaleString(),
195+
label: 'Total Matches',
196+
icon: Star
197+
},
198+
{
199+
number: loadingStats || stats.satisfactionRate === null ? '...' : `${stats.satisfactionRate}%`,
200+
label: 'Satisfaction Rate',
201+
icon: Award
202+
}
151203
].map((stat, index) => (
152-
<div
153-
key={stat.label}
204+
<div
205+
key={stat.label}
154206
className="text-center group"
155207
style={{ animationDelay: `${index * 100}ms` }}
156208
>

0 commit comments

Comments
 (0)