Skip to content

total rep received and given#2023

Merged
ragnep merged 12 commits intomainfrom
outoing-rep
Mar 2, 2026
Merged

total rep received and given#2023
ragnep merged 12 commits intomainfrom
outoing-rep

Conversation

@ragnep
Copy link
Copy Markdown
Contributor

@ragnep ragnep commented Mar 2, 2026

Summary by CodeRabbit

  • New Features

    • Added Received/Given toggle for reputation to view incoming or outgoing contributions.
    • Introduced reputation overview (total rep, contributor counts, your contribution) and categorized reputation with top contributor avatars.
    • Added CIC overview with contributor details and aggregated CIC totals.
  • Bug Fixes

    • Improved mobile dialog sizing with a customizable maximum width.

Signed-off-by: ragnep <ragneinfo@gmail.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 2, 2026

Warning

Rate limit exceeded

@ragnep has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 0 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between d2cba09 and fbe41ff.

📒 Files selected for processing (4)
  • components/user/rep/RepCategoryPill.tsx
  • components/user/rep/UserPageRep.tsx
  • components/user/rep/UserPageRepMobile.tsx
  • components/user/rep/header/UserPageRepHeader.tsx
📝 Walkthrough

Walkthrough

Refactors the reputation (REP) surface from repRates to an overview/categories model with directional support (received/given), adds CIC and REP API endpoints/types, updates components and tests to use the new shapes, introduces new React Query keys/invalidation, and adds a MobileWrapperDialog maxWidthClass prop.

Changes

Cohort / File(s) Summary
API spec & types
openapi.yaml
Added REP and CIC endpoints and schemas (ApiRepOverview, ApiRepCategory, ApiCicOverview, ApiRepDirection, pages, contributors) and added min_price/max_price params to waves leaderboard.
Query keys & invalidation
components/react-query-wrapper/ReactQueryWrapper.tsx
Added QueryKey members REP_OVERVIEW, REP_CATEGORIES, CIC_OVERVIEW and updated multiple mutation/bulk paths to invalidate these keys.
Top-level rep wiring
components/user/rep/UserPageRep.tsx
Replaced repRates/AuthContext usage with fetching repOverview/repCategories (incoming/outgoing) and cicOverview; added repDirection state and activeOverview/activeCategories selection; pass new props to header/mobile.
Header & mobile UI
components/user/rep/header/UserPageRepHeader.tsx, components/user/rep/UserPageRepMobile.tsx
Header and mobile components now accept overview, categories, cicOverview, repDirection, and onRepDirectionChange; added Received/Given toggle and direction-aware labeling and rendering.
Category components & pills
components/user/rep/RepCategoryPill.tsx, components/user/rep/UserPageRep.helpers.ts
Switched from RatingStats/rep to ApiRepCategory; added RepDirection type and getContributorLabel helper; replaced TopRaterAvatars with OverlappingAvatars and updated prop/signature shapes.
New-rep dialogs & search
components/user/rep/new-rep/GrantRepDialog.tsx, components/user/rep/new-rep/UserPageRepNewRep.tsx, components/user/rep/new-rep/UserPageRepNewRepSearch.tsx
Renamed prop repRatesoverview (ApiRepOverview) across grant/new-rep flow; passed overview to child components; MobileWrapperDialog now receives maxWidthClass in GrantRepDialog.
Mobile dialog utility
components/mobile-wrapper-dialog/MobileWrapperDialog.tsx, components/user/followers/UserPageFollowersModal.tsx
Added optional maxWidthClass?: string prop to MobileWrapperDialog and used it in panelClassNames; set maxWidthClass="md:tw-max-w-md" where applied.
Removed/merged avatar helper
components/user/rep/header/TopRaterAvatars.tsx
Deleted TopRaterAvatars component; callers now use OverlappingAvatars with precomputed avatar items.
Identity CIC changes & tests
components/user/identity/header/UserPageIdentityHeader.tsx, components/user/identity/header/UserPageIdentityHeaderCIC.tsx, __tests__/*CIC.test.tsx, __tests__/components/user/rep/header/UserPageRepHeader.test.tsx
Identity CIC components/tests updated to accept/use cicOverview (ApiCicOverview); tests adapted to new prop shapes and avatar mocks.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant Page as UserPageRep
    participant API as API
    participant Header as UserPageRepHeader
    participant Mobile as UserPageRepMobile

    User->>Page: Open profile / toggle direction
    Page->>Page: set repDirection
    alt repDirection = "received"
        Page->>API: GET /profiles/{id}/rep/overview (incoming)
        Page->>API: GET /profiles/{id}/rep/categories (incoming)
    else repDirection = "given"
        Page->>API: GET /profiles/{id}/rep/overview?direction=outgoing
        Page->>API: GET /profiles/{id}/rep/categories?direction=outgoing
    end
    Page->>API: GET /profiles/{id}/cic/overview
    API-->>Page: repOverview, repCategories, cicOverview
    Page->>Page: select activeOverview / activeCategories
    Page->>Header: pass overview, categories, repDirection, onRepDirectionChange
    Page->>Mobile: pass overview, categories, cicOverview, repDirection, onRepDirectionChange
    Header->>User: render counts, toggle
    Mobile->>User: render categories, CIC avatars
    User->>Header: click toggle
    Header->>Page: onRepDirectionChange(direction)
    Page->>Page: update state, refetch endpoints
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • #1986 — Modifies the user rep UI and category-driven components (overlaps with header/mobile/category changes).
  • #1954 — Edits the same rep and identity components and MobileWrapperDialog usage; likely overlapping edits.
  • #1687 — Refactors ReactQueryWrapper and invalidation logic; related to new query keys/invalidation changes.

Suggested reviewers

  • simo6529

Poem

🐰
Hop, I swapped old rates for categories bright,
Received and Given now toggle in light,
CIC and rep overview join the show,
Avatars overlap and new queries flow,
A tiny rabbit cheers as changes grow!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'total rep received and given' accurately summarizes the primary change: restructuring the rep system to track both received and given rep separately with directional controls.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch outoing-rep

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.

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 (6)
components/user/rep/UserPageRep.tsx (1)

40-41: Desktop and mobile views have independent repDirection states.

UserPageRep maintains its own repDirection state for desktop (passed to UserPageRepHeader), while UserPageRepMobile creates a separate local state. If a user switches between viewports (e.g., resizing the browser), the selected direction won't be synchronized.

Consider lifting the state here and passing repDirection and onRepDirectionChange to UserPageRepMobile as well for consistent UX.

♻️ Proposed fix to synchronize state
       <UserPageRepMobile
         profile={profile}
         repRates={repRates ?? null}
         initialActivityLogParams={initialActivityLogParams}
+        repDirection={repDirection}
+        onRepDirectionChange={setRepDirection}
       />

Then update UserPageRepMobile to accept and use these props instead of local state.

Also applies to: 58-62

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/rep/UserPageRep.tsx` around lines 40 - 41, UserPageRep
currently has a local repDirection state (useState<RepDirection>("received"))
that's passed to UserPageRepHeader while UserPageRepMobile maintains its own
separate state; lift repDirection and its setter (setRepDirection) in
UserPageRep and pass both repDirection and an onRepDirectionChange callback
(e.g., setRepDirection) down to UserPageRepMobile so both desktop and mobile use
the same source of truth, then remove the local useState from UserPageRepMobile
and replace its internal updates with calls to the passed onRepDirectionChange
prop (also update any initial/default handling to derive from the incoming
repDirection prop).
components/user/rep/RepGivenList.tsx (1)

41-42: Consider if fetching 200 items at once is appropriate.

The query fetches up to 200 ratings in a single request. For users with many given ratings, this could be a large payload. If this is expected to grow significantly, consider server-side pagination instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/rep/RepGivenList.tsx` around lines 41 - 42, The hardcoded
query params page: "1" and page_size: "200" in RepGivenList can return an
excessively large payload; modify RepGivenList to use server-side pagination
instead of a fixed 200 items—replace the fixed page_size with a configurable
value or state (e.g., page and pageSize in component state), fetch one page at a
time, and add UI controls (next/prev or page selector) that call the API with
the appropriate page and page_size; ensure the query code that currently uses
page and page_size reads from those state values and handles total count/hasMore
from the API to drive pagination.
components/user/rep/header/UserPageRepHeader.tsx (1)

81-103: Consider simplifying the conditional rendering for Total Rep.

The current structure has nested conditions that could be simplified. The "received" direction without repRates and the "given" direction share similar empty-state UI.

💡 Simplified structure
-            {repDirection === "received" && repRates ? (
+            {repDirection === "received" ? (
+              repRates ? (
               <div className="tw-flex tw-flex-shrink-0 tw-flex-col tw-items-end tw-text-right">
                 <div className="tw-mb-1 tw-text-xs tw-font-semibold tw-uppercase tw-tracking-wider tw-text-iron-500">
                   Total Rep
                 </div>
                 <div className="tw-text-3xl tw-font-semibold tw-leading-none tw-tracking-tight tw-text-primary-400">
                   {formatNumberWithCommas(repRates.total_rep_rating)}
                 </div>
                 <span className="tw-mt-1 tw-text-sm tw-font-normal tw-text-iron-400">
                   {formatNumberWithCommas(repRates.number_of_raters)}{" "}
                   {repRates.number_of_raters === 1 ? "rater" : "raters"}
                 </span>
               </div>
+              ) : null
             ) : (
               <div className="tw-flex tw-flex-shrink-0 tw-flex-col tw-items-end tw-text-right">
                 <div className="tw-mb-1 tw-text-xs tw-font-semibold tw-uppercase tw-tracking-wider tw-text-iron-500">
                   Total Rep
                 </div>
                 <div className="tw-text-3xl tw-font-semibold tw-leading-none tw-tracking-tight tw-text-primary-400">
-                  {repDirection === "received" ? "" : "—"}
+
                 </div>
               </div>
             )}

This removes the confusing repDirection === "received" ? "" : "—" branch since the "received" case without repRates would be handled separately.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/rep/header/UserPageRepHeader.tsx` around lines 81 - 103, In
UserPageRepHeader (component rendering Total Rep), simplify the conditional by
treating only the case where repDirection === "received" && repRates shows the
numeric UI (use formatNumberWithCommas(repRates.total_rep_rating) and
repRates.number_of_raters), and collapse all other cases into a single fallback
that renders the empty-state UI with the em dash ("—"); remove the nested
ternary that checks repDirection === "received" ? "" : "—" so the "received
without repRates" and "given" directions share the same fallback JSX.
components/user/rep/UserPageRepMobile.tsx (1)

282-308: Direction toggle UI is duplicated from UserPageRepHeader.

The Received/Given toggle buttons (lines 282-308) have nearly identical code to UserPageRepHeader (lines 106-131). Consider extracting a shared RepDirectionToggle component to reduce duplication.

💡 Example shared component
// components/user/rep/RepDirectionToggle.tsx
export default function RepDirectionToggle({
  direction,
  onChange,
  size = "sm", // "sm" for mobile, "md" for desktop
}: {
  readonly direction: RepDirection;
  readonly onChange: (direction: RepDirection) => void;
  readonly size?: "sm" | "md";
}) {
  const textClass = size === "sm" ? "tw-text-xs" : "tw-text-[13px]";
  const iconClass = size === "sm" ? "tw-h-3 tw-w-3" : "tw-h-3.5 tw-w-3.5";
  // ... shared button markup
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/rep/UserPageRepMobile.tsx` around lines 282 - 308, The
Received/Given toggle UI in UserPageRepMobile is duplicated from
UserPageRepHeader; extract a shared component (e.g., RepDirectionToggle) that
accepts props { direction: RepDirection, onChange: (d: RepDirection) => void,
size?: "sm" | "md" } and replace the inline button group in UserPageRepMobile
with <RepDirectionToggle direction={repDirection} onChange={setRepDirection}
size="sm" /> (and use size="md" where UserPageRepHeader currently renders it);
ensure the new component encapsulates the conditional classes and icons
(ArrowDownLeftIcon, ArrowUpRightIcon) and preserves aria-hidden and click
handlers so behavior and styles remain identical.
components/user/rep/RepGivenPill.tsx (1)

17-17: Zero rating is styled as negative (rose).

When rating.rating === 0, the color will be tw-text-rose-500 (same as negative values). If zero is meant to be neutral rather than negative, consider adjusting the condition.

💡 Optional fix for neutral zero styling
-  const ratingColor = rating.rating > 0 ? "tw-text-emerald-500" : "tw-text-rose-500";
+  const ratingColor =
+    rating.rating > 0
+      ? "tw-text-emerald-500"
+      : rating.rating < 0
+        ? "tw-text-rose-500"
+        : "tw-text-iron-400";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/rep/RepGivenPill.tsx` at line 17, The current ternary sets
ratingColor so zero falls into the negative style; update the condition in
RepGivenPill.tsx where ratingColor is defined (variable ratingColor, using
rating.rating) to explicitly handle three states: positive (rating.rating > 0)
-> "tw-text-emerald-500", negative (rating.rating < 0) -> "tw-text-rose-500",
and neutral (rating.rating === 0) -> a neutral class such as "tw-text-slate-500"
(or your chosen neutral token), ensuring the UI shows zero as neutral rather
than negative.
__tests__/components/user/rep/header/UserPageRepHeader.test.tsx (1)

17-17: Tests updated correctly, but consider adding coverage for new direction functionality.

The new required props repDirection and onRepDirectionChange are correctly passed to align with the updated component signature. However, both tests only cover repDirection="received", leaving the new "given" direction and the direction toggle behavior untested.

💡 Suggested additional test cases
it('calls onRepDirectionChange when direction is toggled', () => {
  const handleDirectionChange = jest.fn();
  const repRates = {
    total_rep_rating: 1500,
    number_of_raters: 25,
    rating_stats: [],
  } as any;
  render(
    <UserPageRepHeader
      repRates={repRates}
      profile={mockProfile}
      repDirection="received"
      onRepDirectionChange={handleDirectionChange}
    />
  );
  // Add interaction to trigger direction change and verify callback
});

it('renders given direction view', () => {
  const repRates = {
    total_rep_rating: 1500,
    number_of_raters: 25,
    rating_stats: [],
  } as any;
  render(
    <UserPageRepHeader
      repRates={repRates}
      profile={mockProfile}
      repDirection="given"
      onRepDirectionChange={() => {}}
    />
  );
  // Assert expected UI for "given" direction
});

Also applies to: 22-22

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__tests__/components/user/rep/header/UserPageRepHeader.test.tsx` at line 17,
Add test coverage for the new repDirection prop and toggle behavior in
__tests__/components/user/rep/header/UserPageRepHeader.test.tsx: implement a
test that renders UserPageRepHeader with repDirection="received" and a jest.fn()
for onRepDirectionChange, simulate the user interaction that toggles direction
(click the direction toggle control used by UserPageRepHeader) and assert
onRepDirectionChange was called; add a second test that renders
UserPageRepHeader with repDirection="given" (and a no-op onRepDirectionChange)
and assert the expected "given" view is shown (check for the specific label/text
or element the component renders for the given direction).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/user/rep/RepGivenList.tsx`:
- Around line 37-39: The query key uses handle.toLowerCase() but the API call in
queryFn passes the raw handle, causing inconsistent caching; update the endpoint
in the commonApiFetch call inside queryFn to use the same normalized handle
(handle.toLowerCase()) so the query key and request use identical casing (refer
to the query key construction, queryFn, and commonApiFetch call with endpoint
`profiles/${...}/rep/ratings/by-rater`).

---

Nitpick comments:
In `@__tests__/components/user/rep/header/UserPageRepHeader.test.tsx`:
- Line 17: Add test coverage for the new repDirection prop and toggle behavior
in __tests__/components/user/rep/header/UserPageRepHeader.test.tsx: implement a
test that renders UserPageRepHeader with repDirection="received" and a jest.fn()
for onRepDirectionChange, simulate the user interaction that toggles direction
(click the direction toggle control used by UserPageRepHeader) and assert
onRepDirectionChange was called; add a second test that renders
UserPageRepHeader with repDirection="given" (and a no-op onRepDirectionChange)
and assert the expected "given" view is shown (check for the specific label/text
or element the component renders for the given direction).

In `@components/user/rep/header/UserPageRepHeader.tsx`:
- Around line 81-103: In UserPageRepHeader (component rendering Total Rep),
simplify the conditional by treating only the case where repDirection ===
"received" && repRates shows the numeric UI (use
formatNumberWithCommas(repRates.total_rep_rating) and
repRates.number_of_raters), and collapse all other cases into a single fallback
that renders the empty-state UI with the em dash ("—"); remove the nested
ternary that checks repDirection === "received" ? "" : "—" so the "received
without repRates" and "given" directions share the same fallback JSX.

In `@components/user/rep/RepGivenList.tsx`:
- Around line 41-42: The hardcoded query params page: "1" and page_size: "200"
in RepGivenList can return an excessively large payload; modify RepGivenList to
use server-side pagination instead of a fixed 200 items—replace the fixed
page_size with a configurable value or state (e.g., page and pageSize in
component state), fetch one page at a time, and add UI controls (next/prev or
page selector) that call the API with the appropriate page and page_size; ensure
the query code that currently uses page and page_size reads from those state
values and handles total count/hasMore from the API to drive pagination.

In `@components/user/rep/RepGivenPill.tsx`:
- Line 17: The current ternary sets ratingColor so zero falls into the negative
style; update the condition in RepGivenPill.tsx where ratingColor is defined
(variable ratingColor, using rating.rating) to explicitly handle three states:
positive (rating.rating > 0) -> "tw-text-emerald-500", negative (rating.rating <
0) -> "tw-text-rose-500", and neutral (rating.rating === 0) -> a neutral class
such as "tw-text-slate-500" (or your chosen neutral token), ensuring the UI
shows zero as neutral rather than negative.

In `@components/user/rep/UserPageRep.tsx`:
- Around line 40-41: UserPageRep currently has a local repDirection state
(useState<RepDirection>("received")) that's passed to UserPageRepHeader while
UserPageRepMobile maintains its own separate state; lift repDirection and its
setter (setRepDirection) in UserPageRep and pass both repDirection and an
onRepDirectionChange callback (e.g., setRepDirection) down to UserPageRepMobile
so both desktop and mobile use the same source of truth, then remove the local
useState from UserPageRepMobile and replace its internal updates with calls to
the passed onRepDirectionChange prop (also update any initial/default handling
to derive from the incoming repDirection prop).

In `@components/user/rep/UserPageRepMobile.tsx`:
- Around line 282-308: The Received/Given toggle UI in UserPageRepMobile is
duplicated from UserPageRepHeader; extract a shared component (e.g.,
RepDirectionToggle) that accepts props { direction: RepDirection, onChange: (d:
RepDirection) => void, size?: "sm" | "md" } and replace the inline button group
in UserPageRepMobile with <RepDirectionToggle direction={repDirection}
onChange={setRepDirection} size="sm" /> (and use size="md" where
UserPageRepHeader currently renders it); ensure the new component encapsulates
the conditional classes and icons (ArrowDownLeftIcon, ArrowUpRightIcon) and
preserves aria-hidden and click handlers so behavior and styles remain
identical.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 46e2cc8 and 3a80cb8.

📒 Files selected for processing (9)
  • __tests__/components/user/rep/header/UserPageRepHeader.test.tsx
  • components/mobile-wrapper-dialog/MobileWrapperDialog.tsx
  • components/user/followers/UserPageFollowersModal.tsx
  • components/user/rep/RepGivenList.tsx
  • components/user/rep/RepGivenPill.tsx
  • components/user/rep/UserPageRep.tsx
  • components/user/rep/UserPageRepMobile.tsx
  • components/user/rep/header/UserPageRepHeader.tsx
  • components/user/rep/new-rep/GrantRepDialog.tsx

Comment thread components/user/rep/RepGivenList.tsx Outdated
ragnep added 2 commits March 2, 2026 15:43
Signed-off-by: ragnep <ragneinfo@gmail.com>
Signed-off-by: ragnep <ragneinfo@gmail.com>
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 (6)
components/user/rep/header/UserPageRepHeader.tsx (1)

102-127: Consider adding aria-pressed for toggle button accessibility.

The direction toggle buttons act as a toggle group. Adding aria-pressed would improve screen reader experience:

♿ Suggested accessibility improvement
             <button
               type="button"
               onClick={() => onRepDirectionChange("received")}
+              aria-pressed={repDirection === "received"}
               className={`tw-inline-flex tw-cursor-pointer tw-items-center tw-gap-1.5 tw-border-0 tw-bg-transparent tw-p-0 tw-text-[13px] tw-font-medium tw-transition-colors tw-duration-200 ${
                 repDirection === "received"
                   ? "tw-text-iron-100"
                   : "tw-text-iron-500 hover:tw-text-iron-300"
               }`}
             >

Apply similarly to the "Given" button.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/rep/header/UserPageRepHeader.tsx` around lines 102 - 127, In
UserPageRepHeader (buttons using ArrowDownLeftIcon/ArrowUpRightIcon and
onRepDirectionChange), add aria-pressed to each toggle button so screen readers
know which is active: set aria-pressed={repDirection === "received"} on the
"Received" button and aria-pressed={repDirection === "given"} on the "Given"
button (keep existing onClick and class logic unchanged); this makes the
repDirection state exposed as a pressed toggle for accessibility.
components/user/rep/UserPageRepMobile.tsx (1)

268-294: Consider extracting the direction toggle into a shared component.

The direction toggle UI (Received/Given buttons with icons) is duplicated between UserPageRepMobile and UserPageRepHeader. This could be extracted into a reusable RepDirectionToggle component to reduce duplication and ensure consistent styling.

♻️ Example extraction
// components/user/rep/RepDirectionToggle.tsx
export function RepDirectionToggle({
  direction,
  onChange,
  size = "md", // "sm" for mobile, "md" for desktop
}: {
  direction: RepDirection;
  onChange: (direction: RepDirection) => void;
  size?: "sm" | "md";
}) {
  const textSize = size === "sm" ? "tw-text-xs" : "tw-text-[13px]";
  const iconSize = size === "sm" ? "tw-h-3 tw-w-3" : "tw-h-3.5 tw-w-3.5";
  // ... shared button rendering
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/rep/UserPageRepMobile.tsx` around lines 268 - 294, Duplicate
Received/Given toggle UI in UserPageRepMobile and UserPageRepHeader should be
extracted into a reusable component; create a new RepDirectionToggle component
that accepts props { direction: RepDirection, onChange: (d: RepDirection) =>
void, size?: "sm" | "md" } and implement the shared rendering (buttons, icons,
active/inactive classes) with conditional classes based on size, then replace
the inline toggle in UserPageRepMobile and UserPageRepHeader by rendering
<RepDirectionToggle direction={repDirection} onChange={onRepDirectionChange}
size="sm" /> (or size="md" in the header) to remove duplication and keep styling
consistent.
components/user/rep/RepCategoryPill.tsx (1)

78-88: Consider explicit null check for authenticated_user_contribution.

Using !!category.authenticated_user_contribution treats both null and 0 as falsy. If the API returns 0 for an explicit zero contribution (vs null for unauthenticated/no relationship), this block won't render "My Rate: 0".

If 0 is semantically equivalent to "no contribution to display," the current logic is correct. Otherwise, consider:

-      {!!category.authenticated_user_contribution && (
+      {category.authenticated_user_contribution !== null && (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/rep/RepCategoryPill.tsx` around lines 78 - 88, The
conditional currently uses a truthy check on
category.authenticated_user_contribution which hides a valid 0 value; change the
check in RepCategoryPill (the conditional around
category.authenticated_user_contribution) to an explicit null/undefined test
(e.g., use category.authenticated_user_contribution != null or check !== null &&
!== undefined) so that 0 renders as "My Rate: 0" while still hiding
null/undefined.
openapi.yaml (3)

9618-9621: Add enum descriptions for ApiRepDirection semantics.

A short description mapping incoming/outgoing to product language (e.g., received/given) would reduce interpretation mistakes for API consumers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openapi.yaml` around lines 9618 - 9621, Update the ApiRepDirection schema to
include clear descriptions mapping the enum values to product language: add a
top-level description for ApiRepDirection and per-enum value descriptions
clarifying that "incoming" means a received report (e.g., from user/customer)
and "outgoing" means a given/sent report (e.g., from system/team). Edit the
ApiRepDirection definition (referencing the enum with values "incoming" and
"outgoing") to include these descriptive fields so API consumers can
unambiguously interpret each value.

5056-5071: Clarify min_price/max_price interaction rules in the contract.

Please document behavior for min_price > max_price and confirm whether these filters are ignored unless sort=PRICE (or rejected with 400).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openapi.yaml` around lines 5056 - 5071, Update the OpenAPI contract to
explicitly state the interaction rules for min_price and max_price: clarify that
both filters are only considered when sort=PRICE (or explicitly state if they
apply regardless), and define the behavior when min_price > max_price (either
return HTTP 400 with a validation error or ignore the filters); add this policy
to the description for the parameters min_price and max_price and document the
corresponding 400 response schema and example for the endpoint that accepts
sort=PRICE so clients know the server behavior and error format.

2913-2930: Consider documenting explicit defaults for direction, page, and page_size.

These new REP endpoints expose optional pagination/direction controls, but defaults are not explicit in the contract. Adding defaults improves client predictability and caching behavior.

Also applies to: 2952-2976, 3006-3023

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openapi.yaml` around lines 2913 - 2930, The OpenAPI parameters direction,
page, and page_size are optional but lack explicit default values; update the
parameter definitions for direction (referring to ApiRepDirection), page, and
page_size to include a default property (e.g., direction: "forward" or other
domain-appropriate enum member, page: 1, page_size: 20) so clients and caches
have predictable behavior; apply the same default additions to the other
occurrences noted (the blocks around lines referenced in the review) by adding
default fields to each parameter object and, if needed, document the chosen
defaults in the ApiRepDirection schema description.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/react-query-wrapper/ReactQueryWrapper.tsx`:
- Around line 109-111: The new QueryKey values (QueryKey.REP_OVERVIEW,
QueryKey.REP_CATEGORIES, QueryKey.CIC_OVERVIEW) are being invalidated in
onProfileCICModify, onProfileRepModify, and onIdentityBulkRate but no queries
use those keys; either implement query hooks that use these keys or remove the
invalidation calls. Add three hooks (for example useRepOverviewQuery,
useRepCategoriesQuery, useCicOverviewQuery) that call useQuery/useInfiniteQuery
with the exact QueryKey constants and the appropriate fetcher/data-shape so
invalidations take effect; alternatively delete or stop calling the invalidation
logic in onProfileCICModify, onProfileRepModify, and onIdentityBulkRate if those
queries aren’t needed. Ensure the hook names and the QueryKey enum values are
referenced verbatim so the keys match the invalidation calls.

---

Nitpick comments:
In `@components/user/rep/header/UserPageRepHeader.tsx`:
- Around line 102-127: In UserPageRepHeader (buttons using
ArrowDownLeftIcon/ArrowUpRightIcon and onRepDirectionChange), add aria-pressed
to each toggle button so screen readers know which is active: set
aria-pressed={repDirection === "received"} on the "Received" button and
aria-pressed={repDirection === "given"} on the "Given" button (keep existing
onClick and class logic unchanged); this makes the repDirection state exposed as
a pressed toggle for accessibility.

In `@components/user/rep/RepCategoryPill.tsx`:
- Around line 78-88: The conditional currently uses a truthy check on
category.authenticated_user_contribution which hides a valid 0 value; change the
check in RepCategoryPill (the conditional around
category.authenticated_user_contribution) to an explicit null/undefined test
(e.g., use category.authenticated_user_contribution != null or check !== null &&
!== undefined) so that 0 renders as "My Rate: 0" while still hiding
null/undefined.

In `@components/user/rep/UserPageRepMobile.tsx`:
- Around line 268-294: Duplicate Received/Given toggle UI in UserPageRepMobile
and UserPageRepHeader should be extracted into a reusable component; create a
new RepDirectionToggle component that accepts props { direction: RepDirection,
onChange: (d: RepDirection) => void, size?: "sm" | "md" } and implement the
shared rendering (buttons, icons, active/inactive classes) with conditional
classes based on size, then replace the inline toggle in UserPageRepMobile and
UserPageRepHeader by rendering <RepDirectionToggle direction={repDirection}
onChange={onRepDirectionChange} size="sm" /> (or size="md" in the header) to
remove duplication and keep styling consistent.

In `@openapi.yaml`:
- Around line 9618-9621: Update the ApiRepDirection schema to include clear
descriptions mapping the enum values to product language: add a top-level
description for ApiRepDirection and per-enum value descriptions clarifying that
"incoming" means a received report (e.g., from user/customer) and "outgoing"
means a given/sent report (e.g., from system/team). Edit the ApiRepDirection
definition (referencing the enum with values "incoming" and "outgoing") to
include these descriptive fields so API consumers can unambiguously interpret
each value.
- Around line 5056-5071: Update the OpenAPI contract to explicitly state the
interaction rules for min_price and max_price: clarify that both filters are
only considered when sort=PRICE (or explicitly state if they apply regardless),
and define the behavior when min_price > max_price (either return HTTP 400 with
a validation error or ignore the filters); add this policy to the description
for the parameters min_price and max_price and document the corresponding 400
response schema and example for the endpoint that accepts sort=PRICE so clients
know the server behavior and error format.
- Around line 2913-2930: The OpenAPI parameters direction, page, and page_size
are optional but lack explicit default values; update the parameter definitions
for direction (referring to ApiRepDirection), page, and page_size to include a
default property (e.g., direction: "forward" or other domain-appropriate enum
member, page: 1, page_size: 20) so clients and caches have predictable behavior;
apply the same default additions to the other occurrences noted (the blocks
around lines referenced in the review) by adding default fields to each
parameter object and, if needed, document the chosen defaults in the
ApiRepDirection schema description.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a80cb8 and 892814c.

⛔ Files ignored due to path filters (10)
  • generated/models/ApiCicContributor.ts is excluded by !**/generated/**
  • generated/models/ApiCicContributorsPage.ts is excluded by !**/generated/**
  • generated/models/ApiCicOverview.ts is excluded by !**/generated/**
  • generated/models/ApiRepCategoriesPage.ts is excluded by !**/generated/**
  • generated/models/ApiRepCategory.ts is excluded by !**/generated/**
  • generated/models/ApiRepContributor.ts is excluded by !**/generated/**
  • generated/models/ApiRepContributorsPage.ts is excluded by !**/generated/**
  • generated/models/ApiRepDirection.ts is excluded by !**/generated/**
  • generated/models/ApiRepOverview.ts is excluded by !**/generated/**
  • generated/models/ObjectSerializer.ts is excluded by !**/generated/**
📒 Files selected for processing (10)
  • components/react-query-wrapper/ReactQueryWrapper.tsx
  • components/user/rep/RepCategoryPill.tsx
  • components/user/rep/UserPageRep.helpers.ts
  • components/user/rep/UserPageRep.tsx
  • components/user/rep/UserPageRepMobile.tsx
  • components/user/rep/header/UserPageRepHeader.tsx
  • components/user/rep/new-rep/GrantRepDialog.tsx
  • components/user/rep/new-rep/UserPageRepNewRep.tsx
  • components/user/rep/new-rep/UserPageRepNewRepSearch.tsx
  • openapi.yaml
💤 Files with no reviewable changes (1)
  • components/user/rep/UserPageRep.helpers.ts

Comment thread components/react-query-wrapper/ReactQueryWrapper.tsx
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.

🧹 Nitpick comments (2)
__tests__/components/user/rep/header/UserPageRepHeader.test.tsx (1)

22-24: Strengthen the null-overview test assertion.

Line 24 only checks for 'Rep', which is too broad and may still pass when the fallback UI is broken. Assert the explicit null-state output instead.

Suggested test tightening
   it('renders without overview', () => {
-    const { container } = render(<UserPageRepHeader overview={null} categories={[]} profile={mockProfile} repDirection="received" onRepDirectionChange={() => {}} />);
-    expect(container).toHaveTextContent('Rep');
+    render(<UserPageRepHeader overview={null} categories={[]} profile={mockProfile} repDirection="received" onRepDirectionChange={() => {}} />);
+    expect(screen.getByText('Total Rep')).toBeInTheDocument();
+    expect(screen.getByText('—')).toBeInTheDocument();
+    expect(screen.queryByText('1,500')).not.toBeInTheDocument();
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__tests__/components/user/rep/header/UserPageRepHeader.test.tsx` around lines
22 - 24, The current test 'renders without overview' only checks for the broad
text 'Rep' and can pass even if the null-state UI breaks; update the test that
renders <UserPageRepHeader overview={null} ...> to assert the explicit fallback
output shown when overview is null—locate the null-state fallback string or
test-id in the UserPageRepHeader component (e.g., the fallback message or an
element like 'rep-overview-empty') and replace the generic
expect(container).toHaveTextContent('Rep') with a precise assertion (e.g.,
getByText/finding the exact fallback text or
expect(queryByTestId('rep-overview-empty')).toBeInTheDocument()) so the test
verifies the real null-state UI.
components/user/rep/UserPageRep.tsx (1)

55-60: Extract shared categories pagination params to avoid drift.

The same page/page_size/top_contributors_limit block is duplicated. A shared constant would reduce maintenance risk.

♻️ Suggested refactor
+const REP_CATEGORIES_BASE_PARAMS = {
+  page: "1",
+  page_size: "20",
+  top_contributors_limit: "5",
+} as const;
+
 const { data: repCategories } = useQuery<ApiRepCategoriesPage>({
@@
-        params: {
-          page: "1",
-          page_size: "20",
-          top_contributors_limit: "5",
-        },
+        params: REP_CATEGORIES_BASE_PARAMS,
@@
 const { data: repCategoriesGiven } = useQuery<ApiRepCategoriesPage>({
@@
-        params: {
-          direction: "outgoing",
-          page: "1",
-          page_size: "20",
-          top_contributors_limit: "5",
-        },
+        params: {
+          ...REP_CATEGORIES_BASE_PARAMS,
+          direction: "outgoing",
+        },

Also applies to: 87-92

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/rep/UserPageRep.tsx` around lines 55 - 60, Extract the
duplicated params object into a shared constant (e.g.,
CATEGORY_PAGINATION_PARAMS = { page: "1", page_size: "20",
top_contributors_limit: "5" }) and replace the inline params in both requests
that use endpoint `profiles/${user}/rep/categories` (the params blocks at the
two call sites) with that constant; ensure imports/exports or file-scope
placement keeps the constant visible to both call sites and preserves
immutability (use const or Object.freeze if needed).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@__tests__/components/user/rep/header/UserPageRepHeader.test.tsx`:
- Around line 22-24: The current test 'renders without overview' only checks for
the broad text 'Rep' and can pass even if the null-state UI breaks; update the
test that renders <UserPageRepHeader overview={null} ...> to assert the explicit
fallback output shown when overview is null—locate the null-state fallback
string or test-id in the UserPageRepHeader component (e.g., the fallback message
or an element like 'rep-overview-empty') and replace the generic
expect(container).toHaveTextContent('Rep') with a precise assertion (e.g.,
getByText/finding the exact fallback text or
expect(queryByTestId('rep-overview-empty')).toBeInTheDocument()) so the test
verifies the real null-state UI.

In `@components/user/rep/UserPageRep.tsx`:
- Around line 55-60: Extract the duplicated params object into a shared constant
(e.g., CATEGORY_PAGINATION_PARAMS = { page: "1", page_size: "20",
top_contributors_limit: "5" }) and replace the inline params in both requests
that use endpoint `profiles/${user}/rep/categories` (the params blocks at the
two call sites) with that constant; ensure imports/exports or file-scope
placement keeps the constant visible to both call sites and preserves
immutability (use const or Object.freeze if needed).

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 892814c and fbf757b.

📒 Files selected for processing (2)
  • __tests__/components/user/rep/header/UserPageRepHeader.test.tsx
  • components/user/rep/UserPageRep.tsx

ragnep added 4 commits March 2, 2026 17:07
Signed-off-by: ragnep <ragneinfo@gmail.com>
Signed-off-by: ragnep <ragneinfo@gmail.com>
Signed-off-by: ragnep <ragneinfo@gmail.com>
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: 4

Caution

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

⚠️ Outside diff range comments (1)
components/user/rep/UserPageRepMobile.tsx (1)

338-370: ⚠️ Potential issue | 🟡 Minor

Keep REP edit flow direction-safe when switching tabs.

Editing is gated to received in pills/CTA, but the modal render path (Line 471) is not direction-gated, so a previously selected category can persist across a direction change.

Suggested fix
+  useEffect(() => {
+    if (repDirection !== "received") {
+      setEditCategory(null);
+    }
+  }, [repDirection]);

...
-      {canEditRep && editCategory && (
+      {canEditRep && repDirection === "received" && editCategory && (
         <UserPageRepModifyModal
           profile={profile}
           category={editCategory}
           onClose={() => setEditCategory(null)}
         />
       )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/rep/UserPageRepMobile.tsx` around lines 338 - 370, The rep
"Grant" flow isn't direction-safe: while the CTA is only shown when repDirection
=== "received", the modal render path isn't gated so a previously-selected
category can persist after switching tabs; fix by gating the modal render and/or
clearing its state on direction change — ensure the same repDirection check used
for the button (repDirection === "received" && canEditRep) is applied before
rendering the grant-rep modal (the isGrantRepOpen modal) and/or reset
modal-related state (e.g., selectedCategory or isGrantRepOpen via
setIsGrantRepOpen) inside the repDirection change handler or an effect so the
modal cannot open with stale data when the direction switches.
🧹 Nitpick comments (1)
components/user/rep/UserPageRep.tsx (1)

47-49: Prefer explicit "incoming" direction in request params for symmetry and safety.

These queries key on incoming direction but rely on backend default behavior. Sending direction: "incoming" explicitly avoids accidental behavior drift if API defaults change later.

♻️ Proposed patch
       queryFn: async () =>
         await commonApiFetch<ApiRepOverview>({
           endpoint: `profiles/${user}/rep/overview`,
+          params: { direction: "incoming" },
         }),
@@
       queryFn: async () =>
         await commonApiFetch<ApiRepCategoriesPage>({
           endpoint: `profiles/${user}/rep/categories`,
           params: {
+            direction: "incoming",
             page: "1",
             page_size: "20",
             top_contributors_limit: "5",
           },
         }),

Also applies to: 60-67

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/rep/UserPageRep.tsx` around lines 47 - 49, The request to
fetch reputation overview uses commonApiFetch for endpoint
`profiles/${user}/rep/overview` but relies on backend defaults for direction;
update the call(s) to pass an explicit request param `{ direction: "incoming" }`
(i.e., include direction: "incoming" in the params/options passed to
commonApiFetch) so both the overview fetch and the other rep query call that
uses commonApiFetch explicitly specify incoming direction to avoid relying on
server-side defaults.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/user/rep/header/UserPageRepHeader.tsx`:
- Around line 111-140: The two buttons used to toggle repDirection ("received"
and "given") are visually stateful but don't expose their pressed state to
assistive tech; update both button elements that call onRepDirectionChange and
check repDirection to include an aria-pressed attribute set to true when
repDirection matches the button's value (e.g., repDirection === "received" for
the Received button, repDirection === "given" for the Given button) so screen
readers can detect the selected state while keeping existing className logic and
icons (ArrowDownLeftIcon, ArrowUpRightIcon) intact.

In `@components/user/rep/RepCategoryPill.tsx`:
- Around line 58-69: The RepCategoryPill component is nesting interactive
content (OverlappingAvatars, which can render Link anchors) inside a clickable
button when canEdit is true; move the avatar block out of the button so the
button and OverlappingAvatars are siblings (or render OverlappingAvatars in a
non-interactive mode if such a prop exists). Locate the JSX that renders
OverlappingAvatars (the span using stopPropagation and the onItemClick handler)
and extract it so it sits outside the button rendered by RepCategoryPill,
keeping the stopPropagation logic on the avatar click handlers to prevent parent
clicks, and ensure keyboard focus/aria behavior remains correct after the
change.
- Around line 30-31: Currently href is only set when c.profile.handle exists,
causing links to be missing for contributors who only have a wallet; update the
spread so href uses the same handle-or-wallet fallback as ariaLabel (e.g. set
href to `/${c.profile.handle ?? c.profile.primary_address}`) while preserving
ariaLabel as `c.profile.handle ?? c.profile.primary_address` in
RepCategoryPill.tsx so all contributors get a valid link.

In `@components/user/rep/UserPageRepMobile.tsx`:
- Around line 263-292: The Received/Given toggle buttons are visually stateful
but lack semantic state for assistive tech; update the two button elements that
call onRepDirectionChange and read repDirection to include an aria-pressed
attribute (e.g., aria-pressed={repDirection === "received"} for the "Received"
button and aria-pressed={repDirection === "given"} for the "Given" button) so
screen readers can detect the selected state; also ensure each button has a
clear accessible name (the existing text labels are fine) and keep the existing
onClick handlers and classes unchanged.

---

Outside diff comments:
In `@components/user/rep/UserPageRepMobile.tsx`:
- Around line 338-370: The rep "Grant" flow isn't direction-safe: while the CTA
is only shown when repDirection === "received", the modal render path isn't
gated so a previously-selected category can persist after switching tabs; fix by
gating the modal render and/or clearing its state on direction change — ensure
the same repDirection check used for the button (repDirection === "received" &&
canEditRep) is applied before rendering the grant-rep modal (the isGrantRepOpen
modal) and/or reset modal-related state (e.g., selectedCategory or
isGrantRepOpen via setIsGrantRepOpen) inside the repDirection change handler or
an effect so the modal cannot open with stale data when the direction switches.

---

Nitpick comments:
In `@components/user/rep/UserPageRep.tsx`:
- Around line 47-49: The request to fetch reputation overview uses
commonApiFetch for endpoint `profiles/${user}/rep/overview` but relies on
backend defaults for direction; update the call(s) to pass an explicit request
param `{ direction: "incoming" }` (i.e., include direction: "incoming" in the
params/options passed to commonApiFetch) so both the overview fetch and the
other rep query call that uses commonApiFetch explicitly specify incoming
direction to avoid relying on server-side defaults.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fbf757b and d2cba09.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • __tests__/components/user/identity/header/UserPageIdentityHeaderCIC.test.tsx
  • __tests__/components/user/rep/header/UserPageRepHeader.test.tsx
  • components/user/identity/header/UserPageIdentityHeader.tsx
  • components/user/identity/header/UserPageIdentityHeaderCIC.tsx
  • components/user/rep/RepCategoryPill.tsx
  • components/user/rep/UserPageRep.helpers.ts
  • components/user/rep/UserPageRep.tsx
  • components/user/rep/UserPageRepMobile.tsx
  • components/user/rep/header/TopRaterAvatars.tsx
  • components/user/rep/header/UserPageRepHeader.tsx
💤 Files with no reviewable changes (1)
  • components/user/rep/header/TopRaterAvatars.tsx

Comment thread components/user/rep/header/UserPageRepHeader.tsx
Comment thread components/user/rep/RepCategoryPill.tsx Outdated
Comment thread components/user/rep/RepCategoryPill.tsx
Comment thread components/user/rep/UserPageRepMobile.tsx
ragnep added 5 commits March 2, 2026 17:25
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Mar 2, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
4.8% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@ragnep ragnep merged commit 54ddaa1 into main Mar 2, 2026
6 of 7 checks passed
@ragnep ragnep deleted the outoing-rep branch March 2, 2026 16:06
@coderabbitai coderabbitai Bot mentioned this pull request Mar 5, 2026
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.

2 participants