feat: introduce gamification badges for EOD streaks - #276
Conversation
✅ Deploy Preview for cv-community-dashboard ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
WalkthroughThis PR adds an achievement badges system to the community dashboard. A new badge domain model ( Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (1)
components/Leaderboard/LeaderboardCard.tsx (1)
88-92: ⚡ Quick winUse the shared badge model type instead of an inline shape.
Import
EarnedBadgefrom the shared badge domain and type this field asEarnedBadge[]to avoid contract drift.Proposed fix
+import type { EarnedBadge } from "`@/lib/badges`"; @@ - badges?: Array<{ - slug: string; - name: string; - variant: "bronze" | "silver" | "gold"; - }>; + badges?: EarnedBadge[];As per coding guidelines,
**/*.{ts,tsx}: Useinterfacefor object shapes andtypefor unions/intersections.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3c9bb380-a859-4027-8568-48319d1f866c
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (5)
components/Leaderboard/LeaderboardCard.tsxcomponents/people/ContributorCard.tsxlib/badges.tsnext-env.d.tsscripts/generateLeaderboard.ts
| <Flame className="w-3 h-3" /> | ||
| <span className="font-bold">{badge.name}</span> |
There was a problem hiding this comment.
Hide decorative Flame icon from assistive tech.
This icon is decorative next to the badge label; set aria-hidden="true" (and optionally focusable="false").
| export type EarnedBadge = { | ||
| slug: string; | ||
| name: string; | ||
| variant: BadgeVariant; | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Use interface for EarnedBadge object shape.
This object shape should be declared as an interface to match repo TypeScript conventions.
Proposed fix
-export type EarnedBadge = {
+export interface EarnedBadge {
slug: string;
name: string;
variant: BadgeVariant;
-};
+}As per coding guidelines, **/*.{ts,tsx}: Use interface for object shapes and type for unions/intersections.
| import fs from "fs"; | ||
| import path from "path"; | ||
| import { coreTeamMembers, alumniMembers } from "../lib/team-data"; | ||
| import { EarnedBadge, STREAK_THRESHOLDS } from "../lib/badges"; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify ESM import style expectations and current relative imports in this file.
fd -i 'tsconfig*.json' -x sh -c 'echo "== {} =="; cat "{}"'
rg -n 'from\s+"(\.\.?/[^"]+)"' scripts/generateLeaderboard.tsRepository: CircuitVerse/community-dashboard
Length of output: 1667
Switch the relative ESM import to use a .js extension
EarnedBadge/STREAK_THRESHOLDS are imported from ../lib/badges without the .js suffix—update the path to ../lib/badges.js to match the repo ESM TS rules (use import type for EarnedBadge only if it’s type-only).
| } | ||
| } | ||
|
|
||
| function calculateBadges(daily_activity: DailyActivity[]): EarnedBadge[] { |
There was a problem hiding this comment.
Rename daily_activity to camelCase.
Use dailyActivity for the function parameter and references.
As per coding guidelines, **/*.{ts,tsx,js}: Use camelCase for function and variable names.
| const sorted = [...daily_activity].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); | ||
|
|
||
| let maxStreak = 0; | ||
| let currentStreak = 0; | ||
| let lastDate: Date | null = null; | ||
|
|
||
| for (const day of sorted) { | ||
| if (day.points > 0) { | ||
| const date = new Date(day.date); | ||
| if (lastDate) { | ||
| const diffTime = Math.abs(date.getTime() - lastDate.getTime()); | ||
| const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); | ||
| if (diffDays === 1) { |
There was a problem hiding this comment.
Guard against invalid activity dates before streak math.
If any date is invalid, new Date(...).getTime() becomes NaN and can silently break sorting/streak computation.
Proposed fix
- const sorted = [...daily_activity].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
+ const sorted = dailyActivity
+ .map((d) => ({ ...d, ts: Date.parse(d.date) }))
+ .filter((d) => Number.isFinite(d.ts))
+ .sort((a, b) => a.ts - b.ts);As per coding guidelines, **/*.ts: Validate user input and external data before processing.
This Pull Request introduces the Gamification Badges (EOD Streak Badge) feature, directly addressing the badges, eod streak requirement outlined in tasks.txt.
The goal of this feature is to increase contributor engagement and consistency by rewarding users for maintaining daily contribution streaks.
🚀 Key Features & Implementation
Introduced a centralized and extensible badge configuration system
Defined tier-based streak rewards:
🥉 Bronze — 5-day streak
🥈 Silver — 10-day streak
🥇 Gold — 15-day streak
Designed for scalability to support future gamification features
Extended Contributor and UserEntry schemas to include:
badges?: EarnedBadge[]
Implemented calculateBadges():
Parses chronological daily_activity
Computes maximum consecutive streak
Assigns appropriate badge tier based on thresholds
Ensures accurate and efficient badge computation during leaderboard generation
3. Frontend Integration
Updated:
LeaderboardCard.tsx
ContributorCard.tsx
Added a dedicated badge display section below contributor roles
Integrated Flame icon from lucide-react with tier-based styling:
🥇 Gold → text-yellow-600 bg-yellow-500/15
🥈 Silver → text-slate-400 bg-slate-400/10
🥉 Bronze → text-orange-600 bg-orange-600/10
Ensured:
Visual consistency with existing design system
Proper rendering in both light and dark modes
Responsive layout across screen sizes
📈 Impact
Encourages daily contributions through positive reinforcement
Adds a visual progression system to the leaderboard
Lays groundwork for future gamification (achievements, levels, etc.)
Type of Change
✅ New feature (non-breaking, additive functionality)
🧪 Testing
✅ Application builds successfully (npm run build)
✅ Badge logic verified with multiple streak scenarios
✅ UI tested across:
Chrome
Firefox
Mobile viewports
✅ No console errors or warnings observed
✅ Checklist
Code follows project style guidelines
Self-review completed
Documentation updated where applicable
No merge conflicts
Summary by CodeRabbit