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>
80 lines
2.6 KiB
JavaScript
80 lines
2.6 KiB
JavaScript
import React from "react";
|
|
import PropTypes from "prop-types";
|
|
|
|
/**
|
|
* BadgeProgress component - displays progress bars for badges in progress
|
|
* @param {Array} badges - Array of badge objects with progress information
|
|
*/
|
|
const BadgeProgress = ({ badges }) => {
|
|
// Filter to show only badges that are in progress (not earned and have some progress)
|
|
const inProgressBadges = badges.filter(
|
|
(badge) => !badge.isEarned && badge.progress > 0 && badge.threshold > 0
|
|
);
|
|
|
|
if (inProgressBadges.length === 0) {
|
|
return (
|
|
<div className="alert alert-info">
|
|
<p className="mb-0">
|
|
Complete tasks and participate in events to earn badges!
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="badge-progress-container">
|
|
<h5 className="mb-3">Badges In Progress</h5>
|
|
{inProgressBadges.map((badge) => {
|
|
const percentage = Math.round((badge.progress / badge.threshold) * 100);
|
|
return (
|
|
<div key={badge._id} className="mb-3">
|
|
<div className="d-flex justify-content-between align-items-center mb-1">
|
|
<div className="d-flex align-items-center">
|
|
<span style={{ fontSize: "1.5rem", marginRight: "0.5rem" }}>
|
|
{badge.icon || "🏆"}
|
|
</span>
|
|
<div>
|
|
<strong>{badge.name}</strong>
|
|
<br />
|
|
<small className="text-muted">{badge.description}</small>
|
|
</div>
|
|
</div>
|
|
<span className="badge badge-info">
|
|
{badge.progress} / {badge.threshold}
|
|
</span>
|
|
</div>
|
|
<div className="progress" style={{ height: "20px" }}>
|
|
<div
|
|
className={`progress-bar ${percentage >= 75 ? "bg-success" : percentage >= 50 ? "bg-info" : "bg-warning"}`}
|
|
role="progressbar"
|
|
style={{ width: `${percentage}%` }}
|
|
aria-valuenow={badge.progress}
|
|
aria-valuemin="0"
|
|
aria-valuemax={badge.threshold}
|
|
>
|
|
{percentage}%
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
BadgeProgress.propTypes = {
|
|
badges: PropTypes.arrayOf(
|
|
PropTypes.shape({
|
|
_id: PropTypes.string.isRequired,
|
|
name: PropTypes.string.isRequired,
|
|
description: PropTypes.string.isRequired,
|
|
icon: PropTypes.string,
|
|
progress: PropTypes.number.isRequired,
|
|
threshold: PropTypes.number.isRequired,
|
|
isEarned: PropTypes.bool.isRequired,
|
|
})
|
|
).isRequired,
|
|
};
|
|
|
|
export default BadgeProgress;
|