Skip to content

feat: introduce gamification badges for EOD streaks - #276

Open
Sharan-Sanadi wants to merge 1 commit into
CircuitVerse:mainfrom
Sharan-Sanadi:feat/eod-streak-badges
Open

feat: introduce gamification badges for EOD streaks#276
Sharan-Sanadi wants to merge 1 commit into
CircuitVerse:mainfrom
Sharan-Sanadi:feat/eod-streak-badges

Conversation

@Sharan-Sanadi

@Sharan-Sanadi Sharan-Sanadi commented May 22, 2026

Copy link
Copy Markdown

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

  1. Badge Configuration (lib/badges.ts)
    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
  2. Data Processing (scripts/generateLeaderboard.ts)

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

  • New Features
    • Added achievement badge system to recognize user activity streaks. Bronze, Silver, and Gold badges now display on leaderboard entries and contributor profiles, awarded for maintaining consecutive daily activity for 5, 10, and 15 days respectively. This provides visual recognition of consistent participation across the platform.

Review Change Stack

@netlify

netlify Bot commented May 22, 2026

Copy link
Copy Markdown

Deploy Preview for cv-community-dashboard ready!

Name Link
🔨 Latest commit 3d4319d
🔍 Latest deploy log https://app.netlify.com/projects/cv-community-dashboard/deploys/6a0fe8f395bb2c0008ba0546
😎 Deploy Preview https://deploy-preview-276--cv-community-dashboard.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown

Walkthrough

This PR adds an achievement badges system to the community dashboard. A new badge domain model (lib/badges.ts) defines BadgeVariant (bronze, silver, gold) and EarnedBadge type with streak thresholds. The leaderboard generation script (scripts/generateLeaderboard.ts) computes earned badges by analyzing each contributor's maximum consecutive active days and assigns badges based on configurable thresholds. Leaderboard and contributor card components extend their data types with the optional badges array and render badges as Tailwind-styled pills with Flame icons alongside existing role badges in both mobile and desktop layouts.

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main feature: introducing gamification badges for EOD streaks, which aligns with the primary objective of the pull request.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering the feature, implementation details, testing, and checklist items that align with the repository template requirements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
components/Leaderboard/LeaderboardCard.tsx (1)

88-92: ⚡ Quick win

Use the shared badge model type instead of an inline shape.

Import EarnedBadge from the shared badge domain and type this field as EarnedBadge[] 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}: Use interface for object shapes and type for unions/intersections.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3c9bb380-a859-4027-8568-48319d1f866c

📥 Commits

Reviewing files that changed from the base of the PR and between 584c342 and 3d4319d.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • components/Leaderboard/LeaderboardCard.tsx
  • components/people/ContributorCard.tsx
  • lib/badges.ts
  • next-env.d.ts
  • scripts/generateLeaderboard.ts

Comment on lines +182 to +183
<Flame className="w-3 h-3" />
<span className="font-bold">{badge.name}</span>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Hide decorative Flame icon from assistive tech.

This icon is decorative next to the badge label; set aria-hidden="true" (and optionally focusable="false").

Comment thread lib/badges.ts
Comment on lines +3 to +7
export type EarnedBadge = {
slug: string;
name: string;
variant: BadgeVariant;
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 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.ts

Repository: 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[] {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Comment on lines +967 to +979
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) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant