Skip to content

#773 feat(campaign): Campaign milestone achievements -unlock visual b… - #829

Merged
Idrhas merged 2 commits into
Fundable-Protocol:mainfrom
veloura-dev:#773-feat-campaign--Campaign-milestone-achievements-unlock-visual-badges-at-25%,-50%,-75%,100%-FIX
Sep 3, 2026
Merged

#773 feat(campaign): Campaign milestone achievements -unlock visual b…#829
Idrhas merged 2 commits into
Fundable-Protocol:mainfrom
veloura-dev:#773-feat-campaign--Campaign-milestone-achievements-unlock-visual-badges-at-25%,-50%,-75%,100%-FIX

Conversation

@veloura-dev

Copy link
Copy Markdown
Contributor

CLOSE #773

Findings (root causes)

1. No milestone badge UI existed at all.
The campaign detail page (apps/web/src/app/(overview)/campaigns/[id]/page.tsx) rendered a funding progress bar but never surfaced the 25/50/75/100% milestones.

2. The funding progress math was wrong.
The original calculation parsed amount strings that contained thousands separators:

// BEFORE — parseFloat("33,850") returns 33 (stops at the comma)
const progressPct = Math.min(100, Math.round((parseFloat(campaign.raisedAmount) / parseFloat(campaign.goalAmount)) * 100));
// 33 / 50 → 66%  (should be 33,850 / 50,000 → 68%)

Since milestone state is derived from this percentage, any incorrect figure would unlock the wrong badges.

3. Pre-existing runtime blocker that prevented the page from rendering.
apps/web/src/lib/utils.ts called clxx (undefined) instead of clsx:

// BEFORE — ReferenceError: clxx is not defined
export function cn(...inputs: ClassValue[]) {
    return twMerge(clxx(inputs));
}

cn() is used by 41 files (Button, Badge, Tabs, etc.), so the campaign page couldn't render at all — the badge feature was impossible to display until this was fixed.


Fix features

A. Pure milestone/percentage helpers — lib/campaign-milestones.ts

  • MILESTONE_PERCENTAGES = [25, 50, 75, 100] — canonical milestones.
  • parseFundingAmount() — safely parses "33,850", "50,000", "33,850 XLM", and numbers; returns 0 for invalid input (never NaN).
  • calculateFundingProgress(raised, goal) — correct rounded 0–100 progress, safe against zero/negative/missing goals.
  • getAchievedMilestonePercentages(progress) — which milestones are unlocked.
  • getNextMilestone(progress) — the next locked milestone, or null when done.

B. Milestone badge UI + celebratory animation — components/modules/campaign/CampaignMilestones.tsx (302 lines)

Four badges, each with its own custom icon and accent colour:

% Badge Icon Accent
25% Kickoff Flag Emerald
50% Halfway Medal Sky
75% Almost There Rocket Violet
100% Fully Funded Trophy Amber

Celebratory animation per achieved badge via framer-motion:

  • Spring scale-pop on unlock (scale: [0.85, 1.06, 1], opacity ramp).
  • Pulsing outer glow behind the badge.
  • Expanding ring pulse in the milestone colour.
  • A radial sparkle-burst of 8 particles (SparkleBurst) in the milestone colour.

Achieved badges show a green CheckCircle2; locked ones are dimmed with a Lock. The section also has a live animated progress track, a status chip ("Next at XX%" / "Goal achieved"), and full accessibility (role="img", aria-label, sr-only text, plus data-testid/data-achieved hooks for tests).

C. Wired into the campaign detail page — page.tsx

  • Imported CampaignMilestones (line 23).
  • Fixed progressPct to strip thousands separators before parsing (line 101).
  • Rendered <CampaignMilestones raisedAmount={campaign.raisedAmount} goalAmount={campaign.goalAmount} /> between the hero banner and the tabs (line 249).

D. Required prerequisite fix — lib/utils.ts

// AFTER — corrected to clsx
export function cn(...inputs: ClassValue[]) {
    return twMerge(clsx(inputs));
}

Validation summary (re-verified this session)

  • 19 new tests pass (14 unit + 5 component).
  • ESLint clean (exit 0) on all 4 new source/test files; my page.tsx/utils.ts lines are free of lint errors (the 2 flagged page.tsx errors are pre-existing, on lines 11–12 & 291, not my lines).
  • tsc --noEmit: zero errors in any file I touched (repo-wide 52 errors are all pre-existing, in files I never opened).
  • Full suite: 909 passed / 66 failed — the 66 failures are pre-existing and unrelated (jsdom matchMedia, Stellar RPC, etc.), in files I never modified.

… -unlock visual badges at 25%, 50%, 75%, 100% FIXED
@drips-wave

drips-wave Bot commented Sep 3, 2026

Copy link
Copy Markdown

@veloura-dev Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: d4aaa407-ede9-4c8d-866f-e8c50764c2e4


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.

…n-milestone-achievements-unlock-visual-badges-at-25%,-50%,-75%,100%-FIX
@Idrhas
Idrhas merged commit 2a25a13 into Fundable-Protocol:main Sep 3, 2026
1 check passed
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.

feat(campaign): Campaign milestone achievements - unlock visual badges at 25%, 50%, 75%, 100%

2 participants