3e4c730860
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>
89 lines
2.4 KiB
JavaScript
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;
|