feat: add user reputation dashboard with XP, skills radar, and contri…#169
feat: add user reputation dashboard with XP, skills radar, and contri…#169legend4tech wants to merge 1 commit intoboundlessfi:mainfrom
Conversation
…bution heatmap - Extend reputation types with XP, skills, and contribution history fields - Create skill radar chart component using Recharts - Create GitHub-style contribution heatmap with streak tracking - Add XP display widget with level progression - Add reputation dashboard tab to profile page - Handle empty states gracefully for all components Co-authored-by: Qwen-Coder <[email protected]>
|
@legend4tech is attempting to deploy a commit to the Threadflow Team on Vercel. A member of the Team first needs to authorize it. |
|
@legend4tech 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! 🚀 |
📝 WalkthroughWalkthroughThis PR introduces a new Reputation Dashboard feature to user profiles, consisting of four new UI components (XpDisplay, SkillRadarChart, ContributionHeatmap, ReputationDashboard), a new dashboard tab integrated into the profile page, and extended reputation types to support the new metrics structure. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 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: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/profile/`[userId]/page.tsx:
- Around line 235-239: The code force-casts reputation to
ContributorReputationWithMetrics when rendering <ReputationDashboard>, which can
crash if the API returned ContributorReputation without metrics; instead, check
for reputation.metrics before casting and handle the missing-metrics case
gracefully—either render a fallback/placeholder UI or pass default metrics
(e.g., zeros) so ReputationDashboard always receives the expected shape; update
the prop handling in ReputationDashboard usage (or its prop type) to accept
ContributorReputation | ContributorReputationWithMetrics and guard/use default
values when metrics is undefined.
In `@components/profile/contribution-heatmap.tsx`:
- Line 12: Remove the unused import and parameter to fix lint warnings: delete
the unused named import isSameDay from the top import list and remove the unused
map callback parameter index (or rename it to _ if needed) in the component's
map/iteration where index is defined so both symbols are no longer unused;
ensure any logic relying on isSameDay is preserved or replaced with the used
utilities (format, subDays, startOfDay) and run the linter to confirm warnings
are resolved.
- Around line 151-169: The current month labels use the static MONTHS array and
can misalign with the actual 365-day heatmap; instead compute monthLabels from
the rendered weeks array: iterate over weeks (use each week’s first real day or
first non-null day), derive its month name/index, and only emit a label when the
month changes or at the first/last week; replace the static MONTHS usage in the
render block with the computed monthLabels so labels align with week columns
(refer to symbols weeks, monthLabels, and MONTHS in contribution-heatmap.tsx).
In `@components/profile/xp-display.tsx`:
- Around line 14-19: getLevelFromXp currently computes level/progress directly
from xp which allows negative xp to produce invalid results; clamp or normalize
xp to be non-negative at the top of getLevelFromXp (e.g., const safeXp =
Math.max(0, xp)) and then use safeXp for computing level, currentLevelXp,
nextLevelXp and progress so level, currentLevelXp and progress are always valid.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 4102b51e-7690-4e95-8068-26ace6d84f11
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (6)
app/profile/[userId]/page.tsxcomponents/profile/contribution-heatmap.tsxcomponents/profile/reputation-dashboard.tsxcomponents/profile/skill-radar-chart.tsxcomponents/profile/xp-display.tsxtypes/reputation.ts
| <TabsContent value="dashboard" className="mt-6"> | ||
| <h2 className="text-xl font-bold mb-4">Reputation Dashboard</h2> | ||
| <ReputationDashboard | ||
| reputation={reputation as ContributorReputationWithMetrics} | ||
| /> |
There was a problem hiding this comment.
Unsafe cast can crash the Dashboard tab at runtime.
At Line 238, reputation is force-cast to ContributorReputationWithMetrics, but API data is typed as ContributorReputation (no guaranteed metrics). Opening this tab can throw when metrics.xp is accessed.
💡 Suggested fix (guard + graceful partial-data handling)
<TabsContent value="dashboard" className="mt-6">
<h2 className="text-xl font-bold mb-4">Reputation Dashboard</h2>
- <ReputationDashboard
- reputation={reputation as ContributorReputationWithMetrics}
- />
+ {"metrics" in reputation && reputation.metrics ? (
+ <ReputationDashboard
+ reputation={reputation as ContributorReputationWithMetrics}
+ />
+ ) : (
+ <div className="p-8 border rounded-lg text-center text-muted-foreground bg-secondary/5">
+ Reputation metrics are not available yet.
+ </div>
+ )}
</TabsContent>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <TabsContent value="dashboard" className="mt-6"> | |
| <h2 className="text-xl font-bold mb-4">Reputation Dashboard</h2> | |
| <ReputationDashboard | |
| reputation={reputation as ContributorReputationWithMetrics} | |
| /> | |
| <TabsContent value="dashboard" className="mt-6"> | |
| <h2 className="text-xl font-bold mb-4">Reputation Dashboard</h2> | |
| {"metrics" in reputation && reputation.metrics ? ( | |
| <ReputationDashboard | |
| reputation={reputation as ContributorReputationWithMetrics} | |
| /> | |
| ) : ( | |
| <div className="p-8 border rounded-lg text-center text-muted-foreground bg-secondary/5"> | |
| Reputation metrics are not available yet. | |
| </div> | |
| )} |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/profile/`[userId]/page.tsx around lines 235 - 239, The code force-casts
reputation to ContributorReputationWithMetrics when rendering
<ReputationDashboard>, which can crash if the API returned ContributorReputation
without metrics; instead, check for reputation.metrics before casting and handle
the missing-metrics case gracefully—either render a fallback/placeholder UI or
pass default metrics (e.g., zeros) so ReputationDashboard always receives the
expected shape; update the prop handling in ReputationDashboard usage (or its
prop type) to accept ContributorReputation | ContributorReputationWithMetrics
and guard/use default values when metrics is undefined.
| TooltipTrigger, | ||
| } from "@/components/ui/tooltip"; | ||
| import { cn } from "@/lib/utils"; | ||
| import { format, subDays, startOfDay, isSameDay } from "date-fns"; |
There was a problem hiding this comment.
Address lint warnings for unused symbols.
Line 12 imports isSameDay but never uses it, and Line 86 defines index but does not use it. This is currently reported by CI lint warnings.
🧹 Minimal cleanup patch
-import { format, subDays, startOfDay, isSameDay } from "date-fns";
+import { format, subDays, startOfDay } from "date-fns";
...
- daysData.forEach((day, index) => {
+ daysData.forEach((day) => {Also applies to: 86-86
🧰 Tools
🪛 GitHub Actions: CI - Build & Lint Check
[warning] 12-12: ESLint (@typescript-eslint/no-unused-vars): 'isSameDay' is defined but never used.
🪛 GitHub Check: build-and-lint (24.x)
[warning] 12-12:
'isSameDay' is defined but never used
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/profile/contribution-heatmap.tsx` at line 12, Remove the unused
import and parameter to fix lint warnings: delete the unused named import
isSameDay from the top import list and remove the unused map callback parameter
index (or rename it to _ if needed) in the component's map/iteration where index
is defined so both symbols are no longer unused; ensure any logic relying on
isSameDay is preserved or replaced with the used utilities (format, subDays,
startOfDay) and run the linter to confirm warnings are resolved.
| <div className="flex gap-[3px] ml-6 mb-1"> | ||
| {MONTHS.map((month, index) => { | ||
| // Show month label at the start of each month | ||
| const shouldShow = | ||
| index === 0 || | ||
| (index > 0 && index % 2 === 0) || | ||
| index === MONTHS.length - 1; | ||
| if (!shouldShow) { | ||
| return <div key={month} className="w-3" />; | ||
| } | ||
| return ( | ||
| <div | ||
| key={month} | ||
| className="text-xs text-muted-foreground w-8" | ||
| > | ||
| {month} | ||
| </div> | ||
| ); | ||
| })} |
There was a problem hiding this comment.
Month labels are static and can be chronologically incorrect.
At Line 151–169, labels are hard-coded (Jan..Dec) instead of derived from the rendered week columns. This can show wrong month boundaries for the actual 365-day window.
🗓️ Suggested direction (derive labels from weeks)
- <div className="flex gap-[3px] ml-6 mb-1">
- {MONTHS.map((month, index) => {
- // Show month label at the start of each month
- const shouldShow =
- index === 0 ||
- (index > 0 && index % 2 === 0) ||
- index === MONTHS.length - 1;
- if (!shouldShow) {
- return <div key={month} className="w-3" />;
- }
- return (
- <div
- key={month}
- className="text-xs text-muted-foreground w-8"
- >
- {month}
- </div>
- );
- })}
- </div>
+ <div className="flex gap-[3px] ml-6 mb-1">
+ {monthLabels.map((label, weekIndex) => (
+ <div key={`month-${weekIndex}`} className="text-xs text-muted-foreground w-3">
+ {label}
+ </div>
+ ))}
+ </div>And compute monthLabels from weeks (based on each week’s first real day) before render.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/profile/contribution-heatmap.tsx` around lines 151 - 169, The
current month labels use the static MONTHS array and can misalign with the
actual 365-day heatmap; instead compute monthLabels from the rendered weeks
array: iterate over weeks (use each week’s first real day or first non-null
day), derive its month name/index, and only emit a label when the month changes
or at the first/last week; replace the static MONTHS usage in the render block
with the computed monthLabels so labels align with week columns (refer to
symbols weeks, monthLabels, and MONTHS in contribution-heatmap.tsx).
| const getLevelFromXp = (xp: number) => { | ||
| const level = Math.floor(xp / 1000) + 1; | ||
| const currentLevelXp = xp % 1000; | ||
| const nextLevelXp = 1000; | ||
| const progress = (currentLevelXp / nextLevelXp) * 100; | ||
|
|
There was a problem hiding this comment.
Clamp invalid XP input to avoid broken progress math.
At Line 15–19, negative xp can produce invalid level/progress output. Add a non-negative guard before calculations.
🔧 Suggested defensive patch
const getLevelFromXp = (xp: number) => {
- const level = Math.floor(xp / 1000) + 1;
- const currentLevelXp = xp % 1000;
+ const safeXp = Math.max(0, xp);
+ const level = Math.floor(safeXp / 1000) + 1;
+ const currentLevelXp = safeXp % 1000;
const nextLevelXp = 1000;
const progress = (currentLevelXp / nextLevelXp) * 100;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/profile/xp-display.tsx` around lines 14 - 19, getLevelFromXp
currently computes level/progress directly from xp which allows negative xp to
produce invalid results; clamp or normalize xp to be non-negative at the top of
getLevelFromXp (e.g., const safeXp = Math.max(0, xp)) and then use safeXp for
computing level, currentLevelXp, nextLevelXp and progress so level,
currentLevelXp and progress are always valid.
Committed and pushed successfully. Here’s a brief PR description you can copy:
Feature: User Reputation Dashboard
Overview
Adds a visual reputation dashboard to user profiles showcasing XP, technical skills, and contribution activity.
Changes
types/reputation.tswithSkillLevel,ContributionHistory, andUserReputationMetricsTesting
Closes #132
Summary by CodeRabbit
Release Notes