Skip to content
Merged
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
42 changes: 42 additions & 0 deletions src/app/api/badge-assignments/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { NextResponse } from 'next/server';
import mongoose from 'mongoose';
import '@/lib/models/badgeSchema'; // <-- Import badgeSchema BEFORE BadgeAssignment
import BadgeAssignment from '@/lib/models/badgeAssignmentSchema';
import connect from '@/lib/db';

export async function GET(
req: Request,
{ params }: { params: Promise<{ id: string }> } // ← PROMISE here
): Promise<NextResponse> {
const { id: userId } = await params; // ← AWAIT here

try {
if (!mongoose.Types.ObjectId.isValid(userId)) {
return NextResponse.json({ error: 'Invalid user ID format' }, { status: 400 });
}

await connect();

const badgeAssignments = await BadgeAssignment.find({ userId })
.populate('badgeId')
.sort({ assignedAt: -1 });

const responseData = badgeAssignments.map((assignment) => ({
id: assignment._id,
badge: {
id: assignment.badgeId._id,
badgeName: assignment.badgeId.badgeName,
badgeImage: assignment.badgeId.badgeImage,
criteria: assignment.badgeId.criteria,
description: assignment.badgeId.description
},
assignedAt: assignment.assignedAt,
context: assignment.assignmentContext
}));

return NextResponse.json(responseData);
} catch (error) {
console.error('Error fetching badge assignments:', error);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}
67 changes: 56 additions & 11 deletions src/components/Dashboard/EarnedBadges.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

import React, { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import { processAvatarUrl } from '@/utils/imageUtils';
import Image from 'next/image';

interface Badge {
id: string;
icon: string; // emoji or image
label: string;
_id: string;
badgeImage: string;
badgeName: string;
// Add other badge properties you need
}

interface Props {
Expand All @@ -15,25 +18,57 @@ interface Props {

export default function EarnedBadges({ userId }: Props) {
const [badges, setBadges] = useState<Badge[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const router = useRouter();

useEffect(() => {
const fetchBadges = async () => {
try {
const res = await fetch('/api/badge');
setLoading(true);
const res = await fetch(`/api/badge-assignments/${userId}`);

if (!res.ok) {
throw new Error('Failed to fetch badges');
}

const data = await res.json();
setBadges(data || []);

// Transform API response to match your Badge interface
const transformedBadges = data
.filter((assignment: any) => assignment.badge) // Only keep assignments with a populated badge
.map((assignment: any) => ({
_id: assignment.badge.id,
badgeImage: assignment.badge.badgeImage, // use badgeImage from schema
badgeName: assignment.badge.badgeName // use badgeName from schema
}));

setBadges(transformedBadges);
setError(null);
} catch (err) {
console.error('Error fetching badges:', err);
setError('Failed to load badges');
setBadges([]);
} finally {
setLoading(false);
}
};

fetchBadges();
if (userId) {
fetchBadges();
}
}, [userId]);

const displayedBadges = badges.slice(0, 5);

if (loading) {
return <div className="text-sm text-gray-500">Loading badges...</div>;
}

if (error) {
return <div className="text-sm text-red-500">{error}</div>;
}

return (
<div className="relative">
{/* Learn More Button - top right */}
Expand All @@ -50,11 +85,21 @@ export default function EarnedBadges({ userId }: Props) {
<div className="flex flex-wrap gap-6 pt-6">
{displayedBadges.length > 0 ? (
displayedBadges.map((badge, idx) => (
<div key={badge.id || idx} className="flex flex-col items-center">
<div className="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center mb-2 text-lg">
{badge.icon}
<div key={badge._id || idx} className="flex flex-col items-center">
<div className="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center mb-2 text-lg overflow-hidden">
{badge.badgeImage ? (
<Image
src={processAvatarUrl(badge.badgeImage) || '/placeholder-badge.png'}
alt={badge.badgeName}
width={48}
height={48}
className="object-contain w-full h-full"
/>
) : (
<span className="text-gray-400">?</span>
)}
</div>
<span className="text-xs text-center">{badge.label}</span>
<span className="text-xs text-center">{badge.badgeName}</span>
</div>
))
) : (
Expand All @@ -63,4 +108,4 @@ export default function EarnedBadges({ userId }: Props) {
</div>
</div>
);
}
}
Loading