Skip to content

total rep categories ui/ux#1986

Merged
ragnep merged 6 commits intomainfrom
total-rep-categories
Feb 26, 2026
Merged

total rep categories ui/ux#1986
ragnep merged 6 commits intomainfrom
total-rep-categories

Conversation

@ragnep
Copy link
Copy Markdown
Contributor

@ragnep ragnep commented Feb 25, 2026

Summary by CodeRabbit

  • New Features

    • Pill-based "Rep Categories" UI showing category, rating, top raters and contributor counts.
    • New grant-rep dialog and a modal for editing reps; "Add rep" flow updated.
    • Load-more control to reveal additional categories.
  • Refactor

    • Replaced table-based rep display with card/pill layout and simplified responsive/mobile layouts.
    • Header and rep flows reorganized to use the new pill/dialog interactions; header label simplified to "Rep".
  • Tests

    • Multiple rep-related table tests removed or simplified to match the new composition.

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

coderabbitai Bot commented Feb 26, 2026

📝 Walkthrough

Walkthrough

Replaces table-based reputation UI with a pill-style category UI, adding RepCategoryPill and GrantRepDialog, removing the table components and many related tests, and updating header and mobile flows to use visibleCount, editCategory, GrantRepDialog, and UserPageRepModifyModal.

Changes

Cohort / File(s) Summary
Tests Updated / Removed
__tests__/components/user/rep/UserPageRep.test.tsx, __tests__/components/user/rep/reps/UserPageRepReps.test.tsx, __tests__/components/user/rep/reps/table/UserPageRepRepsTable.test.tsx, __tests__/components/user/rep/header/UserPageRepHeader.test.tsx, __tests__/components/user/rep/new-rep/UserPageRepNewRepSearchHeader.test.tsx
Adjusted mocks and expectations in UserPageRep tests to new component composition; removed several tests tied to deleted table-based components and updated a header text expectation.
New Components
components/user/rep/RepCategoryPill.tsx, components/user/rep/new-rep/GrantRepDialog.tsx
Added a pill UI for rep categories and a GrantRepDialog that wraps the grant-rep flow in a mobile dialog.
Mobile / Header Changes
components/user/rep/UserPageRepMobile.tsx, components/user/rep/header/UserPageRepHeader.tsx
Replaced table rendering with RepCategoryPill list, introduced visibleCount and editCategory state, added load-more and GrantRepDialog wiring, and integrated edit modal flow.
Removed Table System
components/user/rep/reps/UserPageRepReps.tsx, components/user/rep/reps/table/UserPageRepRepsTable.tsx, components/user/rep/reps/table/UserPageRepRepsTableBody.tsx, components/user/rep/reps/table/UserPageRepRepsTableHeader.tsx, components/user/rep/reps/table/UserPageRepRepsTableHeaderSortableCell.tsx, components/user/rep/reps/table/UserPageRepRepsTableItem.tsx
Deleted the entire table-based rep UI, including sorting, row/item, header, and body components and associated permission/sort logic.
Top-level Rep Page
components/user/rep/UserPageRep.tsx
Removed import and usage of the deleted UserPageRepReps and cleared related commented raters-table blocks.
Layout Tweak
components/user/rep/new-rep/UserPageRepNewRepSearch.tsx
Simplified responsive layout to a full-width vertical flow and removed some container styling.

Sequence Diagram

sequenceDiagram
    participant User
    participant Mobile as UserPageRepMobile
    participant Pill as RepCategoryPill
    participant Grant as GrantRepDialog
    participant Modify as UserPageRepModifyModal

    User->>Mobile: Open Rep tab / view categories
    Mobile->>Pill: Render visible RepCategoryPill items
    Pill->>User: Display category, rating, avatars

    alt Edit category
        User->>Pill: Click pill
        Pill->>Mobile: onEdit(category)
        Mobile->>Modify: set editCategory & open modal
        Modify->>User: Show modify modal
        User->>Modify: Submit edits
        Modify->>Mobile: Close & update state
    else Grant new rep
        User->>Mobile: Click "Add rep"
        Mobile->>Grant: open GrantRepDialog
        Grant->>User: Show grant UI (search/select)
        User->>Grant: Confirm grant
        Grant->>Mobile: onClose()
    end

    User->>Mobile: Click "+ more"
    Mobile->>Mobile: visibleCount += N
    Mobile->>Pill: Render additional pills
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • simo6529

Poem

🐰 I hopped from rows to pill-shaped light,

Categories gleam in soft delight.
Tap to grant, tap to mend,
A tiny dialog, a rabbit friend—
Hooray for rep, and hops that blend!

🚥 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 title 'total rep categories ui/ux' accurately reflects the main changes in this PR, which involve restructuring rep/rating category display with new UI components (RepCategoryPill, GrantRepDialog) and refactoring how categories are shown across mobile and desktop views.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch total-rep-categories

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

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)

86-97: ⚠️ Potential issue | 🟠 Major

Close the rep edit modal on lg breakpoint transitions as well.

Line 86 currently closes grant/NIC sheets, but not the rep modify modal state. Since UserPageRepModifyModal portals to document.body, it can stay visible after crossing into desktop layouts.

🔧 Proposed fix
   useEffect(() => {
     const mq = globalThis.matchMedia("(min-width: 1024px)");
     const handler = (e: MediaQueryListEvent) => {
       if (e.matches) {
         setIsGrantRepOpen(false);
         setIsNicRateOpen(false);
+        setEditCategory(null);
       }
     };

Also applies to: 440-446

🤖 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 86 - 97, The
media-query change handler currently closes only grant and NIC sheets
(setIsGrantRepOpen, setIsNicRateOpen) but not the rep modify modal; update the
handler inside the useEffect that creates mq to also call
setIsModifyRepOpen(false) so the UserPageRepModifyModal (which portals to
document.body) is closed when crossing into the lg breakpoint, and apply the
same addition to the duplicate handler occurrence later in the file (the block
around the other useEffect at lines ~440-446).
🤖 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/RepCategoryPill.tsx`:
- Around line 30-35: The TopRaterAvatars component is rendered inside an
editable <button> when canEdit is true, causing nested interactive elements;
update the render to pass withLinks={false} to TopRaterAvatars (both occurrences
currently inside the content block) so it does not render anchors, and remove
reliance on tw-pointer-events-none for accessibility; locate TopRaterAvatars
usage in the content block around RepCategoryPill and add the withLinks={false}
prop.

---

Outside diff comments:
In `@components/user/rep/UserPageRepMobile.tsx`:
- Around line 86-97: The media-query change handler currently closes only grant
and NIC sheets (setIsGrantRepOpen, setIsNicRateOpen) but not the rep modify
modal; update the handler inside the useEffect that creates mq to also call
setIsModifyRepOpen(false) so the UserPageRepModifyModal (which portals to
document.body) is closed when crossing into the lg breakpoint, and apply the
same addition to the duplicate handler occurrence later in the file (the block
around the other useEffect at lines ~440-446).

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4bf4488 and 2b24b17.

📒 Files selected for processing (15)
  • __tests__/components/user/rep/UserPageRep.test.tsx
  • __tests__/components/user/rep/reps/UserPageRepReps.test.tsx
  • __tests__/components/user/rep/reps/table/UserPageRepRepsTable.test.tsx
  • components/user/rep/RepCategoryPill.tsx
  • 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/UserPageRepNewRepSearch.tsx
  • components/user/rep/reps/UserPageRepReps.tsx
  • components/user/rep/reps/table/UserPageRepRepsTable.tsx
  • components/user/rep/reps/table/UserPageRepRepsTableBody.tsx
  • components/user/rep/reps/table/UserPageRepRepsTableHeader.tsx
  • components/user/rep/reps/table/UserPageRepRepsTableHeaderSortableCell.tsx
  • components/user/rep/reps/table/UserPageRepRepsTableItem.tsx
💤 Files with no reviewable changes (9)
  • components/user/rep/reps/table/UserPageRepRepsTableBody.tsx
  • tests/components/user/rep/reps/UserPageRepReps.test.tsx
  • components/user/rep/reps/UserPageRepReps.tsx
  • components/user/rep/reps/table/UserPageRepRepsTableItem.tsx
  • components/user/rep/reps/table/UserPageRepRepsTableHeader.tsx
  • tests/components/user/rep/reps/table/UserPageRepRepsTable.test.tsx
  • components/user/rep/reps/table/UserPageRepRepsTable.tsx
  • components/user/rep/reps/table/UserPageRepRepsTableHeaderSortableCell.tsx
  • components/user/rep/UserPageRep.tsx

Comment thread components/user/rep/RepCategoryPill.tsx Outdated
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 (1)
components/user/rep/header/UserPageRepHeader.tsx (1)

30-34: Extract pagination constants to avoid desktop/mobile drift.

5 and 10 are duplicated pagination controls here and in components/user/rep/UserPageRepMobile.tsx. Centralizing these values will keep behavior aligned.

Suggested refactor
+const INITIAL_VISIBLE_REP_CATEGORIES = 5;
+const LOAD_MORE_REP_CATEGORIES_STEP = 10;

-  const [visibleCount, setVisibleCount] = useState(5);
+  const [visibleCount, setVisibleCount] = useState(INITIAL_VISIBLE_REP_CATEGORIES);

   useEffect(() => {
-    setVisibleCount(5);
+    setVisibleCount(INITIAL_VISIBLE_REP_CATEGORIES);
   }, [repRates?.rating_stats]);

-                    onClick={() => setVisibleCount((prev) => prev + 10)}
+                    onClick={() =>
+                      setVisibleCount((prev) => prev + LOAD_MORE_REP_CATEGORIES_STEP)
+                    }

Also applies to: 127-127

🤖 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 30 - 34, The
hard-coded pagination numbers (5 and 10) are duplicated between
UserPageRepHeader (visibleCount/useEffect) and UserPageRepMobile; extract them
into shared constants and use those constants instead of literals to keep
desktop/mobile behavior in sync. Create two named constants (e.g.,
DEFAULT_DESKTOP_VISIBLE_COUNT and DEFAULT_MOBILE_VISIBLE_COUNT) in a shared
module or a parent component, import them into UserPageRepHeader and
UserPageRepMobile, and replace the literal 5/10 in useState, setVisibleCount
calls, and any other pagination logic so all components reference the single
source of truth.
🤖 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 89-95: The header currently hides the entire Rep Categories block
when visibleReps.length === 0, which also hides the "add" CTA that opens
GrantRepDialog; update the render condition so the CTA is reachable by either
(a) changing the wrapper conditional to render the section when
visibleReps.length > 0 OR canEditRep is true, or (b) move the add-CTA (which
triggers GrantRepDialog) out of the visibleReps-only branch and render it
whenever canEditRep is true; adjust UserPageRepHeader so the heading and CTA
appear for editors even when visibleReps is empty while preserving the existing
display of category chips when visibleReps has items.

---

Nitpick comments:
In `@components/user/rep/header/UserPageRepHeader.tsx`:
- Around line 30-34: The hard-coded pagination numbers (5 and 10) are duplicated
between UserPageRepHeader (visibleCount/useEffect) and UserPageRepMobile;
extract them into shared constants and use those constants instead of literals
to keep desktop/mobile behavior in sync. Create two named constants (e.g.,
DEFAULT_DESKTOP_VISIBLE_COUNT and DEFAULT_MOBILE_VISIBLE_COUNT) in a shared
module or a parent component, import them into UserPageRepHeader and
UserPageRepMobile, and replace the literal 5/10 in useState, setVisibleCount
calls, and any other pagination logic so all components reference the single
source of truth.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b24b17 and 82a9f8e.

📒 Files selected for processing (4)
  • components/user/rep/RepCategoryPill.tsx
  • components/user/rep/UserPageRepMobile.tsx
  • components/user/rep/header/UserPageRepHeader.tsx
  • components/user/rep/new-rep/GrantRepDialog.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • components/user/rep/RepCategoryPill.tsx
  • components/user/rep/new-rep/GrantRepDialog.tsx

Comment thread components/user/rep/header/UserPageRepHeader.tsx Outdated
Signed-off-by: ragnep <ragneinfo@gmail.com>
Signed-off-by: ragnep <ragneinfo@gmail.com>
@sonarqubecloud
Copy link
Copy Markdown

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.

♻️ Duplicate comments (1)
components/user/rep/RepCategoryPill.tsx (1)

35-41: ⚠️ Potential issue | 🟠 Major

Avoid nested interactive elements in editable pills.

When canEdit is true (Line 66), TopRaterAvatars (Line 36) is inside a <button>. If avatars render links, this creates invalid nested interactive content and breaks keyboard accessibility. pointer-events/stopPropagation don’t fix that semantic issue.

🔧 Proposed fix
-        <span className="tw-pointer-events-none desktop-hover:tw-pointer-events-auto">
+        <span>
           <TopRaterAvatars
             handleOrWallet={profileHandle}
             category={rep.category}
             count={5}
             onAvatarClick={stopPropagation}
+            withLinks={!canEdit}
           />
         </span>

Also applies to: 66-72

🤖 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 35 - 41, The
TopRaterAvatars component is being rendered inside the editable <button> when
canEdit is true, causing nested interactive elements and accessibility issues;
update RepCategoryPill so that when canEdit is true you either move
TopRaterAvatars (the instance passing handleOrWallet, category, count,
onAvatarClick) out of the <button> or render a non-interactive variant/prop on
TopRaterAvatars (e.g., disabled or plain avatars) that removes links and
keyboard focus; ensure stopPropagation remains only for click behavior and not
used to mask nested interactive semantics, and adjust the conditional rendering
around canEdit in RepCategoryPill accordingly.
🧹 Nitpick comments (2)
components/user/rep/UserPageRepMobile.tsx (1)

56-57: Consider extracting shared “visible reps” pagination logic.

This visibleCount + reset + “+more” pattern duplicates the same behavior in components/user/rep/header/UserPageRepHeader.tsx. Pulling it into a small hook/helper would reduce drift and keep desktop/mobile behavior aligned.

Also applies to: 99-101, 285-302

🤖 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 56 - 57, The
visibleCount + reset + “+more” pagination logic in UserPageRepMobile.tsx
(visibleCount, setVisibleCount) is duplicated in UserPageRepHeader.tsx; extract
it into a small reusable hook (e.g., useVisibleCount) that accepts an initial
count and total count and returns visibleCount, showMore (increments),
resetVisible, and a boolean hasMore; replace the local state and inline handlers
in both UserPageRepMobile and UserPageRepHeader with calls to this hook so both
components share the same behavior and reduce drift.
__tests__/components/user/rep/header/UserPageRepHeader.test.tsx (1)

22-23: Use a more specific assertion for the null-state render.

toHaveTextContent('Rep') is broad and may pass for unrelated content. Prefer asserting the Rep heading directly.

🧪 Suggested test tweak
-    const { container } = render(<UserPageRepHeader repRates={null} profile={mockProfile} />);
-    expect(container).toHaveTextContent('Rep');
+    render(<UserPageRepHeader repRates={null} profile={mockProfile} />);
+    expect(screen.getByRole('heading', { name: 'Rep' })).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 - 23, The test's null-state assertion is too broad; update the test in
UserPageRepHeader.test.tsx for the UserPageRepHeader component to assert the
actual Rep heading rather than any occurrence of the string — e.g.,
render(<UserPageRepHeader repRates={null} profile={mockProfile} />) then use a
more specific query like getByRole('heading', { name: /Rep/i }) or
getByText(/^Rep$/) and assert that this element is present to ensure the heading
itself is rendered.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@components/user/rep/RepCategoryPill.tsx`:
- Around line 35-41: The TopRaterAvatars component is being rendered inside the
editable <button> when canEdit is true, causing nested interactive elements and
accessibility issues; update RepCategoryPill so that when canEdit is true you
either move TopRaterAvatars (the instance passing handleOrWallet, category,
count, onAvatarClick) out of the <button> or render a non-interactive
variant/prop on TopRaterAvatars (e.g., disabled or plain avatars) that removes
links and keyboard focus; ensure stopPropagation remains only for click behavior
and not used to mask nested interactive semantics, and adjust the conditional
rendering around canEdit in RepCategoryPill accordingly.

---

Nitpick comments:
In `@__tests__/components/user/rep/header/UserPageRepHeader.test.tsx`:
- Around line 22-23: The test's null-state assertion is too broad; update the
test in UserPageRepHeader.test.tsx for the UserPageRepHeader component to assert
the actual Rep heading rather than any occurrence of the string — e.g.,
render(<UserPageRepHeader repRates={null} profile={mockProfile} />) then use a
more specific query like getByRole('heading', { name: /Rep/i }) or
getByText(/^Rep$/) and assert that this element is present to ensure the heading
itself is rendered.

In `@components/user/rep/UserPageRepMobile.tsx`:
- Around line 56-57: The visibleCount + reset + “+more” pagination logic in
UserPageRepMobile.tsx (visibleCount, setVisibleCount) is duplicated in
UserPageRepHeader.tsx; extract it into a small reusable hook (e.g.,
useVisibleCount) that accepts an initial count and total count and returns
visibleCount, showMore (increments), resetVisible, and a boolean hasMore;
replace the local state and inline handlers in both UserPageRepMobile and
UserPageRepHeader with calls to this hook so both components share the same
behavior and reduce drift.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 82a9f8e and cb3fcea.

📒 Files selected for processing (5)
  • __tests__/components/user/rep/header/UserPageRepHeader.test.tsx
  • __tests__/components/user/rep/new-rep/UserPageRepNewRepSearchHeader.test.tsx
  • components/user/rep/RepCategoryPill.tsx
  • components/user/rep/UserPageRepMobile.tsx
  • components/user/rep/header/UserPageRepHeader.tsx
💤 Files with no reviewable changes (1)
  • tests/components/user/rep/new-rep/UserPageRepNewRepSearchHeader.test.tsx

@ragnep ragnep merged commit 398fe1d into main Feb 26, 2026
7 checks passed
@ragnep ragnep deleted the total-rep-categories branch February 26, 2026 10:03
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