Files
adopt-a-street/frontend/src/components/BadgeDisplay.js
T
William Valentin 3e4c730860 feat: implement comprehensive gamification, analytics, and leaderboard system
This commit adds a complete gamification system with analytics dashboards,
leaderboards, and enhanced badge tracking functionality.

Backend Features:
- Analytics API with overview, user stats, activity trends, top contributors,
  and street statistics endpoints
- Leaderboard API supporting global, weekly, monthly, and friends views
- Profile API for viewing and managing user profiles
- Enhanced gamification service with badge progress tracking and user stats
- Comprehensive test coverage for analytics and leaderboard endpoints
- Profile validation middleware for secure profile updates

Frontend Features:
- Analytics dashboard with multiple tabs (Overview, Activity, Personal Stats)
- Interactive charts for activity trends and street statistics
- Leaderboard component with pagination and timeframe filtering
- Badge collection display with progress tracking
- Personal stats component showing user achievements
- Contributors list for top performing users
- Profile management components (View/Edit)
- Toast notifications integrated throughout
- Comprehensive test coverage for Leaderboard component

Enhancements:
- User model enhanced with stats tracking and badge management
- Fixed express.Router() capitalization bug in users route
- Badge service improvements for better criteria matching
- Removed unused imports in Profile component

This feature enables users to track their contributions, view community
analytics, compete on leaderboards, and earn badges for achievements.

🤖 Generated with OpenCode

Co-Authored-By: AI Assistant <noreply@opencode.ai>
2025-11-03 13:53:48 -08:00

89 lines
2.4 KiB
JavaScript

import React from "react";
import PropTypes from "prop-types";
/**
* BadgeDisplay component - displays a single badge with icon, name, and description
* @param {Object} badge - Badge object
* @param {boolean} isEarned - Whether the badge is earned
* @param {boolean} showTooltip - Whether to show tooltip on hover
*/
const BadgeDisplay = ({ badge, isEarned = false, showTooltip = true }) => {
const getRarityColor = (rarity) => {
switch (rarity) {
case "common":
return "#6c757d";
case "rare":
return "#0d6efd";
case "epic":
return "#6f42c1";
case "legendary":
return "#ffc107";
default:
return "#6c757d";
}
};
const badgeStyle = {
filter: isEarned ? "none" : "grayscale(100%) opacity(0.5)",
borderColor: getRarityColor(badge.rarity),
borderWidth: "3px",
transition: "all 0.3s ease",
};
const iconStyle = {
fontSize: "3rem",
marginBottom: "0.5rem",
};
return (
<div
className="card badge-card text-center p-3"
style={badgeStyle}
title={
showTooltip
? `${badge.name} - ${badge.description}\n${
badge.criteria?.type
? `Unlock: ${badge.criteria.threshold} ${badge.criteria.type.replace(
"_",
" "
)}`
: ""
}`
: ""
}
>
<div style={iconStyle}>{badge.icon || "🏆"}</div>
<h6 className="mb-1">{badge.name}</h6>
<p className="small text-muted mb-1">{badge.description}</p>
<span
className={`badge badge-${badge.rarity === "legendary" ? "warning" : badge.rarity === "epic" ? "purple" : badge.rarity === "rare" ? "primary" : "secondary"}`}
>
{badge.rarity}
</span>
{isEarned && (
<div className="mt-2">
<span className="badge badge-success"> Earned</span>
</div>
)}
</div>
);
};
BadgeDisplay.propTypes = {
badge: PropTypes.shape({
_id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
icon: PropTypes.string,
rarity: PropTypes.oneOf(["common", "rare", "epic", "legendary"]).isRequired,
criteria: PropTypes.shape({
type: PropTypes.string,
threshold: PropTypes.number,
}),
}).isRequired,
isEarned: PropTypes.bool,
showTooltip: PropTypes.bool,
};
export default BadgeDisplay;