Conversation
📝 WalkthroughWalkthroughReplaces 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 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 | 🟠 MajorClose the rep edit modal on
lgbreakpoint transitions as well.Line 86 currently closes grant/NIC sheets, but not the rep modify modal state. Since
UserPageRepModifyModalportals todocument.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
📒 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.tsxcomponents/user/rep/RepCategoryPill.tsxcomponents/user/rep/UserPageRep.tsxcomponents/user/rep/UserPageRepMobile.tsxcomponents/user/rep/header/UserPageRepHeader.tsxcomponents/user/rep/new-rep/GrantRepDialog.tsxcomponents/user/rep/new-rep/UserPageRepNewRepSearch.tsxcomponents/user/rep/reps/UserPageRepReps.tsxcomponents/user/rep/reps/table/UserPageRepRepsTable.tsxcomponents/user/rep/reps/table/UserPageRepRepsTableBody.tsxcomponents/user/rep/reps/table/UserPageRepRepsTableHeader.tsxcomponents/user/rep/reps/table/UserPageRepRepsTableHeaderSortableCell.tsxcomponents/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
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
components/user/rep/header/UserPageRepHeader.tsx (1)
30-34: Extract pagination constants to avoid desktop/mobile drift.
5and10are duplicated pagination controls here and incomponents/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
📒 Files selected for processing (4)
components/user/rep/RepCategoryPill.tsxcomponents/user/rep/UserPageRepMobile.tsxcomponents/user/rep/header/UserPageRepHeader.tsxcomponents/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
|
There was a problem hiding this comment.
♻️ Duplicate comments (1)
components/user/rep/RepCategoryPill.tsx (1)
35-41:⚠️ Potential issue | 🟠 MajorAvoid nested interactive elements in editable pills.
When
canEditis 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/stopPropagationdon’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 incomponents/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
📒 Files selected for processing (5)
__tests__/components/user/rep/header/UserPageRepHeader.test.tsx__tests__/components/user/rep/new-rep/UserPageRepNewRepSearchHeader.test.tsxcomponents/user/rep/RepCategoryPill.tsxcomponents/user/rep/UserPageRepMobile.tsxcomponents/user/rep/header/UserPageRepHeader.tsx
💤 Files with no reviewable changes (1)
- tests/components/user/rep/new-rep/UserPageRepNewRepSearchHeader.test.tsx



Summary by CodeRabbit
New Features
Refactor
Tests