Skip to content

feat(leaderboard): add tooltip explaining contribution scoring#43

Open
gautamsidhwani29 wants to merge 6 commits intoCircuitVerse:mainfrom
gautamsidhwani29:feat/tooltip-for-points-info
Open

feat(leaderboard): add tooltip explaining contribution scoring#43
gautamsidhwani29 wants to merge 6 commits intoCircuitVerse:mainfrom
gautamsidhwani29:feat/tooltip-for-points-info

Conversation

@gautamsidhwani29
Copy link
Copy Markdown

@gautamsidhwani29 gautamsidhwani29 commented Dec 29, 2025

Description

This PR adds a small info (ⓘ) tooltip next to the leaderboard title to clearly explain how contribution points are calculated.

The leaderboard was already correctly sorted by total contribution points, but the lack of visible scoring rules caused confusion when individual metrics (PRs opened, issues opened) appeared higher for lower-ranked contributors. This change improves clarity without altering any existing logic.

Related Issue

Fixes #42

Type of change

  • Bug fix
  • [ ✔️] Feature
  • Refactor
  • Documentation

Checklist

  • [ ✔️] Code follows project style
  • [✔️ ] Tested locally
  • [✔️ ] No unnecessary files added
  • [✔️ ] PR title is clear and descriptive

Screenshots

image

Summary by CodeRabbit

  • New Features

    • Info button in the leaderboard header shows points-allocation details via desktop tooltip or mobile bottom-sheet.
  • Improvements

    • Header layout and spacing refined for clearer grouping of title, info control, and actions; responsive breakpoints updated.
    • Per-entry layout, activity-chart placement, empty-state rendering, and pagination reworked for consistent alignment and ellipsis handling.
    • Top Contributors panel alignment and contributor item styling improved.
    • Rankings now compute within the currently filtered subset.
    • Tooltips can display richer content and have small text/label consistency tweaks.
  • Accessibility

    • Info control supports keyboard focus, Escape-to-close, and screen-reader labels.

✏️ Tip: You can customize this high-level summary in your review settings.

@netlify
Copy link
Copy Markdown

netlify bot commented Dec 29, 2025

Deploy Preview for circuitverse-leaderboard ready!

Name Link
🔨 Latest commit 0da304b
🔍 Latest deploy log https://app.netlify.com/projects/circuitverse-leaderboard/deploys/6957b4ecbb947e00089e5921
😎 Deploy Preview https://deploy-preview-43--circuitverse-leaderboard.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

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

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Dec 29, 2025

Walkthrough

Adds a new PointsInfoButton component and renders it inline in the Leaderboard header (desktop: tooltip; mobile: bottom-sheet) to show points allocation. Updates LeaderboardView layout and responsive breakpoints, standardizes string quoting, refactors activityStyles typing and getActivityStyle usage, adjusts search/sort URL handling, ensures ranking is computed within filtered results, reworks pagination rendering, refines per-entry activity and chart rendering, and updates Hint so HintProps.label accepts string | React.ReactNode with conditional tooltip content rendering.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Out of Scope Changes check ❓ Inconclusive Changes are focused on the tooltip feature and minor refactoring of LeaderboardView; however, the Hint component interface changes to accept ReactNode alter public API beyond the tooltip's direct scope. Clarify whether the Hint component API change is necessary for this feature or if alternative approaches could limit scope to tooltip-specific modifications.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main feature added: a tooltip explaining contribution scoring on the leaderboard.
Description check ✅ Passed The description includes all required sections: a clear summary of changes, linked issue reference (#42), feature classification, and completed checklist items with a screenshot.
Linked Issues check ✅ Passed The PR implements the core requirement from issue #42: a tooltip displaying point allocation rules (PR merged +5, PR opened +2, Issue opened +1) next to the leaderboard title.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@netlify
Copy link
Copy Markdown

netlify bot commented Dec 29, 2025

Deploy Preview for cv-community-dashboard failed.

Name Link
🔨 Latest commit 5d99c27
🔍 Latest deploy log https://app.netlify.com/projects/cv-community-dashboard/deploys/695bbcc54151f600083ffd38

@Atharva7126
Copy link
Copy Markdown
Collaborator

hey @gautamsidhwani29 that is a helpful PR, one suggestion, i have already created a reusable component named hint.tsx can you replace with our tooltip, just to make the code more readable :) thanks!!

@naman79820
Copy link
Copy Markdown
Member

Hey @gautamsidhwani29 info tooltip is not responsive for mid-sized viewports, can you fix that as well please?

@gautamsidhwani29
Copy link
Copy Markdown
Author

@Atharva7126 will use the hint component for the infocard

@gautamsidhwani29
Copy link
Copy Markdown
Author

Hey @gautamsidhwani29 info tooltip is not responsive for mid-sized viewports, can you fix that as well please?

yes for sure!

@Atharva7126
Copy link
Copy Markdown
Collaborator

also one suggestion replace this with the info, it is punchy but uses higher-level vocabulary than "opened"

Heading: Point Allocation

  • Merged PRs +5 points
  • Submitted PRs +2 points
  • Issue open +1 point

Footer: Leaderboard reflects total activity score

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
components/hint.tsx (1)

25-26: Consider extracting z-index value to a theme constant.

The hardcoded z-50 class could conflict with other layered UI elements. For maintainability and consistency, consider defining z-index values in a central theme configuration.

components/PointsInfoButton.tsx (1)

17-94: Extract hardcoded color value to theme configuration.

The color #50B78B appears multiple times throughout this component (lines 29, 42, 68). For maintainability and theming consistency, define this color in your theme configuration and reference it via a CSS variable or Tailwind theme extension.

Example: Using Tailwind theme extension

In your tailwind.config.js:

+theme: {
+  extend: {
+    colors: {
+      'brand-primary': '#50B78B',
+    },
+  },
+}

Then update the component:

-className="font-semibold text-[#50B78B] text-base"
+className="font-semibold text-brand-primary text-base"
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3475c0d and 0b62994.

📒 Files selected for processing (3)
  • components/Leaderboard/LeaderboardView.tsx
  • components/PointsInfoButton.tsx
  • components/hint.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/Leaderboard/LeaderboardView.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
components/hint.tsx (1)
components/ui/tooltip.tsx (4)
  • TooltipProvider (61-61)
  • Tooltip (61-61)
  • TooltipTrigger (61-61)
  • TooltipContent (61-61)
🔇 Additional comments (5)
components/hint.tsx (2)

28-36: Conditional rendering preserves backward compatibility.

The type guard and conditional rendering correctly handle both string and ReactNode labels. String labels receive the original styled wrapper, while ReactNode labels are rendered directly, allowing custom styling.


11-11: Type change is backward-compatible, not breaking.

The label prop has been widened from string to string | React.ReactNode. This is an API expansion, not a breaking change. All existing callers passing strings (e.g., in footer.tsx and RelativeTime.tsx) remain compatible. The implementation correctly handles both types with conditional rendering. New callers can now pass ReactNode content (as in PointsInfoButton.tsx), but no existing code requires modification.

Likely an incorrect or invalid review comment.

components/PointsInfoButton.tsx (3)

6-10: LGTM!

The POINTS_CONFIG correctly defines the scoring rules specified in issue #42 (PR merged: +5, PR opened: +2, Issue opened: +1). The as const assertion provides proper type narrowing.


102-112: Desktop tooltip implementation looks good.

The desktop variant correctly uses the Hint component with PointsContent as a ReactNode label. The button includes proper accessibility attributes (aria-label, focus-visible ring) and responsive behavior.


127-132: The animation utility classes (animate-in, fade-in, slide-in-from-bottom) are properly configured in this project. The codebase uses @tailwindcss/postcss, Tailwind CSS's official v4 plugin, which includes built-in animation utilities. These classes are already being used consistently throughout the codebase in multiple UI components (tooltip.tsx, popover.tsx, select.tsx, dropdown-menu.tsx) and require no additional configuration.

Comment thread components/PointsInfoButton.tsx
Comment on lines +124 to +141
{showMobileInfo && (
<>
<div
className="fixed inset-0 bg-black/60 z-40 animate-in fade-in duration-200"
onClick={() => setShowMobileInfo(false)}
/>

<div
className="fixed bottom-0 left-0 right-0 z-50 bg-white dark:bg-zinc-900 rounded-t-2xl border-t border-zinc-200 dark:border-zinc-800 shadow-2xl animate-in slide-in-from-bottom duration-300"
role="dialog"
>
<PointsContent
isMobile
onClose={() => setShowMobileInfo(false)}
/>
</div>
</>
)}
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 | 🟠 Major

Critical accessibility gaps in mobile modal implementation.

The mobile dialog is missing several essential accessibility features:

  1. Missing aria-labelledby: The dialog should reference its title for screen readers.
  2. No keyboard dismissal: Users cannot close the modal with the Escape key.
  3. No focus management: Focus should move to the dialog when opened and return to the trigger button when closed.
  4. Backdrop not keyboard-accessible: The backdrop uses onClick which doesn't work for keyboard navigation; consider adding onKeyDown or making the backdrop non-interactive.

These issues prevent keyboard-only users from effectively using this feature.

Recommended fixes
+import { useEffect, useRef } from "react";

 export default function PointsInfoButton() {
   const [showMobileInfo, setShowMobileInfo] = useState(false);
+  const triggerRef = useRef<HTMLButtonElement>(null);
+  const dialogRef = useRef<HTMLDivElement>(null);
+
+  useEffect(() => {
+    if (showMobileInfo) {
+      dialogRef.current?.focus();
+    } else {
+      triggerRef.current?.focus();
+    }
+  }, [showMobileInfo]);
+
+  useEffect(() => {
+    const handleEscape = (e: KeyboardEvent) => {
+      if (e.key === 'Escape' && showMobileInfo) {
+        setShowMobileInfo(false);
+      }
+    };
+    document.addEventListener('keydown', handleEscape);
+    return () => document.removeEventListener('keydown', handleEscape);
+  }, [showMobileInfo]);

   return (
     <>
       {/* ... */}
       <div className="md:hidden">
         <button
+          ref={triggerRef}
           type="button"
           onClick={() => setShowMobileInfo(true)}
           className="..."
+          aria-label="Show points allocation"
         >
           ⓘ
         </button>

         {showMobileInfo && (
           <>
             <div
               className="..."
               onClick={() => setShowMobileInfo(false)}
+              aria-hidden="true"
             />

             <div
+              ref={dialogRef}
               className="..."
               role="dialog"
+              aria-labelledby="points-modal-title"
+              aria-modal="true"
+              tabIndex={-1}
             >
               <PointsContent
                 isMobile
                 onClose={() => setShowMobileInfo(false)}
               />
             </div>
           </>
         )}
       </div>
     </>
   );
 }

Committable suggestion skipped: line range outside the PR's diff.

@naman79820
Copy link
Copy Markdown
Member

Hey @gautamsidhwani29 can you please resolve coderrabbit suggestions :))

@naman79820 naman79820 self-requested a review January 2, 2026 09:49
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
components/PointsInfoButton.tsx (1)

148-156: Previous accessibility issues addressed.

The mobile button now includes the aria-label attribute. Good work implementing the feedback from the previous review.

Consider adding focus-visible:ring styling to match the desktop button for consistency:

-          className="text-lg text-[#50B78B]/70 active:text-[#50B78B] transition-colors focus:outline-none rounded-sm"
+          className="text-lg text-[#50B78B]/70 active:text-[#50B78B] transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[#50B78B]/50 rounded-sm"
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b62994 and f697f4f.

📒 Files selected for processing (1)
  • components/PointsInfoButton.tsx
🔇 Additional comments (4)
components/PointsInfoButton.tsx (4)

6-10: LGTM!

The POINTS_CONFIG constant with as const assertion provides good type safety and makes the scoring rules easily maintainable. The values align with the documented point allocation.


12-15: LGTM!

Interface is minimal and correctly typed for the component's usage.


17-95: LGTM!

The PointsContent component is well-structured with proper accessibility attributes. The mobile close button has an appropriate aria-label, and the id="points-modal-title" correctly references the modal's aria-labelledby. The conditional styling for mobile vs. desktop variants is clear and maintainable.


158-180: Accessibility improvements well implemented.

The modal now includes all essential accessibility features that were flagged in the previous review:

  • aria-labelledby="points-modal-title" correctly references the header
  • aria-modal="true" and role="dialog" are present
  • tabIndex={-1} allows programmatic focus
  • Backdrop uses aria-hidden="true"
  • Escape key handler is implemented in the effect above

Comment thread components/PointsInfoButton.tsx Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91b3cf6 and 9ea28df.

📒 Files selected for processing (1)
  • components/Leaderboard/LeaderboardView.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
components/Leaderboard/LeaderboardView.tsx (1)
components/Leaderboard/ActivityTrendChart.tsx (1)
  • ActivityTrendChart (13-211)
🔇 Additional comments (2)
components/Leaderboard/LeaderboardView.tsx (2)

241-246: LGTM! PointsInfoButton integration addresses PR objectives and responsive feedback.

The changes successfully integrate the PointsInfoButton inline with the leaderboard title, as requested in issue #42. The responsive breakpoint change from md to sm (line 241) directly addresses the mid-sized viewport concern raised by naman79820.


255-268: LGTM! Improved search bar structure and styling.

The search bar refactoring improves the component structure by properly positioning the search icon within the input using absolute positioning. The enhanced styling includes better focus states and dark mode support. The responsive breakpoint change (mdsm) aligns with the overall improvements for mid-sized viewports.

Comment thread components/Leaderboard/LeaderboardView.tsx Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
components/Leaderboard/LeaderboardView.tsx (1)

12-12: Unused import issue resolved.

The previously flagged unused Divide import has been removed. The import statement now correctly includes only the icons used in this file.

🧹 Nitpick comments (2)
components/Leaderboard/LeaderboardView.tsx (2)

257-268: Consider extracting input styles for better readability.

The search bar implementation is functionally correct and includes proper accessibility features (focus-visible ring, aria-label via Search icon). However, the Input component has a very long className string (spanning lines 264-266) that reduces readability.

Optional: Extract styles to a constant or use Tailwind's @apply

You could improve maintainability by extracting the repeated focus styles:

+ const searchInputClasses = cn(
+   "pl-9 h-9 w-full sm:w-64",
+   "bg-white dark:bg-[#07170f]",
+   "border border-[#50B78B]/60 dark:border-[#50B78B]/40",
+   "text-foreground dark:text-foreground",
+   "shadow-sm dark:shadow-none",
+   "focus-visible:ring-2 focus-visible:ring-[#50B78B] focus-visible:ring-offset-0",
+   "transition-colors"
+ );

  <Input
    type="text"
    placeholder="Search contributors..."
    value={searchQuery}
    onChange={(e) => setSearchQuery(e.target.value)}
-   className="
-     pl-9 h-9 w-full sm:w-64 bg-white dark:bg-[#07170f] border border-[#50B78B]/60 dark:border-[#50B78B]/40 text-foreground dark:text-foreground shadow-sm dark:shadow-none outline-none focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#50B78B] focus-visible:ring-offset-0 transition-colors
-   "
+   className={searchInputClasses}
  />

Also note: the multiple outline-none declarations (outline-none focus:outline-none focus-visible:outline-none) are redundant—focus-visible:ring-* already provides the appropriate focus indicator.


247-247: Remove unnecessary blank line.

There's an empty line (247) after the closing </h1> tag that doesn't follow the spacing pattern of the rest of the file.

Minor cleanup
                </h1>
-
                <p className="text-muted-foreground">
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9ea28df and 0da304b.

📒 Files selected for processing (1)
  • components/Leaderboard/LeaderboardView.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
components/Leaderboard/LeaderboardView.tsx (2)
components/PointsInfoButton.tsx (1)
  • PointsInfoButton (97-190)
components/Leaderboard/ActivityTrendChart.tsx (1)
  • ActivityTrendChart (13-211)
🔇 Additional comments (4)
components/Leaderboard/LeaderboardView.tsx (4)

418-418: LGTM: Consistent responsive breakpoint adjustment.

The card layout breakpoint change from md to sm is consistent with the header changes (line 241) and improves the mobile experience by enabling horizontal layout at 640px instead of 768px.


508-517: Activity chart hidden on mobile devices.

The ActivityTrendChart is wrapped in a hidden sm:block container, meaning it will only display on screens ≥ 640px wide. This is likely intentional to save space on mobile, but ensure this design decision aligns with the mobile UX requirements.

The conditional rendering logic correctly checks for the presence and non-empty state of entry.daily_activity before rendering the chart.


20-20: No action needed—PointsInfoButton correctly implements all requirements.

The PointsInfoButton component already uses the Hint component (line 4 of PointsInfoButton.tsx) and properly implements all requested features:

  • Hint component usage: Correctly imported and used on line 141 for desktop tooltip
  • Tooltip content: Matches expectations exactly—"Points Allocation" header, correct point allocations (+5 merged, +2 opened, +1 issue), and footer text "Leaderboard reflects total activity score"
  • Responsiveness: Properly handles mid-sized viewports with separate implementations—desktop uses Hint tooltip, mobile uses bottom sheet modal with full accessibility support

The component is fully functional and addresses all reviewer suggestions from the PR comments.


241-246: The responsive layout change from md to sm is intentional and consistent with other components in the codebase. The title and PointsInfoButton display correctly on narrow mobile screens due to proper flex containment using min-w-0 on the parent container. The PointsInfoButton is a minimal-width icon (~30-40px), and period labels ("Daily", "Weekly", "Monthly", "Yearly") fit within typical mobile viewports when rendered at text-4xl. No layout adjustments needed.

@gautamsidhwani29
Copy link
Copy Markdown
Author

@naman79820 you can have a look at this now

@naman79820
Copy link
Copy Markdown
Member

Hey @gautamsidhwani29 tooltip is still not responsive

@naman79820
Copy link
Copy Markdown
Member

also can you fix coderabbit suggestions which he gave

@naman79820
Copy link
Copy Markdown
Member

naman79820 commented Jan 3, 2026

Now after forking the latest repo, the PR merged / PR opened / Issue opened points look like this now
image

what do you think @Atharva7126 should we make it exact on tooltip or let it be like that?

@gautamsidhwani29 you can also give your thoughts in it :))

@gautamsidhwani29
Copy link
Copy Markdown
Author

I think we should Match the colors on the hint as well but at the same time i think it going to look cluttered as 3-4 colors in a hint will look something off , also @naman79820 can you point out the exact issue in the responsiveness by a screenshot
Screenshot from 2026-01-03 16-07-44
this is how it looks on phone screen and the mid and above screen have the hovering hint

@Atharva7126
Copy link
Copy Markdown
Collaborator

Atharva7126 commented Jan 3, 2026

honestly @naman79820 PR merged / PR opened / Issue opened points are looking very different from the layout of the leaderboard, the old pills were perfectly matching that

and for the tooltip it should be simple as user dont really observe that much

@naman79820
Copy link
Copy Markdown
Member

@gautamsidhwani29

Desktop.2026.01.03.-.20.09.58.02.mp4

Copy link
Copy Markdown
Member

@naman79820 naman79820 left a comment

Choose a reason for hiding this comment

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

Can you please resolve these merge conflicts as ui is abit changed now :))

@gautamsidhwani29
Copy link
Copy Markdown
Author

@coderabbitai what needs to be changed and cleaned up?

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 5, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch all review comments from coderabbitai on PR #43
gh api repos/CircuitVerse/community-dashboard/pulls/43/comments \
  --jq '.[] | select(.user.login == "coderabbitai[bot]") | {path: .path, line: .line, body: .body, created_at: .created_at}' \
  | jq -s '.'

Length of output: 8476


🏁 Script executed:

#!/bin/bash
# Also fetch review comments from the reviews endpoint
gh api repos/CircuitVerse/community-dashboard/pulls/43/reviews \
  --jq '.[] | select(.user.login == "coderabbitai[bot]") | {state: .state, body: .body, submitted_at: .submitted_at}'

Length of output: 18072


🏁 Script executed:

#!/bin/bash
# Get the current files in the PR to understand what was changed
gh pr view 43 --repo CircuitVerse/community-dashboard --json files --jq '.files[] | {path: .path, additions: .additions, deletions: .deletions}'

Length of output: 216


@gautamsidhwani29 Great job addressing most of the suggestions! Here's a summary of what you've already fixed:

✅ Already Addressed

  1. Mobile info button accessibility - Added aria-label="Show points allocation" to the mobile button
  2. Focus management - Fixed the return-focus effect that was firing on initial render
  3. Modal accessibility - Added all essential accessibility features:
    • aria-labelledby linking to the modal title
    • aria-modal="true" and role="dialog"
    • Escape key handler to close modal
    • aria-hidden="true" on backdrop
    • Proper focus management with refs
  4. Unused import - Removed the unused Divide icon import from LeaderboardView.tsx

🎨 Optional Minor Improvements (Nitpicks)

These are optional styling improvements, not critical issues:

  1. Search input styles (LeaderboardView.tsx, lines 257-268) - The className string is quite long. You could optionally extract it to a constant for better readability, but it works fine as-is.

  2. Extra blank line (LeaderboardView.tsx, line 247) - There's an empty line after the closing </h1> tag that could be removed for consistency.

  3. Color hardcoding (PointsInfoButton.tsx) - The color #50B78B appears multiple times. You could optionally define it in your Tailwind theme config, but this is a maintainability suggestion, not a bug.

Summary

All critical issues have been resolved! The remaining items are minor style/formatting suggestions that don't affect functionality. The PR is in good shape from a technical perspective. The main discussion now seems to be about the visual design with the reviewers discussing color matching for the points display.

@gautamsidhwani29 gautamsidhwani29 force-pushed the feat/tooltip-for-points-info branch from 0da304b to 839b261 Compare January 5, 2026 12:23
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

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

943-1046: Consider extracting pagination logic for testability.

The pagination UI implementation is well-crafted with good accessibility attributes and smart ellipsis handling. However, the complex inline IIFE (lines 961-1031) would benefit from extraction into a separate helper function for better testability and reusability.

🔎 Suggested refactor to extract pagination logic

Create a helper function in the same file:

function calculatePageNumbers(currentPage: number, totalPages: number): number[] {
  const pages: number[] = [];

  if (totalPages <= 7) {
    for (let i = 1; i <= totalPages; i++) {
      pages.push(i);
    }
  } else {
    pages.push(1);

    if (currentPage <= 4) {
      for (let i = 2; i <= 5; i++) {
        pages.push(i);
      }
      pages.push(-1);
      pages.push(totalPages);
    } else if (currentPage >= totalPages - 3) {
      pages.push(-1);
      for (let i = totalPages - 4; i <= totalPages; i++) {
        pages.push(i);
      }
    } else {
      pages.push(-1);
      pages.push(currentPage - 1);
      pages.push(currentPage);
      pages.push(currentPage + 1);
      pages.push(-1);
      pages.push(totalPages);
    }
  }

  return pages;
}

Then simplify the rendering:

<div className="flex items-center gap-1">
  {calculatePageNumbers(currentPage, totalPages).map((pageNum, idx) => {
    if (pageNum === -1) {
      return (
        <span key={`ellipsis-${idx}`} className="px-2 text-muted-foreground"></span>
      );
    }
    return (
      <Button
        key={pageNum}
        variant={currentPage === pageNum ? "default" : "ghost"}
        size="sm"
        onClick={() => goToPage(pageNum)}
        className={cn(
          "h-9 w-9 p-0",
          currentPage === pageNum
            ? "bg-[#50B78B] text-white hover:bg-[#50B78B]/90"
            : "hover:bg-[#50B78B]/20 hover:text-[#50B78B]"
        )}
        aria-label={`Go to page ${pageNum}`}
        aria-current={currentPage === pageNum ? "page" : undefined}
      >
        {pageNum}
      </Button>
    );
  })}
</div>

This improves testability and separates concerns between logic and presentation.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0da304b and 839b261.

📒 Files selected for processing (3)
  • components/Leaderboard/LeaderboardView.tsx
  • components/PointsInfoButton.tsx
  • components/hint.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/PointsInfoButton.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
components/Leaderboard/LeaderboardView.tsx (2)
lib/leaderboard.ts (2)
  • SortBy (3-3)
  • sortEntries (9-30)
lib/utils.ts (1)
  • cn (5-7)
🔇 Additional comments (10)
components/hint.tsx (3)

28-36: Clean conditional rendering implementation.

The type-based conditional rendering correctly handles both string and ReactNode labels. String labels maintain the previous styled appearance while ReactNode labels can provide their own styling.


11-11: API change is backward compatible and properly implemented.

The expansion of label to accept string | React.ReactNode passes verification. All existing usages remain compatible: string literals in footer.tsx and RelativeTime.tsx continue to work, while the PointsInfoButton component's React.ReactNode usage is now formally supported. The implementation correctly handles both types with a typeof check (lines 28–36), rendering strings in a styled container and passing through ReactNode directly.


25-26: z-50 is appropriate and consistent with the design system.

The z-index value of z-50 on TooltipContent aligns with the application's established stacking strategy. All overlay components in the codebase (dropdowns, popovers, tooltips, modals, etc.) consistently use z-50, indicating this is an intentional design pattern, not a conflict risk. No higher z-indices exist in the codebase that would cause stacking issues.

components/Leaderboard/LeaderboardView.tsx (7)

38-38: LGTM!

The PointsInfoButton import supports the main feature of this PR.


82-109: Improved type safety and structure.

The refactored activityStyles with explicit type annotations enhances code clarity and maintainability. The consistent addition of borderColor to all activity types is a good improvement.


111-120: Safer fallback handling with explicit defaults.

The refactored getActivityStyle now returns a well-structured fallback object instead of potentially undefined values, improving defensive programming.

The fallback icon: () => null works correctly with the conditional rendering pattern at lines 876-883 where IconComponent && ensures it's only rendered when the component exists.


497-507: Verify responsive behavior on mid-sized viewports.

The PointsInfoButton integration looks good, and the header layout adjustments are reasonable. However, PR comments indicate ongoing concerns about responsiveness on mid-sized viewports.

Based on the PR discussion, ensure the tooltip and header layout work correctly across all viewport sizes, particularly mid-sized screens (tablet range). The overflow-x-auto wrapper on line 499 suggests potential overflow issues that should be tested.

Test the following breakpoints:

  • Small mobile (< 640px)
  • Large mobile/small tablet (640px - 768px)
  • Medium tablet (768px - 1024px)
  • Desktop (1024px+)

Verify:

  1. The PointsInfoButton tooltip appears correctly and is fully visible
  2. The header title doesn't overflow or wrap awkwardly
  3. The search and filter controls remain accessible

152-212: Good standardization of URLSearchParams usage.

The consistent use of .get("key") throughout improves code readability and follows the correct URLSearchParams API pattern.


846-909: Enhanced activity breakdown rendering.

The activity breakdown rendering is well-structured with explicit type annotations, proper icon handling, and consistent styling. The priority-based sorting ensures consistent display order.


915-923: Good defensive rendering for daily activity charts.

The conditional check ensures charts only render when data exists, preventing potential rendering issues with empty datasets.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/Leaderboard/LeaderboardView.tsx (1)

372-390: Inconsistent filter clearing behavior between mobile and desktop.

The clearFilters function behaves differently on mobile vs. desktop:

  • Desktop: Clears roles, sort, and search query
  • Mobile: Only clears search query (early return on line 377)

This inconsistency means mobile users cannot clear role filters or sorting via the "Clear" button, leading to a confusing user experience.

🔎 Proposed fix: Make clearFilters consistent across devices
 const clearFilters = () => {
-  const isMobile = typeof window !== "undefined" && window.innerWidth < 640;
   const params = new URLSearchParams(searchParams.toString());
-  if (isMobile) {
-    setSearchQuery("");
-    return;
-  }
   params.delete("roles");
   params.delete("sort");
   params.delete("order");
   // Reset to page 1 when clearing filters
   params.delete("page");
   setCurrentPage(1);
   // Note: We preserve the limit param when clearing filters

   window.history.replaceState(null, "", `${pathname}?${params.toString()}`);
   setSearchQuery("");
   setSortBy("points");
 };
🧹 Nitpick comments (2)
components/Leaderboard/LeaderboardView.tsx (2)

283-287: Good defensive error handling.

The try-catch around sortEntries prevents crashes and logs errors appropriately. This ensures the UI remains functional even if sorting fails.

Optional improvement: Consider displaying a toast notification to inform users when sorting fails, rather than silently falling back to the unsorted list.


965-1038: Extract pagination page calculation to a helper function.

The IIFE-based page number calculation (lines 967-1037) is complex and difficult to maintain inline. The logic appears correct but would benefit from extraction to a named helper function for better readability and testability.

🔎 Suggested refactor

Extract to a helper above the component:

function calculatePageNumbers(currentPage: number, totalPages: number): number[] {
  const pages: number[] = [];
  
  if (totalPages <= 7) {
    for (let i = 1; i <= totalPages; i++) {
      pages.push(i);
    }
  } else {
    pages.push(1);
    
    if (currentPage <= 4) {
      for (let i = 2; i <= 5; i++) {
        pages.push(i);
      }
      pages.push(-1); // ellipsis
      pages.push(totalPages);
    } else if (currentPage >= totalPages - 3) {
      pages.push(-1);
      for (let i = totalPages - 4; i <= totalPages; i++) {
        pages.push(i);
      }
    } else {
      pages.push(-1);
      pages.push(currentPage - 1);
      pages.push(currentPage);
      pages.push(currentPage + 1);
      pages.push(-1);
      pages.push(totalPages);
    }
  }
  
  return pages;
}

Then simplify the JSX:

 <div className="flex items-center gap-1">
-  {(() => {
-    const pages: number[] = [];
-    // ... complex logic ...
-    return pages.map((pageNum, idx) => {
+  {calculatePageNumbers(currentPage, totalPages).map((pageNum, idx) => {
+    if (pageNum === -1) {
+      return (
+        <span key={`ellipsis-${idx}`} className="px-2 text-muted-foreground">
+
+        </span>
+      );
+    }
+    return (
       // ... Button render ...
-    });
-  })()}
+    );
+  })}
 </div>
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 839b261 and 5375a0a.

📒 Files selected for processing (1)
  • components/Leaderboard/LeaderboardView.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
components/Leaderboard/LeaderboardView.tsx (6)
lib/leaderboard.ts (1)
  • sortEntries (9-32)
components/PointsInfoButton.tsx (1)
  • PointsInfoButton (104-201)
lib/utils.ts (1)
  • cn (5-7)
components/Leaderboard/ActivityTrendChart.tsx (1)
  • ActivityTrendChart (13-211)
components/ui/button.tsx (1)
  • Button (60-60)
components/ui/card.tsx (2)
  • Card (85-85)
  • CardContent (91-91)
🔇 Additional comments (7)
components/Leaderboard/LeaderboardView.tsx (7)

39-39: Verify PointsInfoButton responsiveness and planned refactor to hint component.

Per PR comments, responsiveness issues on mid-sized viewports have been reported repeatedly. Additionally, a reviewer requested replacing this custom component with the existing reusable hint.tsx component for consistency.

Please confirm:

  1. The tooltip renders correctly and is responsive across all viewport sizes (especially mid-range as flagged in comments).
  2. Whether you still plan to refactor this to use the hint component as suggested by the reviewer.

If responsiveness issues persist, please test the component at various breakpoints (sm, md, lg) and ensure the mobile bottom-sheet and desktop tooltip both function correctly.


83-91: LGTM: Improved type explicitness.

The expanded type annotation makes the structure of activityStyles more explicit and readable. This is a good refactoring for maintainability.


246-262: Verify the ranking behavior change is intentional.

The ranking calculation now computes ranks within the filtered role subset rather than globally. This means when users filter by role, they see relative ranks within that role (e.g., "1st among mentors") rather than global ranks.

While this behavior seems intuitive, please confirm:

  1. This change is intentional and aligns with product requirements.
  2. Users understand that filtered rankings are relative to the filter, not absolute.

Consider adding a visual indicator (e.g., "Rank within selected role(s)") if rankings are filtered, to avoid confusion when the same contributor shows different ranks under different filters.


500-510: LGTM: Clean integration of PointsInfoButton.

The button is well-integrated into the header with appropriate responsive spacing and overflow handling. The inline-flex alignment ensures the button stays visually connected to the "Leaderboard" text.

Note: This assumes the PointsInfoButton component itself handles responsiveness correctly (as flagged in earlier comment on line 39).


921-929: LGTM: Defensive rendering of activity chart.

The conditional check ensures ActivityTrendChart only renders when data is available, preventing empty charts and potential errors. This is a good defensive coding practice.


1064-1083: LGTM: Consistent styling across leaderboard and sidebar.

The sidebar activity cards now use the same color scheme and styling as the main leaderboard activity badges, creating a cohesive visual experience. The use of cn() with activity styles ensures maintainability.


1120-1125: LGTM: Improved activity count formatting.

The singular/plural formatting ("1 activity" vs "2 activities") improves readability and polish.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/Leaderboard/LeaderboardView.tsx (1)

651-692: Remove duplicate Checkbox components.

Each role renders two separate Checkbox components:

  1. Lines 657-661: <Checkbox id={role} .../>
  2. Lines 672-680: <Checkbox id={role-${role}} .../>

The label at line 663 has htmlFor={role-${role}}, which only associates with the second checkbox. The first checkbox is orphaned and creates a confusing double-checkbox UI. Additionally, the label has a redundant key={role} attribute when it's already inside a div with the same key.

🔎 Proposed fix

Remove the first checkbox and its wrapper div, and remove the redundant key from the label:

                        <h4 className="font-medium text-sm">Role</h4>
-                        <div className="space-y-2 max-h-64 overflow-y-auto px-4">
+                        <div className="space-y-2 max-h-64 overflow-y-auto">
                          {availableRoles.map((role) => (
-                            <div
-                              key={role}
-                              className="flex items-center space-x-2"
-                            >
-                              <Checkbox
-                                id={role}
-                                checked={selectedRoles.has(role)}
-                                onCheckedChange={() => toggleRole(role)}
-                              />
                              <label
                                key={role}
                                htmlFor={`role-${role}`}
                                className={cn(
                                  "flex items-center gap-3 px-3 py-2.5 rounded-lg cursor-pointer transition-all duration-200 group",
                                  selectedRoles.has(role)
                                    ? "bg-[#50B78B]/10 border border-[#50B78B]/30 shadow-sm"
                                    : "bg-muted/30 hover:bg-muted/60 border border-transparent"
                                )}
                              >
                                <Checkbox
                                  id={`role-${role}`}
                                  checked={selectedRoles.has(role)}
                                  onCheckedChange={() => toggleRole(role)}
                                  className={cn(
                                    "data-[state=checked]:bg-[#50B78B] data-[state=checked]:border-[#50B78B] border-2",
                                    "transition-all duration-200"
                                  )}
                                />
                                <span className={cn(
                                  "text-sm font-medium flex-1 transition-colors",
                                  selectedRoles.has(role) 
                                    ? "text-[#50B78B]" 
                                    : "text-foreground group-hover:text-[#50B78B]"
                                )}>
                                  {role}
                                </span>
                              </label>
-                            ))}
-                          </div>
+                          ))}
                        </div>
🧹 Nitpick comments (2)
components/Leaderboard/LeaderboardView.tsx (2)

987-1057: Consider extracting pagination page-range logic.

The IIFE calculating which page numbers to display (lines 987-1057) is correct but complex. Extracting it into a named helper function would improve testability and readability.

Example refactor
function calculateVisiblePages(currentPage: number, totalPages: number): number[] {
  const pages: number[] = [];
  
  if (totalPages <= 7) {
    for (let i = 1; i <= totalPages; i++) {
      pages.push(i);
    }
  } else {
    pages.push(1);
    
    if (currentPage <= 4) {
      for (let i = 2; i <= 5; i++) {
        pages.push(i);
      }
      pages.push(-1);
      pages.push(totalPages);
    } else if (currentPage >= totalPages - 3) {
      pages.push(-1);
      for (let i = totalPages - 4; i <= totalPages; i++) {
        pages.push(i);
      }
    } else {
      pages.push(-1);
      pages.push(currentPage - 1);
      pages.push(currentPage);
      pages.push(currentPage + 1);
      pages.push(-1);
      pages.push(totalPages);
    }
  }
  
  return pages;
}

Then use:

{calculateVisiblePages(currentPage, totalPages).map((pageNum, idx) => {
  // ... existing render logic
})}

505-505: Consider centralizing the brand color.

The color #50B78B appears over 20 times throughout this file. Consider defining it as a CSS variable or adding it to your Tailwind theme configuration for easier maintenance.

Example approach

In tailwind.config.js:

module.exports = {
  theme: {
    extend: {
      colors: {
        'brand-primary': '#50B78B',
      }
    }
  }
}

Then replace instances like text-[#50B78B] with text-brand-primary.

Also applies to: 538-539, 546-546, 628-632, 748-748, 1045-1046

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5375a0a and 5d99c27.

📒 Files selected for processing (1)
  • components/Leaderboard/LeaderboardView.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
components/Leaderboard/LeaderboardView.tsx (3)
components/PointsInfoButton.tsx (1)
  • PointsInfoButton (104-201)
lib/leaderboard.ts (1)
  • SortBy (3-3)
lib/utils.ts (1)
  • cn (5-7)
🔇 Additional comments (5)
components/Leaderboard/LeaderboardView.tsx (5)

39-39: LGTM: Clean integration of PointsInfoButton.

The import and inline rendering of the PointsInfoButton component within the leaderboard header is well-placed and maintains semantic structure.

Also applies to: 508-508


83-91: Improved type safety.

The explicit type annotation for activityStyles enhances code clarity and type safety without altering functionality.


501-511: Verify responsive behavior on mid-sized viewports.

The PR comments repeatedly mention that the tooltip is not responsive on mid-sized viewports. While the overflow-x-auto pattern with whitespace-nowrap on small screens is a valid approach for long headings, please test thoroughly on tablet/mid-range breakpoints (768px–1024px) to ensure the info button and tooltip remain accessible and don't cause layout issues.

Based on PR comments from reviewers.


119-127: LGTM: Formatting improvement.

The explicit return wrapping is a stylistic improvement with no functional change.


598-649: Excellent refactor of Sort By UI.

The explicit button-based approach with clear option definitions improves maintainability. The state management properly coordinates popover closure, URL updates, and pagination reset. Good use of aria-pressed for accessibility.

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: Clarify leaderboard ranking logic using a tooltip indicator

3 participants