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>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user