Skip to content

Profile page identity rep combined#1954

Merged
ragnep merged 18 commits intomainfrom
profile-page-identity-rep-combined
Feb 23, 2026
Merged

Profile page identity rep combined#1954
ragnep merged 18 commits intomainfrom
profile-page-identity-rep-combined

Conversation

@ragnep
Copy link
Copy Markdown
Contributor

@ragnep ragnep commented Feb 19, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Added matter-based filtering for activity logs with NIC/Rep separation.
    • Enhanced reputation display with top raters and edit capabilities.
    • Added mobile-optimized rep and identity tab interface.
  • Bug Fixes

    • Fixed profile navigation routing consistency across app.
  • Design Updates

    • Redesigned activity logs from table to card-based layout.
    • Updated avatar styling to circular displays.
    • Refined typography and spacing throughout profile pages.
  • Chores

    • Removed legacy identity page tests and components.
    • Simplified profile table routing structure.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR consolidates the Identity and Rep user profile pages by removing separate Identity tab components and redirecting the /[user]/identity route to render unified Rep content that incorporates both reputation ratings and Network Identity Check (NIC) information in an integrated layout.

Changes

Cohort / File(s) Summary
Test File Deletions
__tests__/components/user/identity/*, __tests__/pages/userPageIdentity.test.tsx, __tests__/components/user/rep/...test.tsx (UserPageRepActivityLog, UserPageRepHeader, UserPageRepNewRep, UserPageRepNewRepSearch), __tests__/components/waves/groups/curation/WaveCurationGroupsSection.test.tsx
Removed entire test suites for UserPageIdentity, UserPageIdentityWrapper, UserPageIdentityActivityLog, and related Rep activity log tests; removed initProfileIdentityPage mock from WaveCurationGroupsSection context.
Component Deletions
components/user/identity/UserPageIdentity.tsx, components/user/identity/UserPageIdentityWrapper.tsx, components/user/identity/activity/UserPageIdentityActivityLog.tsx
Deleted three Identity-specific wrapper/composition components that previously orchestrated the identity tab rendering; no replacement as functionality is integrated into unified Rep page.
Page Route Configuration
app/[user]/identity/page.tsx, app/[user]/rep/page.tsx
Identity page now renders RepTab instead of IdentityTab; introduced REP_MATTER_TYPE and CIC_MATTER_TYPE constants; added initialCICGivenParams and initialCICReceivedParams initialization; Rep page updated to use new matter type constants and passes CIC params to UserPageRepWrapper.
Activity Log Refactoring
components/profile-activity/ProfileActivityLogs.tsx, components/profile-activity/list/ProfileActivityLogsList.tsx, components/profile-activity/list/items/ProfileActivityLog*.tsx (20+ files)
Extended ProfileActivityLogs with optional withMatterFilter prop and matter field in ActivityLogParams; converted table-based activity log list to div-based card layout; updated typography and color tokens across activity log item components (text-base → text-sm with md:text-md breakpoints).
Rep Page Expansion
components/user/rep/UserPageRep.tsx, components/user/rep/UserPageRepWrapper.tsx, components/user/rep/UserPageRepMobile.tsx
Restructured UserPageRep with two-column desktop layout (left: rep content, right: identity/NIC card), integrated identity header and statements; added UserPageRepMobile with tab-based mobile interface; extended UserPageRepWrapper to accept CIC params.
Rep Header & Rate Components
components/user/rep/header/UserPageRepHeader.tsx, components/user/rep/header/TopRaterAvatars.tsx, components/user/rep/header/cic-rate/UserPageIdentityHeaderCICRate.tsx, components/user/rep/header/cic-rate/UserPageIdentityHeaderCICRateStats.tsx
Added profile prop and rich gradient header with top raters display to UserPageRepHeader; new TopRaterAvatars component fetches and displays rater avatars; refactored CICRate component with tooltip/non-tooltip branching and centralized rate input; rewrote CICRateStats with items-based unified rendering.
Rep Grant/Modify Flow
components/user/rep/new-rep/UserPageRepNewRepSearch.tsx, components/user/rep/new-rep/UserPageRepNewRepSearchHeader.tsx, components/user/rep/modify-rep/UserPageRepModifyModal.tsx
Replaced onRepSearch callback with internal state-driven grant flow using React Query mutations and auth context; integrated UserPageRateInput component; simplified search mock in tests from functional callback to div-based stub.
New Utility Components
components/user/rep/UserPageCombinedActivityLog.tsx, components/user/utils/rate/UserPageRateInput.tsx, components/user/utils/rate/UserRateAdjustmentHelper.tsx
Added UserPageCombinedActivityLog wrapper handling optional matter filtering; new UserPageRateInput for numeric rate entry with icon decorations; grid layout update for UserRateAdjustmentHelper.
Identity Statements Refactoring
components/user/identity/statements/UserPageIdentityStatements.tsx, components/user/identity/statements/*/...tsx (consolidated addresses, contacts, social media, etc.)
Simplified headers from decorative icon+label blocks to compact text labels; added rightAccessory prop to header for tooltip integration; unified tooltip styling via TOOLTIP_STYLES import; restructured statement list items with right-aligned action clusters.
Identity Header Simplification
components/user/identity/header/UserPageIdentityHeader.tsx, components/user/identity/header/UserPageIdentityHeaderCIC.tsx
Removed multi-column wrapper layout; replaced icon header blocks with compact text labels; restructured NIC display to vertical stack layout; removed CIC rate wrapper (now integrated in Rep page).
Avatar & Icon Styling
components/common/OverlappingAvatars.tsx, components/user/utils/user-cic-type/icons/*.tsx
Changed avatar border-radius from rounded-md to rounded-full; added tw-align-top class to CIC icon components for vertical alignment adjustments.
Tab & Navigation Changes
components/user/layout/userTabs.config.ts, components/header/header-search/HeaderSearchModal.tsx, components/header/header-search/HeaderSearchModalItem.tsx, components/user/brain/UserPageBrainWrapper.tsx
Removed Rep tab ID from TAB_DEFINITIONS; default profile navigation redirects from rep to identity tab; updated redirect logic to point to /identity instead of /rep.
Raters Table Simplification
components/user/utils/raters-table/ProfileRatersTable*.tsx, __tests__/components/user/utils/raters-table/*, helpers/profile-logs.helpers.ts
Removed ProfileRatersTableType prop and routing logic; ProfileRatersTableItem now always routes to /{handle}/identity instead of branching by type; simplified table cell type signatures; updated activity log params conversion to map NIC matter to CIC.
Typography & Color Updates
components/profile-activity/list/items/*.tsx, components/user/utils/stats/UserStatsRow.tsx, components/user/utils/CommonProfileLink.tsx, components/utils/CommonInfoBox.tsx
Systematic text size updates from tw-text-base to tw-text-sm with md:tw-text-md responsive breakpoints; color token updates from zinc to iron palette; added optional textClassName prop to CommonProfileLink; minor SVG sizing adjustments.
Miscellaneous Updates
components/profile-activity/filter/ProfileActivityLogsFilter*.tsx, components/utils/table/paginator/CommonTablePagination.tsx, helpers/Helpers.ts, components/brain/notifications/identity-rating/NotificationIdentityRating.tsx
Renamed user prop to \_user in filter (no longer used); added showTopBorder and className props to pagination component; introduced formatNumberCompact helper; fixed profile link path to always return /identity.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • proxy-min-max-limit-display #1929: Modifies CIC/rep rating component validation logic (UserPageIdentityHeaderCICRate and UserPageRepModifyModal proxy min/max handling) that overlaps with this PR's rate component restructuring.
  • Fix no unused parameters #1695: Updates ProfileActivityLogRate and ProfileActivityLogsFilter components that are substantially refactored in this PR for matter filtering and list-based rendering.
  • Submission view redesign #1655: Introduces and exports TOOLTIP_STYLES helper from tooltip.helpers module, which this PR extensively integrates across identity statement components.

Suggested reviewers

  • prxt6529
  • simo6529

🐰 A hop, a skip, a rep display,
Where NIC and rates now dance and play,
From separate tabs to one grand view,
The profile page gets a redesigned crew!
Avatars round, the helpers align,
This unified Rep page works just fine!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Profile page identity rep combined' is vague and generic, using non-descriptive terms that don't clearly convey the main intent of the changeset. Provide a more descriptive title that clearly summarizes the primary change, such as 'Consolidate identity and rep pages into single profile view' or 'Merge identity and rep tabs into combined profile page'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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 profile-page-identity-rep-combined

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: 11

Caution

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

⚠️ Outside diff range comments (6)
components/user/utils/raters-table/ProfileRatersTableItem.tsx (1)

9-14: ⚠️ Potential issue | 🟠 Major

Incomplete refactor: type still required by the TypeScript type contract

The destructured parameter list (line 10) correctly drops type, but line 13 keeps readonly type: ProfileRatersTableType; in the interface. TypeScript uses the interface as the public contract, so all callers (ProfileRatersTableBody.tsx, test files) are still required to supply type even though the value is silently discarded. The ProfileRatersTableType import on line 5 is now dead code.

Additionally, getProfileRoute on line 25 is a trivial zero-argument function that could be inlined directly.

🛠️ Proposed fix
-import { ProfileRatersTableType } from "@/types/enums";
 import Link from "next/link";

 export default function ProfileRatersTableItem({
   rating,
 }: {
   readonly rating: RatingWithProfileInfoAndLevel;
-  readonly type: ProfileRatersTableType;
 }) {

Also consider inlining:

-  const getProfileRoute = (): string => `/${rating.handle}/rep`;
-
-  const profileRoute = getProfileRoute();
+  const profileRoute = `/${rating.handle}/rep`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/utils/raters-table/ProfileRatersTableItem.tsx` around lines 9
- 14, The props type for the ProfileRatersTableItem component is inconsistent:
remove the unused `type` prop from the component's props contract (delete
`readonly type: ProfileRatersTableType;` and drop the `ProfileRatersTableType`
import) so callers are no longer required to pass it, and update the function
signature to only accept `rating: RatingWithProfileInfoAndLevel`; also inline
the trivial zero-argument helper `getProfileRoute` by replacing its call with
the template/string expression used to build the route (e.g., use the profile
id/slug directly where `getProfileRoute()` was used).
components/user/identity/statements/consolidated-addresses/UserPageIdentityStatementsConsolidatedAddressesItem.tsx (1)

33-40: ⚠️ Potential issue | 🟡 Minor

Inconsistent stopPropagation between goToOpensea and goToEtherscan.

goToOpensea (Line 33) calls e.stopPropagation() but goToEtherscan (Line 38) does not accept the event parameter at all. If this component is nested inside a clickable parent, Etherscan clicks will bubble up. Align both handlers.

Proposed fix
-  const goToEtherscan = () => {
+  const goToEtherscan = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
+    e.stopPropagation();
     window.open(`https://etherscan.io/address/${address.wallet}`, "_blank");
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/user/identity/statements/consolidated-addresses/UserPageIdentityStatementsConsolidatedAddressesItem.tsx`
around lines 33 - 40, The click handlers are inconsistent: goToOpensea stops
propagation but goToEtherscan does not, allowing Etherscan clicks to bubble;
modify goToEtherscan to accept the event parameter (e:
React.MouseEvent<HTMLButtonElement, MouseEvent>) and call e.stopPropagation()
before opening the Etherscan URL so both handlers behave the same (ensure any
onClick uses the updated signature).
components/user/identity/statements/utils/UserPageIdentityStatementsStatement.tsx (1)

26-31: ⚠️ Potential issue | 🟠 Major

Bug: handleCopy copies the title state, which can be "Copied!".

If the user clicks the copy button while the "Copied!" feedback is still visible (within the 1-second timeout), copyToClipboard(title) will copy the literal string "Copied!" to the clipboard.

Proposed fix
   const handleCopy = () => {
-    copyToClipboard(title);
+    copyToClipboard(statement.statement_value);
     setTitle("Copied!");
     setTimeout(() => {
       setTitle(statement.statement_value);
     }, 1000);
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/user/identity/statements/utils/UserPageIdentityStatementsStatement.tsx`
around lines 26 - 31, handleCopy currently calls copyToClipboard(title) after
title has been set to "Copied!", so clicking while the feedback is visible can
copy "Copied!" instead of the real value; change handleCopy to capture the
original statement text first (e.g., const valueToCopy =
statement.statement_value or read a stable variable before calling setTitle),
call copyToClipboard(valueToCopy), then setTitle("Copied!") and restore with
setTimeout; update references in UserPageIdentityStatementsStatement (function
handleCopy, variables title and statement.statement_value) accordingly.
components/user/layout/userTabs.config.ts (2)

30-34: ⚠️ Potential issue | 🟡 Minor

Missing indentation on the collected tab's opening brace.

Line 30 has { at column 0 rather than the standard 2-space indent used by every other tab entry in TAB_DEFINITIONS.

🛠️ Proposed fix
   },
-{
+  {
     id: "collected",
     title: "Collected",
     route: "collected",
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/layout/userTabs.config.ts` around lines 30 - 34, The opening
brace for the "collected" tab in TAB_DEFINITIONS is incorrectly not indented;
update the entry for id "collected" so its `{` aligns with the other tab objects
(use the standard 2-space indent used across TAB_DEFINITIONS) to match
formatting of the surrounding entries and maintain consistent indentation for
the "collected" tab object.

18-74: ⚠️ Potential issue | 🔴 Critical

Add 301 redirect from /[user]/identity to /[user]/rep to avoid breaking existing links.

The identity tab has been cleanly removed—no lingering string references, no TAB_IDS.IDENTITY patterns, and the route file is gone. However, there is no configured 301 redirect. Without it, existing bookmarks and links to /[user]/identity will return 404 instead of redirecting to the new /[user]/rep route, damaging SEO and user experience.

Configure the redirect in next.config.ts by adding a redirects() function to sharedConfig:

redirects: async () => [
  {
    source: '/:user/identity',
    destination: '/:user/rep',
    permanent: true, // 301 redirect
  },
]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/layout/userTabs.config.ts` around lines 18 - 74, Add a 301
redirect for the removed identity tab by updating the Next.js config: in the
sharedConfig object in next.config.ts, add an async redirects() function that
returns a redirect entry mapping source '/:user/identity' to destination
'/:user/rep' with permanent: true (301). Ensure this redirect is exported as
part of the final NextConfig so requests to '/[user]/identity' are redirected to
'/[user]/rep'.
components/user/rep/new-rep/UserPageRepNewRep.tsx (1)

19-54: ⚠️ Potential issue | 🟠 Major

Remove dead code: onRepSearch, modal state, and modal rendering are unreachable.

onRepSearch (line 24) is the only place that sets isAddNewRepModalOpen to true and repToAdd to a value, but it is never called — it's no longer passed to UserPageRepNewRepSearch. This means isAddNewRepModalOpen stays false and repToAdd stays null, so the condition on line 48 is never satisfied and UserPageRepModifyModal never renders.

UserPageRepNewRepSearch now handles the rep search and grant flow internally (using onRepSelect and onGrantRep), making all of this residual state, functions, and the modal block dead code that should be removed.

Proposed fix: remove dead code
-import { useState } from "react";
-import UserPageRepModifyModal from "../modify-rep/UserPageRepModifyModal";
-import type {
-  ApiProfileRepRatesState,
-  RatingStats,
-} from "@/entities/IProfile";
+import type { ApiProfileRepRatesState } from "@/entities/IProfile";
 import UserPageRepNewRepSearch from "./UserPageRepNewRepSearch";
 import type { ApiIdentity } from "@/generated/models/ApiIdentity";
 
 export default function UserPageRepNewRep({
   profile,
   repRates,
 }: {
   readonly profile: ApiIdentity;
   readonly repRates: ApiProfileRepRatesState | null;
 }) {
-  const [isAddNewRepModalOpen, setIsAddNewRepModalOpen] =
-    useState<boolean>(false);
-
-  const [repToAdd, setRepToAdd] = useState<RatingStats | null>(null);
-
-  const onRepSearch = (repSearch: string) => {
-    const rep: RatingStats = repRates?.rating_stats.find(
-      (r) => r.category === repSearch
-    ) ?? {
-      category: repSearch,
-      rating: 0,
-      contributor_count: 0,
-      rater_contribution: 0,
-    };
-    setRepToAdd(rep);
-    setIsAddNewRepModalOpen(true);
-  };
-
-  const onCloseModal = () => {
-    setIsAddNewRepModalOpen(false);
-    setRepToAdd(null);
-  };
-
   return (
-    <>
-      <UserPageRepNewRepSearch
-        repRates={repRates}
-        profile={profile}
-      />
-      {isAddNewRepModalOpen && repToAdd && (
-        <UserPageRepModifyModal
-          profile={profile}
-          category={repToAdd.category}
-          onClose={onCloseModal}
-        />
-      )}
-    </>
+    <UserPageRepNewRepSearch
+      repRates={repRates}
+      profile={profile}
+    />
   );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/rep/new-rep/UserPageRepNewRep.tsx` around lines 19 - 54,
Remove the dead modal-related state and handlers: delete the useState
declarations for isAddNewRepModalOpen and repToAdd, remove the onRepSearch and
onCloseModal functions, and remove the conditional rendering block that mounts
<UserPageRepModifyModal ... />; keep the component rendering of
<UserPageRepNewRepSearch repRates={repRates} profile={profile} /> as-is (since
UserPageRepNewRepSearch now manages onRepSelect/onGrantRep internally) and also
remove any now-unused imports or references to those state values/handlers in
UserPageRepNewRep.tsx.
🧹 Nitpick comments (24)
components/user/utils/raters-table/ProfileRatersTableItem.tsx (1)

25-27: getProfileRoute wrapper is now a no-op abstraction

After removing the CIC/REP branching, getProfileRoute is a zero-argument function that returns a single template literal. The indirection adds cognitive overhead with no benefit.

♻️ Simplification
-  const getProfileRoute = (): string => `/${rating.handle}/rep`;
-
-  const profileRoute = getProfileRoute();
+  const profileRoute = `/${rating.handle}/rep`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/utils/raters-table/ProfileRatersTableItem.tsx` around lines
25 - 27, getProfileRoute is now a no-op wrapper that just returns
`/${rating.handle}/rep`; remove the unnecessary function and replace its usage
by defining profileRoute directly as the template literal (e.g., const
profileRoute = `/${rating.handle}/rep`), deleting the getProfileRoute
declaration; ensure any other references to getProfileRoute are updated to use
profileRoute and keep rating.handle as the source of the handle.
components/user/utils/rate/UserRateAdjustmentHelperValue.tsx (2)

47-47: tw-border-solid is redundant.

Tailwind's preflight overrides default border styles for all elements with *, ::before, ::after { border-width: 0; border-style: solid; ... }. Since border-style: solid is already set globally, tw-border-solid is a no-op alongside tw-border.

♻️ Proposed simplification
-    <div className="tw-flex tw-items-center tw-justify-between tw-px-2 tw-py-1.5 tw-bg-iron-800/50 tw-border tw-border-solid tw-border-iron-700/50 tw-rounded">
+    <div className="tw-flex tw-items-center tw-justify-between tw-px-2 tw-py-1.5 tw-bg-iron-800/50 tw-border tw-border-iron-700/50 tw-rounded">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/utils/rate/UserRateAdjustmentHelperValue.tsx` at line 47, In
UserRateAdjustmentHelperValue.tsx remove the redundant tw-border-solid token
from the className on the outer div (the div with className starting "tw-flex
tw-items-center...") since global preflight already sets border-style: solid and
tw-border alone suffices; simply delete "tw-border-solid" from that className
string to simplify the markup.

47-47: tw-border-solid is redundant alongside tw-border.

Tailwind's preflight applies border: 0 solid; globally to all elements (*, ::before, ::after, ::backdrop, ::file-selector-button), which sets border-style: solid by default. When you add the tw-border utility (which only sets border-width: 1px), the solid style is already inherited from preflight, making tw-border-solid unnecessary.

♻️ Proposed simplification
-    <div className="tw-flex tw-items-center tw-justify-between tw-px-2 tw-py-1.5 tw-bg-iron-800/50 tw-border tw-border-solid tw-border-iron-700/50 tw-rounded">
+    <div className="tw-flex tw-items-center tw-justify-between tw-px-2 tw-py-1.5 tw-bg-iron-800/50 tw-border tw-border-iron-700/50 tw-rounded">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/utils/rate/UserRateAdjustmentHelperValue.tsx` at line 47, The
CSS class list on the container div in UserRateAdjustmentHelperValue (the JSX
div with className starting "tw-flex tw-items-center ...") includes the
redundant utility tw-border-solid; remove tw-border-solid and keep tw-border
(and the existing tw-border-iron-700/50) so the visual output is unchanged while
simplifying the className.
components/user/identity/header/cic-rate/UserPageIdentityHeaderCICRateStats.tsx (2)

105-124: Same duplication of !activeProfileProxy conditions here.

Lines 105-113 and 115-124 both check !activeProfileProxy independently. Same suggestion applies — wrap in a single conditional.

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

In
`@components/user/identity/header/cic-rate/UserPageIdentityHeaderCICRateStats.tsx`
around lines 105 - 124, The two adjacent blocks rendering "Available NIC" and
"Max/min to {profile.handle}" both repeat the same condition
(!activeProfileProxy); wrap both divs in a single conditional branch (e.g., if
!activeProfileProxy) inside UserPageIdentityHeaderCICRateStats component so the
check is evaluated once, keeping the inner spans that use availableCredit,
minMaxValues.max, profile.handle and formatNumberWithCommas unchanged.

58-89: Clean tooltip-mode early return.

The compact vertical layout for tooltip mode is well-structured.

One minor observation: Lines 71-78 and 79-86 both gate on !activeProfileProxy — they could be combined into a single conditional block to reduce nesting.

Combine the two blocks
-        {!activeProfileProxy && (
-          <span className="tw-block tw-text-iron-400 tw-font-medium">
-            <span>Available NIC:</span>
-            <span className="tw-ml-1 tw-font-semibold tw-text-iron-50">
-              {formatNumberWithCommas(availableCredit)}
-            </span>
-          </span>
-        )}
-        {!activeProfileProxy && (
-          <span className="tw-block tw-text-iron-400 tw-font-medium tw-break-all">
-            <span>Max/min to {profile.handle}:</span>
-            <span className="tw-ml-1 tw-font-semibold tw-text-iron-50">
-              +/- {formatNumberWithCommas(minMaxValues.max)}
-            </span>
-          </span>
-        )}
+        {!activeProfileProxy && (
+          <>
+            <span className="tw-block tw-text-iron-400 tw-font-medium">
+              <span>Available NIC:</span>
+              <span className="tw-ml-1 tw-font-semibold tw-text-iron-50">
+                {formatNumberWithCommas(availableCredit)}
+              </span>
+            </span>
+            <span className="tw-block tw-text-iron-400 tw-font-medium tw-break-all">
+              <span>Max/min to {profile.handle}:</span>
+              <span className="tw-ml-1 tw-font-semibold tw-text-iron-50">
+                +/- {formatNumberWithCommas(minMaxValues.max)}
+              </span>
+            </span>
+          </>
+        )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/user/identity/header/cic-rate/UserPageIdentityHeaderCICRateStats.tsx`
around lines 58 - 89, The tooltip early-return block in
UserPageIdentityHeaderCICRateStats (guarded by isTooltip) has two separate
conditionals both checking !activeProfileProxy; combine them into a single
conditional that renders both the "Available NIC" and the "Max/min to
{profile.handle}" spans together to reduce duplication and nesting—update the
JSX inside the isTooltip return so one {!activeProfileProxy && (...)} block
contains both spans (using formatNumberWithCommas(availableCredit) and +/-
{formatNumberWithCommas(minMaxValues.max)} and the existing classNames and Link
usage remains unchanged).
components/user/identity/statements/consolidated-addresses/UserPageIdentityStatementsConsolidatedAddressesItem.tsx (1)

168-237: Consider extracting the repeated tooltip+icon-button pattern.

The Etherscan, Opensea, and Copy buttons each repeat the same tooltip-styling boilerplate (~18 lines each). The same pattern appears in UserPageIdentityStatementsStatement.tsx. Extracting a small IconButtonWithTooltip component would reduce duplication across both files.

Also note the ternary condition order is flipped on Line 197 (isTouchScreen ? null : ...) vs Lines 174/220 (!isTouchScreen ? ... : null). Functionally identical but worth keeping consistent for readability.

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

In
`@components/user/identity/statements/consolidated-addresses/UserPageIdentityStatementsConsolidatedAddressesItem.tsx`
around lines 168 - 237, Extract the repeated tooltip+icon-button pattern in
UserPageIdentityStatementsConsolidatedAddressesItem (and mirror in
UserPageIdentityStatementsStatement.tsx) by creating a small reusable
IconButtonWithTooltip component that accepts props: icon (ReactNode), ariaLabel,
onClick, tooltipIdSuffix (use address.wallet), tooltipText, and isTouchScreen;
replace the Etherscan/Opensea/Copy button blocks (which call goToEtherscan,
goToOpensea, handleCopy and reference address.wallet) with this component to
remove duplication and shared inline Tooltip styling, and make the touch-screen
ternary usage consistent (use !isTouchScreen ? { "data-tooltip-content": "..." }
: null everywhere).
components/user/identity/statements/UserPageIdentityStatements.tsx (1)

91-91: Vestigial xl:tw-grid-cols-5 — the sole child spans full width.

Line 91 declares xl:tw-grid-cols-5 but the only child (Line 92) uses tw-col-span-full, making the 5-column grid definition a no-op. This appears to be leftover from the previous multi-column layout. Consider simplifying to remove the unused grid columns.

Proposed cleanup
-          <div className="tw-relative tw-pb-6 lg:tw-pb-8 tw-mx-auto tw-grid tw-grid-cols-1 xl:tw-gap-x-8 xl:tw-gap-y-2 lg:tw-mx-0 xl:tw-grid-cols-5">
+          <div className="tw-relative tw-pb-6 lg:tw-pb-8 tw-mx-auto tw-grid tw-grid-cols-1 tw-gap-y-2 lg:tw-mx-0">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/identity/statements/UserPageIdentityStatements.tsx` at line
91, In UserPageIdentityStatements, remove the vestigial grid column declaration
by deleting "xl:tw-grid-cols-5" from the parent div's class list (the element
that also contains "tw-relative tw-pb-6..."); the sole child already uses
"tw-col-span-full", so either remove the grid columns or replace with a
single-column class like "xl:tw-grid-cols-1" to simplify the layout and avoid
the no-op grid definition.
components/user/identity/header/cic-rate/UserPageIdentityHeaderCICRate.tsx (1)

329-483: Significant JSX duplication between tooltip and non-tooltip branches.

The +/- SVG icon pair, the NIC input, and the Rate button are nearly identical in both branches (≈150 lines of duplicated markup). Consider extracting shared sub-elements (e.g., the icon adornment, the input field, the submit button) into small helper components or variables to reduce the surface area for future drift.

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

In `@components/user/identity/header/cic-rate/UserPageIdentityHeaderCICRate.tsx`
around lines 329 - 483, The JSX for the +/- SVG icon group, the NIC input
(id="nic-rating-input", props value={adjustedRatingStr},
onChange={onValueChange}, onBlur={adjustStrValueToMinMax}) and the Rate submit
button (type="submit", disabled={isSaveDisabled}, showing CircleLoader when
mutating) is duplicated between the isTooltip branches; extract each repeated
piece into small reusable components or render helpers (e.g., NicIconAdorn,
NicInput, NicSubmitButton) and replace the duplicated markup in both branches,
keeping UserRateAdjustmentHelper and branch-specific layout (inLineValues prop
and surrounding wrappers) intact so behavior (adjustedValue/originalValue and
mutating state) remains unchanged.
components/common/OverlappingAvatars.tsx (1)

47-48: Redundant tw-rounded-full on img element — remove the explicit one.

avatarRing (line 48) already contains tw-rounded-full, so the explicit tw-rounded-full prepended on line 68 duplicates it. Tailwind handles duplicates harmlessly, but it's noisy and could cause confusion if the class is ever changed in avatarRing.

♻️ Proposed cleanup
-            className={`tw-h-full tw-w-full tw-flex-shrink-0 tw-rounded-full ${avatarRing}`}
+            className={`tw-h-full tw-w-full tw-flex-shrink-0 ${avatarRing}`}

Also applies to: 68-68

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

In `@components/common/OverlappingAvatars.tsx` around lines 47 - 48, The avatar
image includes a duplicated class: remove the explicit "tw-rounded-full" added
directly to the img/className and rely on the shared avatarRing constant
instead; update the component in OverlappingAvatars (where avatarRing is used)
to only apply avatarRing (and any other unique classes) so the redundant
tw-rounded-full is eliminated.
components/user/rep/header/TopRaterAvatars.tsx (1)

35-47: queryKey normalizes handleOrWallet to lowercase but the endpoint URL does not.

The query key uses handleOrWallet.toLowerCase() (line 38), while the API endpoint on line 42 uses the original handleOrWallet. If the same profile is rendered with mixed-case inputs (e.g., "Alice" vs "alice"), the cache key differs and triggers a redundant network request, but both calls hit the same API resource.

♻️ Proposed fix — normalize before use
+ const normalizedHandle = handleOrWallet.toLowerCase();
+
  const { data: ratersPage } = useQuery<Page<RatingWithProfileInfoAndLevel>>({
    queryKey: [
      QueryKey.PROFILE_RATERS,
-     { handleOrWallet: handleOrWallet.toLowerCase(), category, count },
+     { handleOrWallet: normalizedHandle, category, count },
    ],
    queryFn: async () =>
      await commonApiFetch<Page<RatingWithProfileInfoAndLevel>>({
-       endpoint: `profiles/${handleOrWallet}/rep/ratings/by-rater`,
+       endpoint: `profiles/${normalizedHandle}/rep/ratings/by-rater`,
        params,
      }),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/rep/header/TopRaterAvatars.tsx` around lines 35 - 47, The
query key lowercases handleOrWallet but the API call uses the original value,
causing cache misses; normalize once and reuse it: compute a normalizedHandle
(e.g., handleOrWallet.toLowerCase()) before the useQuery call and replace
occurrences in queryKey and in the commonApiFetch endpoint string (and any
params that reference handleOrWallet) so useQuery, queryKey, endpoint, and
commonApiFetch all use the same normalizedHandle.
components/brain/notifications/identity-rating/NotificationIdentityRating.tsx (1)

42-45: getProfileLink can be flattened to a computed value.

With the /identity branch removed, the function is now just a null guard. The wrapper function adds unnecessary indirection.

♻️ Proposed simplification
-  const getProfileLink = (): string | null => {
-    if (!myHandle) return null;
-    return `/${myHandle}/rep`;
-  };
-  const linkHref = getProfileLink();
+  const linkHref = myHandle ? `/${myHandle}/rep` : null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/brain/notifications/identity-rating/NotificationIdentityRating.tsx`
around lines 42 - 45, The getProfileLink wrapper is now just a null guard and
can be inlined as a computed value: replace the getProfileLink function with a
const (e.g., profileLink) that evaluates to myHandle ? `/${myHandle}/rep` : null
so callers use profileLink directly; update any usages of getProfileLink to
reference this computed const and remove the unused function.
__tests__/components/user/rep/new-rep/UserPageRepNewRepSearch.test.tsx (1)

21-31: Consider adding a test for the mutation (rep grant) path.

The test covers dropdown render and selection but the new mutateAsync-based grant flow (triggered when a category is selected and confirmed) is left untested. Given this is the core functional change to UserPageRepNewRepSearch, a dedicated test exercising the mutateAsync call path would improve coverage.

Would you like me to draft a test case for the rep grant mutation path?

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

In `@__tests__/components/user/rep/new-rep/UserPageRepNewRepSearch.test.tsx`
around lines 21 - 31, Add a unit test for the mutateAsync-based rep grant flow
in UserPageRepNewRepSearch: mock the mutation hook (the function that returns
mutateAsync) to track calls and return a resolved promise, render
UserPageRepNewRepSearch with appropriate props, simulate typing to open the
dropdown and selecting a category (same as existing test), then simulate the
confirm action that triggers mutateAsync and assert mutateAsync is called with
the expected payload and that any success UI state or callback is invoked;
ensure useQuery mocks still supply dropdown data (e.g., data: ["cat1"]) so
selection works.
components/profile-activity/list/items/utils/ProfileActivityLogItemWrapper.tsx (1)

36-41: N+1 identity fetches per log page — bounded but worth noting.

useIdentity fires a query for every rendered log item. With a page size of 10, that's up to 10 concurrent requests. React Query's cache deduplication will coalesce requests for the same handleOrWallet, but pages with many distinct actors will still produce a burst of calls.

This is an acceptable tradeoff for showing avatars, but if you observe request spikes, consider pre-fetching profiles in the parent list component or batching identities via a bulk endpoint.

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

In
`@components/profile-activity/list/items/utils/ProfileActivityLogItemWrapper.tsx`
around lines 36 - 41, ProfileActivityLogItemWrapper currently calls useIdentity
per item (causing up to page-size concurrent identity queries); move identity
resolution to the parent list: collect all handleOrWallet values for the page in
the parent, call a bulk identity fetch (or prefetch individual identities via
React Query) to populate the cache, then pass the resolved profile or pfp down
as a prop to ProfileActivityLogItemWrapper instead of calling useIdentity inside
it; update ProfileActivityLogItemWrapper to accept a profile/pfp prop and remove
the internal useIdentity call to eliminate the N+1 per-item fetches.
components/user/utils/rate/UserPageRateInput.tsx (1)

7-12: Minor: getValueStr doesn't strip a leading zero after a minus sign (e.g., pasted "-05").

If a user pastes a value like "-05", the check val.startsWith("0") won't match because it starts with "-", so the leading zero is preserved in the display. This is a low-risk edge case since normal keyboard input is sanitized by handleChange, but for completeness:

Proposed fix
 const getValueStr = (val: string): string => {
-  if (val.length > 1 && val.startsWith("0")) {
-    return val.slice(1);
+  if (val.length > 1 && val.startsWith("0") && val !== "0") {
+    return val.slice(1);
+  }
+  if (val.length > 2 && val.startsWith("-0") && val !== "-0") {
+    return "-" + val.slice(2);
   }
   return val;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/utils/rate/UserPageRateInput.tsx` around lines 7 - 12,
getValueStr currently only strips a leading "0" when the string starts with "0",
so pasted values like "-05" keep the unwanted zero; update getValueStr to also
detect a negative sign followed by "0" (e.g., "-0...") and remove that single
zero after the "-" while preserving the "-" sign. In other words, inside
getValueStr check for val starting with "0" or starting with "-0" and return the
appropriately sliced string (for "-0..." return "-" + val.slice(2), otherwise
return val.slice(1)); keep the existing behavior for other inputs.
components/user/rep/UserPageRep.tsx (2)

18-18: Unused import: ProfileRatersTableWrapper is only referenced in commented-out code.

This will likely trigger lint warnings. Consider removing the import until the raters tables are uncommented.

Proposed fix
-import ProfileRatersTableWrapper from "../utils/raters-table/wrapper/ProfileRatersTableWrapper";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/rep/UserPageRep.tsx` at line 18, The import
ProfileRatersTableWrapper is unused (only present in commented-out code) and
will cause lint warnings; remove the import line that references
ProfileRatersTableWrapper from UserPageRep.tsx (or uncomment and use it where
intended) so the module is no longer imported unused—search for
ProfileRatersTableWrapper to locate and delete the import statement or restore
the related JSX where it should be used.

27-35: Remove or document unused raters table props.

Four props (initialRepReceivedParams, initialRepGivenParams, initialCICReceivedParams, initialCICGivenParams) are unused in active code—they only appear within TODO-commented blocks (lines 69–82 and 108–119). The entire computation chain from page.tsx through UserPageRepWrapper to here has no runtime effect.

Either:

  1. Remove these props and their upstream wiring until the tables are ready, or
  2. Keep them but add a brief // TODO note on the prop definitions to clarify intent
🤖 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 27 - 35, The four props
initialRepReceivedParams, initialRepGivenParams, initialCICReceivedParams and
initialCICGivenParams on the UserPageRep component are currently unused (only
referenced in commented TODO blocks); either remove their definitions and
upstream wiring (from page.tsx and UserPageRepWrapper) to avoid dead props, or
keep them but add a short inline TODO comment next to each prop in the
UserPageRep props block explaining they are reserved for future raters tables
(e.g., "// TODO: kept for future raters tables; currently unused"). Update
UserPageRepWrapper and page.tsx accordingly if you remove them so type/signature
changes are consistent.
__tests__/components/user/rep/new-rep/UserPageRepNewRep.test.tsx (1)

2-2: Unused import: userEvent is no longer used.

Since the test no longer simulates user interactions, this import can be removed.

Proposed fix
-import userEvent from '@testing-library/user-event';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__tests__/components/user/rep/new-rep/UserPageRepNewRep.test.tsx` at line 2,
Remove the unused import userEvent from the test file: delete the import
statement "import userEvent from '@testing-library/user-event';" in
UserPageRepNewRep.test.tsx so the file no longer imports an unused symbol and
lint errors are resolved.
components/user/rep/modify-rep/UserPageRepModifyModal.tsx (1)

258-295: Consider simplifying the remaining state+effect chains to derived values.

newRating, haveChanged, and isSaveDisabled are each stored as state and synchronized via useEffect, but they could be simple derived const values (like isValidValue now is), eliminating the extra render cycle where stale values can flash.

Illustrative simplification
-  const [newRating, setNewRating] = useState<number>(
-    getStringAsNumberOrZero(adjustedRatingStr)
-  );
-
-  useEffect(() => {
-    setNewRating(getStringAsNumberOrZero(adjustedRatingStr));
-  }, [adjustedRatingStr]);
-
-  const [haveChanged, setHaveChanged] = useState<boolean>(
-    newRating !== repState?.rater_contribution
-  );
-
-  useEffect(() => {
-    setHaveChanged(newRating !== repState?.rater_contribution);
-  }, [newRating, repState]);
-
-  const getIsSaveDisabled = (): boolean => { ... };
-
-  const [isSaveDisabled, setIsSaveDisabled] = useState<boolean>(
-    getIsSaveDisabled()
-  );
-
-  useEffect(() => {
-    setIsSaveDisabled(getIsSaveDisabled());
-  }, [haveChanged, isValidValue, repState]);
+  const newRating = getStringAsNumberOrZero(adjustedRatingStr);
+  const haveChanged = newRating !== repState?.rater_contribution;
+  const isSaveDisabled = !repState || !haveChanged || !isValidValue;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/rep/modify-rep/UserPageRepModifyModal.tsx` around lines 258 -
295, Replace the state+effect chains for newRating, haveChanged and
isSaveDisabled with derived constants: compute newRating directly from
adjustedRatingStr (like const newRating =
getStringAsNumberOrZero(adjustedRatingStr)), compute haveChanged as newRating
!== repState?.rater_contribution, and compute isSaveDisabled by calling the
existing getIsSaveDisabled logic or inlining that predicate using repState,
haveChanged and isValidValue; remove the corresponding useState and useEffect
declarations and any setters (setNewRating, setHaveChanged, setIsSaveDisabled)
so these values are pure derived values and won’t cause extra renders or stale
flashes.
components/user/rep/new-rep/UserPageRepNewRepSearch.tsx (2)

241-253: amountStr pre-fill effect can cause subtle UX issues.

When repState changes (e.g., query data refetches in the background), lines 241-245 overwrite amountStr — even if the user is actively editing it. Consider guarding with a ref that tracks whether the user has manually interacted with the input.

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

In `@components/user/rep/new-rep/UserPageRepNewRepSearch.tsx` around lines 241 -
253, The current useEffect that sets amountStr from repState (useEffect ->
setAmountStr when repState changes) overwrites user edits; update it to only
pre-fill when the user has not interacted with the input by adding a ref/flag
(e.g., hasTouchedAmountRef) that is set when the user focuses/changes the amount
input, and guard the effect to run setAmountStr only if hasTouchedAmountRef is
false; ensure the flag is referenced in the effect and reset appropriately when
starting a new form or when repState truly represents a fresh record.

185-238: Derived state via useState + useEffect — prefer useMemo for computed values.

repState, proxyAvailableCredit, heroAvailableRep, and minMaxValues are all purely derived from other state/query data. Using useState + useEffect introduces an extra render cycle where these values are stale (the render that triggers the effect sees old derived state). useMemo computes synchronously during render, avoiding this.

♻️ Example refactor for derived state
-  const [repState, setRepState] = useState<RatingStats | null>(getRepState());
-  const [proxyAvailableCredit, setProxyAvailableCredit] = useState<
-    number | null
-  >(getProxyAvailableCredit());
-  const [heroAvailableRep, setHeroAvailableRep] = useState<number>(
-    getHeroAvailableRep()
-  );
-
-  useEffect(() => {
-    setRepState(getRepState());
-  }, [
-    selectedCategory,
-    proxyGrantorRepRates,
-    activeProfileProxy,
-    connectedProfileRepRates,
-  ]);
-
-  useEffect(
-    () => setProxyAvailableCredit(getProxyAvailableCredit()),
-    [activeProfileProxy]
-  );
-
-  useEffect(
-    () => setHeroAvailableRep(getHeroAvailableRep()),
-    [activeProfileProxy, proxyGrantorRepRates, connectedProfileRepRates]
-  );
+  const repState = useMemo(() => getRepState(), [
+    selectedCategory,
+    proxyGrantorRepRates,
+    activeProfileProxy,
+    connectedProfileRepRates,
+  ]);
+
+  const proxyAvailableCredit = useMemo(
+    () => getProxyAvailableCredit(),
+    [activeProfileProxy]
+  );
+
+  const heroAvailableRep = useMemo(
+    () => getHeroAvailableRep(),
+    [activeProfileProxy, proxyGrantorRepRates, connectedProfileRepRates]
+  );

Similarly for minMaxValues:

-  const [minMaxValues, setMinMaxValues] = useState<{...}>(...);
-  useEffect(
-    () => setMinMaxValues({ min: getMinValue(), max: getMaxValue() }),
-    [repState, proxyGrantorRepRates, proxyAvailableCredit, heroAvailableRep]
-  );
+  const minMaxValues = useMemo(
+    () => ({ min: getMinValue(), max: getMaxValue() }),
+    [repState, proxyAvailableCredit, heroAvailableRep]
+  );

Note: useMemo would need to be added to the useRef, useState import from react and the helper functions (getRepState, etc.) would need to be stabilized with useCallback or inlined into the useMemo.

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

In `@components/user/rep/new-rep/UserPageRepNewRepSearch.tsx` around lines 185 -
238, Replace derived state useState/useEffect patterns with synchronous useMemo
(and useCallback if needed) so values aren't stale: compute repState via useMemo
calling getRepState(), proxyAvailableCredit via useMemo calling
getProxyAvailableCredit(), and heroAvailableRep via useMemo calling
getHeroAvailableRep(); then compute minMaxValues with a useMemo that calls the
existing getMinValue/getMaxValue logic (or inline those computations) and
depends on repState, proxyAvailableCredit, heroAvailableRep,
proxyGrantorRepRates, and connectedProfileRepRates. Ensure any helper functions
used inside useMemo are stable (wrap
getRepState/getProxyAvailableCredit/getHeroAvailableRep in useCallback or inline
their logic) and remove the corresponding useState/useEffect blocks for
repState, proxyAvailableCredit, heroAvailableRep, and minMaxValues.
components/user/rep/reps/table/UserPageRepRepsTable.tsx (1)

64-91: Pre-existing: sortReps mutates the reps prop array in place.

Array.prototype.sort() sorts in place. Since items is the reps prop passed directly from the parent, this mutates the parent's data. Consider spreading before sorting: [...items].sort(...). This is a pre-existing issue but now more impactful since the same reps array is also used for maxRep computation and hasMore check.

♻️ Proposed fix
   const sortReps = (
     items: RatingStats[],
     sort: RepsTableSort,
     dir: SortDirection
   ): RatingStats[] => {
+    const sorted = [...items];
     switch (sort) {
       case RepsTableSort.REP:
-        return items.sort((a, d) =>
+        return sorted.sort((a, d) =>
           compareValuesForSort(a.rating, d.rating, dir)
         );
       case RepsTableSort.RATERS:
-        return items.sort((a, d) =>
+        return sorted.sort((a, d) =>
           compareValuesForSort(a.contributor_count, d.contributor_count, dir)
         );
       case RepsTableSort.MY_RATES:
-        return items.sort((a, d) =>
+        return sorted.sort((a, d) =>
           compareValuesForSort(
             a.rater_contribution,
             d.rater_contribution,
             dir,
             true
           )
         );
       default:
         assertUnreachable(sort);
-        return items;
+        return sorted;
     }
   };

Also applies to: 93-99

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

In `@components/user/rep/reps/table/UserPageRepRepsTable.tsx` around lines 64 -
91, sortReps currently calls Array.prototype.sort which mutates the incoming
items (the reps prop); to fix, ensure you sort a shallow copy instead of the
original by spreading items (e.g., use [...items].sort(...)) inside sortReps so
the parent prop isn't mutated; apply the same non-mutating change to any other
in-place sort usages in this file (the other sort block referenced around the
second sort usage) and keep using compareValuesForSort, RepsTableSort, and
RatingStats types unchanged.
components/user/rep/reps/table/UserPageRepRepsTableItem.tsx (1)

9-21: Minor rounding edge case in formatCompact.

When abs is between ~999,950 and 999,999, (abs / 1_000).toFixed(1) rounds up to "1000.0", producing "1000.0K" instead of "1M". Consider using Intl.NumberFormat with notation: "compact" for robust compact formatting, or add a re-check after rounding.

♻️ Suggested fix using Intl.NumberFormat
 function formatCompact(n: number): string {
-  const abs = Math.abs(n);
-  const sign = n < 0 ? "-" : "";
-  if (abs >= 1_000_000) {
-    const m = abs / 1_000_000;
-    return sign + (m % 1 === 0 ? m.toFixed(0) : m.toFixed(1)) + "M";
-  }
-  if (abs >= 1_000) {
-    const k = abs / 1_000;
-    return sign + (k % 1 === 0 ? k.toFixed(0) : k.toFixed(1)) + "K";
-  }
-  return formatNumberWithCommas(n);
+  const abs = Math.abs(n);
+  if (abs >= 1_000) {
+    return new Intl.NumberFormat("en-US", {
+      notation: "compact",
+      maximumFractionDigits: 1,
+    }).format(n);
+  }
+  return formatNumberWithCommas(n);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/rep/reps/table/UserPageRepRepsTableItem.tsx` around lines 9 -
21, The formatCompact function has a rounding edge-case where values like
999,950 become "1000.0K" instead of "1M"; update formatCompact to use
Intl.NumberFormat with { notation: "compact", compactDisplay: "short",
maximumFractionDigits: 1 } (preserve the negative sign logic) to produce correct
compact strings, and fall back to formatNumberWithCommas for numbers below
1_000; ensure the function still handles exact integer compact values and
preserves existing behavior for negatives by computing sign separately before
formatting.
components/user/rep/header/UserPageRepHeader.tsx (2)

4-4: Unused imports: AnimatePresence and CommonAnimationOpacity.

Neither is referenced in the JSX. Remove them to avoid confusion.

-import { AnimatePresence } from "framer-motion";
-import CommonAnimationOpacity from "@/components/utils/animation/CommonAnimationOpacity";

Also applies to: 13-13

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

In `@components/user/rep/header/UserPageRepHeader.tsx` at line 4, Unused imports
AnimatePresence and CommonAnimationOpacity are present in UserPageRepHeader.tsx;
remove the import statements that reference AnimatePresence (from
"framer-motion") and CommonAnimationOpacity (wherever it's imported) and delete
any dead references or JSX wrappers that would only be used by those symbols so
the component contains only the actually used imports and elements (search for
AnimatePresence and CommonAnimationOpacity in UserPageRepHeader and remove those
lines/wrappers).

50-58: Consider useMemo instead of useState + useEffect for derived data.

topReps is a pure derivation of repRates?.rating_stats. The current pattern (state + effect) causes an extra render cycle with stale data before the effect fires. A useMemo avoids this:

-const [topReps, setTopReps] = useState<RatingStats[]>(
-  sortReps(repRates?.rating_stats ?? []).slice(0, TOP_REPS_COUNT)
-);
-
-useEffect(() => {
-  setTopReps(
-    sortReps(repRates?.rating_stats ?? []).slice(0, TOP_REPS_COUNT)
-  );
-}, [repRates?.rating_stats]);
+const topReps = useMemo(
+  () => sortReps(repRates?.rating_stats ?? []).slice(0, TOP_REPS_COUNT),
+  [repRates?.rating_stats]
+);

The same pattern applies to canEditRep (lines 85-99) once the dependency fix above is in place.

🤖 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 50 - 58,
Replace the state + effect pattern for computing topReps with a memoized value:
compute topReps using useMemo that returns sortReps(repRates?.rating_stats ??
[]).slice(0, TOP_REPS_COUNT) and depends on repRates?.rating_stats; this removes
the extra render with stale data. After fixing dependencies, apply the same
change to canEditRep: derive it with useMemo (instead of useState/useEffect) and
list its actual dependencies so it updates synchronously with props/state
changes. Ensure you update imports to include useMemo and remove the now-unused
setTopReps/setState logic.

Comment thread __tests__/components/user/rep/header/UserPageRepHeader.test.tsx
Comment thread __tests__/components/user/rep/new-rep/UserPageRepNewRep.test.tsx
Comment thread components/profile-activity/ProfileActivityLogs.tsx
Comment thread components/user/identity/header/cic-rate/UserPageIdentityHeaderCICRate.tsx Outdated
Comment thread components/user/identity/statements/utils/UserPageIdentityStatementsStatement.tsx Outdated
Comment thread components/user/rep/header/TopRaterAvatars.tsx Outdated
Comment thread components/user/rep/header/UserPageRepHeader.tsx Outdated
Comment thread components/user/rep/header/UserPageRepHeader.tsx Outdated
Comment thread components/user/rep/new-rep/UserPageRepNewRepSearch.tsx
Comment thread components/user/rep/reps/table/UserPageRepRepsTableItem.tsx
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: 7

Caution

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

⚠️ Outside diff range comments (1)
components/profile-activity/list/items/ProfileActivityLogClassification.tsx (1)

34-34: ⚠️ Potential issue | 🔴 Critical

Typo in stroke attribute — SVG arrow will not inherit the intended color.

"currentcOLOR" is not a valid SVG paint keyword. The SVG spec requires the exact string "currentColor" (capital C). With the current typo, the browser falls back to black (or the default SVG stroke color) instead of the tw-text-iron-400 color applied via CSS.

🐛 Proposed fix
-              stroke="currentcOLOR"
+              stroke="currentColor"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/profile-activity/list/items/ProfileActivityLogClassification.tsx`
at line 34, In ProfileActivityLogClassification.tsx the SVG stroke attribute is
misspelled as "currentcOLOR" so the arrow won't inherit the CSS color; update
the stroke value to the exact SVG keyword "currentColor" in the SVG element
(wherever stroke="currentcOLOR" is used) so the icon inherits the
tw-text-iron-400 styling, and scan the same component for any other casing typos
of "currentColor".
🧹 Nitpick comments (13)
components/utils/table/paginator/CommonTablePagination.tsx (1)

8-9: LGTM — additive, backward-compatible props with correct defaults.

  • showTopBorder = true preserves existing rendering for all current callers.
  • className ?? "" correctly prevents "undefined" appearing in the class string.
  • The narrowed border condition (small && showTopBorder) is strictly more restrictive than the old small check, which is the intended opt-out behavior.

One cosmetic note: when className is undefined, the assembled string gets a trailing space ("tw-pt-4 tw-pb-3 "). This is harmless in HTML/React class attribute parsing, but if tidiness matters, className can be included conditionally:

🧹 Optional: eliminate trailing space
-      } tw-pt-4 tw-pb-3 ${className ?? ""}`}
+      } tw-pt-4 tw-pb-3${className ? ` ${className}` : ""}`}

Also applies to: 17-18, 23-26

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

In `@components/utils/table/paginator/CommonTablePagination.tsx` around lines 8 -
9, The current CommonTablePagination change adds showTopBorder = true and
appends className using className ?? "" which can produce a trailing space when
className is undefined (e.g. resulting in "tw-pt-4 tw-pb-3 "). Update the class
string assembly in CommonTablePagination (where showTopBorder and className are
used) to include className conditionally instead of always appending an empty
string so the trailing space is avoided; keep the same default for showTopBorder
and preserve the existing conditional for the top border (small &&
showTopBorder).
components/profile-activity/list/items/ProfileActivityLogGeneralStatement.tsx (1)

15-15: tw-overflow-hidden is redundant alongside tw-truncate.

tw-truncate already sets overflow: hidden (plus text-overflow: ellipsis and white-space: nowrap), so tw-overflow-hidden is a no-op here.

♻️ Proposed cleanup
-      <span className="tw-min-w-0 tw-truncate tw-overflow-hidden tw-text-md tw-font-medium tw-text-iron-200">
+      <span className="tw-min-w-0 tw-truncate tw-text-md tw-font-medium tw-text-iron-200">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/profile-activity/list/items/ProfileActivityLogGeneralStatement.tsx`
at line 15, In ProfileActivityLogGeneralStatement (the span with className
starting "tw-min-w-0 tw-truncate tw-overflow-hidden tw-text-md..."), remove the
redundant "tw-overflow-hidden" utility since "tw-truncate" already applies
overflow:hidden; update the className to drop "tw-overflow-hidden" so the span
still uses "tw-min-w-0 tw-truncate tw-text-md tw-font-medium tw-text-iron-200".
components/user/utils/raters-table/ProfileRatersTableItem.tsx (1)

23-25: getProfileRoute can be inlined — the function wrapper adds no value.

With the switch removed, getProfileRoute is now a zero-argument function that immediately returns a single template literal. Declaring and calling it separately is unnecessary indirection.

♻️ Proposed simplification
-  const getProfileRoute = (): string => `/${rating.handle}/rep`;
-
-  const profileRoute = getProfileRoute();
+  const profileRoute = `/${rating.handle}/rep`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/utils/raters-table/ProfileRatersTableItem.tsx` around lines
23 - 25, The helper getProfileRoute is a useless zero-argument wrapper around a
template literal; inline its result by replacing the separate getProfileRoute()
declaration and call with a direct assignment to profileRoute (e.g., const
profileRoute = `/${rating.handle}/rep`), removing the getProfileRoute function
entirely and keeping any usage of profileRoute unchanged.
components/profile-activity/filter/ProfileActivityLogsFilter.tsx (1)

10-20: user prop is dead code — remove it from the interface and both call sites.

The user parameter (line 16) is never referenced anywhere in the component. Two call sites in ProfileActivityLogs.tsx (lines 206–211 and 253–258) still pass this prop unnecessarily, making the interface misleading about what affects rendering.

♻️ Proposed fixes

Component definition:

 export default function ProfileActivityLogsFilter({
-  user,
   selected,
   options,
   setSelected,
 }: {
-  readonly user: string | null;
   readonly selected: ProfileActivityLogType[];
   readonly options: ProfileActivityLogType[];
   readonly setSelected: (selected: ProfileActivityLogType) => void;
 }) {

Call site 1 (line 206):

               <ProfileActivityLogsFilter
-                user={initialParams.handleOrWallet}
                 options={initialParams.logTypes}
                 selected={selectedFilters}
                 setSelected={onFilter}
               />

Call site 2 (line 253):

                 <ProfileActivityLogsFilter
-                  user={initialParams.handleOrWallet}
                   options={initialParams.logTypes}
                   selected={selectedFilters}
                   setSelected={onFilter}
                 />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/profile-activity/filter/ProfileActivityLogsFilter.tsx` around
lines 10 - 20, The ProfileActivityLogsFilter component has an unused prop
"user"; remove "user" from its props type and parameter list in the
ProfileActivityLogsFilter declaration, and update both call sites in
ProfileActivityLogs that still pass a user argument to stop passing it.
Specifically, edit the ProfileActivityLogsFilter function signature to drop the
"user" property and its type (keep selected, options, setSelected), and remove
the corresponding argument from the two places in ProfileActivityLogs where
ProfileActivityLogsFilter(...) is invoked so the caller matches the new
signature.
components/user/rep/header/UserPageRepHeader.tsx (1)

40-56: Move sortReps outside the component — it's a pure comparator and triggers an exhaustive-deps violation.

sortReps closes over no state or props (it only touches its items parameter and RatingStats fields), so there is no reason to recreate it on every render. More concretely, because it is redefined on each render but omitted from the useEffect dependency array at line 56, ESLint's react-hooks/exhaustive-deps rule will flag a lint error.

♻️ Proposed fix
-const TOP_REPS_COUNT = 4;
+const TOP_REPS_COUNT = 4;
+
+const sortReps = (items: RatingStats[]) =>
+  [...items].sort((a, d) => {
+    if (a.rating === d.rating) {
+      return d.contributor_count - a.contributor_count;
+    }
+    return d.rating - a.rating;
+  });

 export default function UserPageRepHeader({ repRates, profile }: { ... }) {
   const { connectedProfile, activeProfileProxy } = useContext(AuthContext);

-  const sortReps = (items: RatingStats[]) =>
-    [...items].sort((a, d) => {
-      if (a.rating === d.rating) {
-        return d.contributor_count - a.contributor_count;
-      }
-      return d.rating - a.rating;
-    });
-
   const [topReps, setTopReps] = useState<RatingStats[]>(
     sortReps(repRates?.rating_stats ?? []).slice(0, TOP_REPS_COUNT)
   );

With sortReps at module scope the useEffect dependency array at line 56 is already correct (a stable reference needs no entry).

🤖 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 40 - 56, Move
the pure comparator function sortReps out of the component body to module scope
so it isn't recreated each render; update nothing else in the effect logic —
keep the useEffect that calls setTopReps(sortReps(repRates?.rating_stats ??
[]).slice(0, TOP_REPS_COUNT)) and its dependency on repRates?.rating_stats
unchanged so ESLint's react-hooks/exhaustive-deps no longer flags the missing
dependency. Ensure sortReps still accepts items: RatingStats[] and preserves the
existing sort logic used by topReps/setTopReps and TOP_REPS_COUNT.
components/user/identity/statements/consolidated-addresses/UserPageIdentityStatementsConsolidatedAddressesItem.tsx (2)

170-232: Inconsistent conditional direction on tooltip data attribute spread.

Lines 175 and 217 use !isTouchScreen ? content : null, but line 196 flips it to isTouchScreen ? null : content. Functionally identical, but pick one pattern for readability.

♻️ Suggested fix
-            {...(isTouchScreen ? null : { "data-tooltip-content": "Opensea" })}
+            {...(!isTouchScreen ? { "data-tooltip-content": "Opensea" } : null)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/user/identity/statements/consolidated-addresses/UserPageIdentityStatementsConsolidatedAddressesItem.tsx`
around lines 170 - 232, The tooltip data-attribute spread is inconsistent:
replace the flipped ternary used on the Opensea button (currently using
isTouchScreen ? null : { "data-tooltip-content": "Opensea" }) so it matches the
other buttons' pattern (!isTouchScreen ? { "data-tooltip-content": "…" } :
null); update the JSX for the Opensea button (near onClick={goToOpensea} /
id={`opensea-tooltip-${address.wallet}`}) to use the same conditional expression
as the Etherscan and Copy buttons (which use !isTouchScreen), ensuring
consistent style around the data-tooltip-content prop while leaving
goToEtherscan, goToOpensea, handleCopy and the Tooltip components unchanged.

170-232: Consider extracting the repeated icon-button+tooltip pattern.

The Etherscan, Opensea, and Copy blocks each follow the exact same structure: <button> → icon wrapper → conditional <Tooltip>. A small ActionIconButton component (or inline helper) would remove ~60 lines of duplication and make it easier to add/remove actions later.

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

In
`@components/user/identity/statements/consolidated-addresses/UserPageIdentityStatementsConsolidatedAddressesItem.tsx`
around lines 170 - 232, Extract the repeated icon-button+tooltip into a small
reusable ActionIconButton component that accepts props like onClick, ariaLabel,
tooltipText (or null), tooltipIdSuffix (or id), isTouchScreen, icon (children)
and uses TOOLTIP_STYLES and address.wallet to build the data-tooltip-id; inside
it render the <button ... data-tooltip-id=... {...(isTouchScreen ? null :
{"data-tooltip-content": tooltipText})}> wrapper with the icon child and
conditionally render the <Tooltip id=... place="top" positionStrategy="fixed"
offset={8} opacity={1} style={TOOLTIP_STYLES} /> when !isTouchScreen. Replace
the three instances that currently use EtherscanIcon/goToEtherscan,
OpenseaIcon/goToOpensea, and CopyIcon/handleCopy (and their aria-labels and
tooltip texts) with calls to ActionIconButton passing the corresponding props
(use address.wallet for unique ids).
components/user/rep/reps/table/UserPageRepRepsTable.tsx (1)

71-71: sortReps mutates the reps prop array in-place.

Array.prototype.sort() mutates its receiver. Since reps (a prop) is passed directly on lines 94 and 98, the parent's array is mutated on every sort. This is pre-existing, but now that displayedReps and maxRep also derive from this data, a defensive copy avoids subtle ordering bugs:

Proposed fix (in sortReps)
  const sortReps = (
    items: RatingStats[],
    sort: RepsTableSort,
    dir: SortDirection
  ): RatingStats[] => {
+   const copy = [...items];
    switch (sort) {
      case RepsTableSort.REP:
-       return items.sort((a, d) =>
+       return copy.sort((a, d) =>

(apply the same copy substitution in the other two cases)

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

In `@components/user/rep/reps/table/UserPageRepRepsTable.tsx` at line 71, sortReps
is calling Array.prototype.sort on the props array which mutates the parent's
reps; make a defensive copy before sorting (e.g., use slice() or spread) inside
sortReps and likewise ensure displayedReps and maxRep derive from a copied array
before sorting/filtering so the original props aren't mutated; update references
to sortReps, displayedReps and maxRep to operate on the copied array.
components/user/identity/header/cic-rate/UserPageIdentityHeaderCICRate.tsx (2)

96-101: Unsafe error cast in onError.

error as unknown as string will produce garbled toast messages if the API returns a non-string error (e.g., an Error object → "[object Object]"). Consider using error instanceof Error ? error.message : String(error).

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

In `@components/user/identity/header/cic-rate/UserPageIdentityHeaderCICRate.tsx`
around lines 96 - 101, The onError handler currently casts the incoming error
unsafely with "error as unknown as string" when calling setToast; update the
onError implementation (the onError callback that calls setToast) to normalize
the error value by checking if error is an instance of Error and using
error.message, otherwise convert with String(error) (or provide a fallback like
"Unknown error") before passing to setToast so toast messages are readable and
safe.

238-254: tooltipButtonContent and fullButtonContent are nearly identical.

Both show <CircleLoader /> when mutating and "Rate" otherwise — the only difference is an extra centering wrapper. Consider unifying into a single variable or small component to reduce duplication.

Proposed simplification
-  const tooltipButtonContent = mutating ? (
-    <div className="tw-w-8">
-      <CircleLoader />
-    </div>
-  ) : (
-    <>Rate</>
-  );
-
-  const fullButtonContent = mutating ? (
-    <div className="tw-flex tw-items-center tw-justify-center">
-      <div className="tw-w-8">
-        <CircleLoader />
-      </div>
-    </div>
-  ) : (
-    <>Rate</>
-  );
+  const buttonContent = mutating ? (
+    <div className={`tw-w-8 ${isTooltip ? "" : "tw-mx-auto"}`}>
+      <CircleLoader />
+    </div>
+  ) : (
+    <>Rate</>
+  );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/identity/header/cic-rate/UserPageIdentityHeaderCICRate.tsx`
around lines 238 - 254, tooltipButtonContent and fullButtonContent duplicate
logic for the mutating state; extract a small helper (e.g., a ButtonContent
component or a getButtonContent function) that returns the loader when mutating
and "Rate" otherwise, and use it for both tooltipButtonContent and
fullButtonContent, adding the extra centering wrapper only where needed (keep
CircleLoader as the loader component and preserve the centering wrapper in
fullButtonContent's usage).
components/user/rep/new-rep/UserPageRepNewRepSearch.tsx (3)

445-445: onFocus unconditionally opens the dropdown even when a category is already selected

onFocus={() => setIsOpen(true)} bypasses the guard in the categoriesToDisplay effect (line 353: if (debouncedValue.length && !selectedCategory)). After a category is selected, clicking the input field to edit the amount causes the dropdown to flash open. Consider suppressing the dropdown when a category is already committed:

♻️ Proposed tweak
- onFocus={() => setIsOpen(true)}
+ onFocus={() => { if (!selectedCategory) setIsOpen(true); }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/rep/new-rep/UserPageRepNewRepSearch.tsx` at line 445, The
onFocus handler currently calls setIsOpen(true) unconditionally causing the
dropdown to open even after a category is selected; change the handler to only
open when it makes sense by checking the same guard used in categoriesToDisplay:
call setIsOpen(true) only when debouncedValue.length > 0 and selectedCategory is
falsy (or use the existing selectedCategory state) so focusing the input to edit
amount won't flash the dropdown; update the onFocus reference near the existing
setIsOpen and ensure behavior matches the categoriesToDisplay effect.

247-253: amountNum and newRating are the same computation — merge into one variable

Both are getStringAsNumberOrZero(amountStr). Deduplicate:

♻️ Proposed cleanup
- const amountNum = getStringAsNumberOrZero(amountStr);
+ const parsedAmount = getStringAsNumberOrZero(amountStr);
  const isValidValue =
    !!activeProfileProxy ||
-   (amountNum >= minMaxValues.min && amountNum <= minMaxValues.max);
+   (parsedAmount >= minMaxValues.min && parsedAmount <= minMaxValues.max);

- const newRating = getStringAsNumberOrZero(amountStr);
- const haveChanged = newRating !== (repState?.rater_contribution ?? 0);
+ const haveChanged = parsedAmount !== (repState?.rater_contribution ?? 0);

(And reference parsedAmount in UserRateAdjustmentHelper's adjustedValue prop at line 507.)

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

In `@components/user/rep/new-rep/UserPageRepNewRepSearch.tsx` around lines 247 -
253, The code computes getStringAsNumberOrZero(amountStr) twice as amountNum and
newRating; consolidate into a single variable (e.g., parsedAmount) and replace
both amountNum and newRating with that variable, then update the
UserRateAdjustmentHelper usage to pass parsedAmount into its adjustedValue prop
(reference: getStringAsNumberOrZero, amountNum, newRating, and the
UserRateAdjustmentHelper adjustedValue prop).

131-238: Replace five useState + useEffect pairs for derived state with useMemo

getRepState, getProxyAvailableCredit, getHeroAvailableRep, getMinValue, and getMaxValue are all pure derivations of existing state/context. Storing them in useState and keeping them in sync via useEffect introduces an extra render cycle on every dependency change and makes the helper functions appear in none of the useEffect dependency arrays (which would be flagged by react-hooks/exhaustive-deps). The idiomatic React pattern is useMemo:

const repState = useMemo<RatingStats | null>(() => {
  if (!selectedCategory) return null;
  const rates = activeProfileProxy
    ? proxyGrantorRepRates
    : connectedProfileRepRates;
  if (!rates) return null;
  return (
    rates.rating_stats.find((s) => s.category === selectedCategory) ?? {
      category: selectedCategory,
      rating: 0,
      contributor_count: 0,
      rater_contribution: 0,
    }
  );
}, [selectedCategory, activeProfileProxy, proxyGrantorRepRates, connectedProfileRepRates]);

const proxyAvailableCredit = useMemo<number | null>(() => {
  const repProxy = activeProfileProxy?.actions.find(
    (a) => a.action_type === ApiProfileProxyActionType.AllocateRep
  );
  if (!repProxy) return null;
  return Math.max((repProxy.credit_amount ?? 0) - (repProxy.credit_spent ?? 0), 0);
}, [activeProfileProxy]);

const heroAvailableRep = useMemo<number>(
  () => activeProfileProxy
    ? proxyGrantorRepRates?.rep_rates_left_for_rater ?? 0
    : connectedProfileRepRates?.rep_rates_left_for_rater ?? 0,
  [activeProfileProxy, proxyGrantorRepRates, connectedProfileRepRates]
);

// minMaxValues follows the same pattern

This collapses the 10 lines of useState + the five useEffect blocks entirely and is lint-clean.

Note: the minMaxValues useEffect dependency array at line 237 also includes proxyGrantorRepRates even though getMinValue/getMaxValue don't directly read it — a redundant dep that the useMemo approach eliminates automatically.

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

In `@components/user/rep/new-rep/UserPageRepNewRepSearch.tsx` around lines 131 -
238, Replace the derived-state pattern (getRepState, getProxyAvailableCredit,
getHeroAvailableRep, getMinValue, getMaxValue and the minMaxValues state) which
currently uses useState + useEffect pairs with useMemo computations: compute
repState, proxyAvailableCredit, heroAvailableRep, and the min/max values via
useMemo with correct dependency arrays (selectedCategory, activeProfileProxy,
proxyGrantorRepRates, connectedProfileRepRates, etc.), then remove the
corresponding useState initializations and useEffect synchronizers so the
component returns the same values but without extra renders or redundant deps.
🤖 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/profile-activity/list/items/utils/ProfileActivityLogItemWrapper.tsx`:
- Around line 36-41: ProfileActivityLogItemWrapper currently calls useIdentity
per item (via handleOrWallet and computing pfp), causing N separate profile
fetches for distinct users; instead, move profile fetching to the parent list
component: collect unique handleOrWallet values, batch or prefetch their
identities once (using your existing identity fetcher / React Query keys), map
handles to pfp values, and pass pfp into ProfileActivityLogItemWrapper as a
prop; update ProfileActivityLogItemWrapper to use the incoming pfp prop and skip
calling useIdentity when pfp is provided (keep the existing isArchived guard and
the enabled check for empty handles).

In `@components/user/identity/statements/UserPageIdentityStatements.tsx`:
- Around line 93-114: The element with data-tooltip-id="statements-help" is
marked role="button" and tabIndex={0} but has no activation handlers; either
make it a real button or remove the affordance — for a tooltip-only anchor
remove role="button" and tabIndex, keep the aria-label and add aria-describedby
(or similar) referencing the tooltip so assistive tech knows it's
descriptive-only; if it should be actionable instead, add onClick and onKeyDown
handlers to the same element to handle Enter/Space and keep role="button".
Ensure changes are applied to the same div containing the SVG and
data-tooltip-id="statements-help".

In `@components/user/rep/new-rep/UserPageRepNewRepSearch.tsx`:
- Around line 318-331: onGrantRep currently sets setMutating(true) then awaits
requestAuth and addRepMutation.mutateAsync without proper error handling, which
can permanently leave mutating=true or produce unhandled rejections; wrap the
core body of onGrantRep in try/finally so setMutating(false) is always called,
and wrap the mutateAsync call in a try/catch to handle/convert any thrown error
into a user-facing toast (using setToast) instead of letting it bubble as an
unhandled rejection; keep the existing checks for
selectedCategory/amountStr/haveChanged and the requestAuth success branch (using
requestAuth and setToast) inside the try block and ensure setMutating(true) is
called before the try and setMutating(false) in finally.
- Around line 91-127: The cache key for the rep-rating queries uses
profile?.handle but the fetch uses profile?.query, causing cache mismatch;
update the queryKey objects in the useQuery calls for proxyGrantorRepRates and
connectedProfileRepRates (QueryKey.PROFILE_REP_RATINGS entries) to use
handleOrWallet: profile?.query instead of profile?.handle, and make the same
consistent change in UserPageRepNewRepSearchHeader.tsx and
UserPageRepModifyModal.tsx so all queries use profile?.query as the identifier
to ensure proper cache deduplication.

In `@components/user/rep/reps/table/UserPageRepRepsTable.tsx`:
- Line 27: The component UserPageRepRepsTable keeps pagination state in showAll
(setShowAll) which can become stale when the reps prop changes; add a useEffect
that watches reps (or reps.length) and resets showAll to false when reps updates
so expansion state doesn't carry across profile navigations. Locate the showAll
state declaration and add a useEffect referencing reps to call setShowAll(false)
whenever reps changes.

In `@components/user/rep/reps/table/UserPageRepRepsTableItem.tsx`:
- Around line 9-21: The formatCompact function can round e.g. 999.95K up to
"1000.0K" (instead of "1M") and similarly produce "1.0K" at the 999.5 boundary;
fix by re-checking the rounded value before returning: after computing m = abs /
1_000_000 and k = abs / 1_000, compute the rounded-to-one-decimal value (or use
Math.round to 1 decimal) and if that rounded k >= 1000 return the next unit
("1M") or if rounded m >= 1000 return the next unit accordingly; otherwise
format using the rounded value and existing toFixed logic. Update formatCompact
and keep formatNumberWithCommas usage for values below 1_000.

In `@components/user/utils/rate/UserPageRateInput.tsx`:
- Around line 10-15: getValueStr currently only strips leading "0" and therefore
misses cases like "-05" from pasted input; update getValueStr to handle an
optional leading minus sign by detecting "-0" as well as "0" and strip only the
zero(s) that immediately follow the sign. Locate the getValueStr function and
change its logic to check for val starting with "-0" (or use a regex like
/^-?0+/) and return the string with the leading zero(s) removed while preserving
a single leading "-" when present.

---

Outside diff comments:
In `@components/profile-activity/list/items/ProfileActivityLogClassification.tsx`:
- Line 34: In ProfileActivityLogClassification.tsx the SVG stroke attribute is
misspelled as "currentcOLOR" so the arrow won't inherit the CSS color; update
the stroke value to the exact SVG keyword "currentColor" in the SVG element
(wherever stroke="currentcOLOR" is used) so the icon inherits the
tw-text-iron-400 styling, and scan the same component for any other casing typos
of "currentColor".

---

Duplicate comments:
In
`@components/profile-activity/list/items/ProfileActivityLogProfileArchived.tsx`:
- Around line 10-18: The three span elements in
ProfileActivityLogProfileArchived use the class tw-text-md which may not exist
in the Tailwind config; confirm whether tw-text-md is a valid custom utility and
if not replace it with the correct size utility (e.g., tw-text-sm / tw-text-base
or the design-system token) or add tw-text-md to the Tailwind theme; update the
className on the spans in the ProfileActivityLogProfileArchived component (the
span with "profile", the span rendering {log.contents.handle}, and the span with
"archived") to use the validated utility so the text renders at the intended
size.

In `@components/profile-activity/list/items/ProfileActivityLogSocialMedia.tsx`:
- Around line 20-22: The span inside ProfileActivityLogSocialMedia (the element
with className starting "tw-whitespace-nowrap tw-text-md tw-text-iron-300
tw-font-medium") uses an invalid Tailwind token `tw-text-md`; replace it with
the correct typography token used across the PR (e.g., `tw-text-sm` or
`tw-text-base` depending on intended size) so the className becomes something
like "tw-whitespace-nowrap tw-text-base tw-text-iron-300 tw-font-medium".

In
`@components/profile-activity/list/items/ProfileActivityLogSocialMediaVerificationPost.tsx`:
- Around line 20-22: The span in ProfileActivityLogSocialMediaVerificationPost
uses a non-standard Tailwind class "tw-text-md"; replace it with the correct
design-system/ Tailwind typography class (for example "tw-text-base" or the
approved token used across this PR) to match the other typography updates, and
update any other occurrences of "tw-text-md" in this component to the chosen
valid class so styles remain consistent; refer to the span element in
ProfileActivityLogSocialMediaVerificationPost and the "tw-text-md" token when
making the change.

In `@components/profile-activity/ProfileActivityLogs.tsx`:
- Around line 119-122: onMatterChange currently only calls setMatter and
setCurrentPage, leaving stale selectedFilters active; when withMatterFilter is
true and the user switches away from NIC (e.g., to REP or ALL) the UI for
log-type filters disappears but the query still uses previous selectedFilters.
Update onMatterChange to also reset selectedFilters (e.g., call
setSelectedFilters to an empty/default value) whenever the new matter is not NIC
(or is null) so filters are cleared when the matter changes away from NIC; keep
setMatter and setCurrentPage behavior intact.

In `@components/user/rep/header/UserPageRepHeader.tsx`:
- Around line 58-97: getCanEditRep is incorrectly referencing the outer closure
profile (profile.handle) instead of the targetProfile parameter and the effect
that updates canEditRep doesn't include activeProfileProxy in its dependency
array; update getCanEditRep to use targetProfile.handle wherever the function
should check the target (replace profile.handle with targetProfile.handle) and
add activeProfileProxy to the dependency array of the useEffect that calls
setCanEditRep so canEditRep is recalculated when the proxy context changes
(references: getCanEditRep, targetProfile, profile, activeProfileProxy,
useEffect, connectedProfile, setCanEditRep).
- Around line 17-29: formatCompact currently formats 999_999 as "1000.0K"
because k = abs/1_000 is 999.999 and toFixed(1) rounds up to 1000.0K instead of
rolling into the "M" branch; update formatCompact to detect when the rounded
thousand value reaches 1000 and promote to the millions formatting: after
computing k (abs/1_000) compute a rounded value (e.g., const kRounded =
Math.round(k * 10) / 10); if kRounded >= 1000 then compute m = abs / 1_000_000
and return the millions string using the same integer-vs-1-decimal logic used
for M (reuse the existing m formatting logic), otherwise proceed to format K
using kRounded (dropping unnecessary ".0" when integer). Ensure you reference
formatCompact and formatNumberWithCommas when making the change.

In `@components/user/rep/new-rep/UserPageRepNewRepSearch.tsx`:
- Around line 310-312: Replace the unsafe cast in the onError handler of
UserPageRepNewRepSearch.tsx: don't use `error as unknown as string`; instead
detect the error type and derive a safe string (e.g., if (error instanceof
Error) use error.message, else if typeof error === 'string' use it, otherwise
JSON.stringify(error) or a fallback message) before calling setToast so setToast
always receives a well-formed string rather than forcing a cast.
- Around line 318-320: The onGrantRep handler uses Number.parseInt(amountStr,
10) which mismatches the earlier validation and haveChanged logic that use
getStringAsNumberOrZero(amountStr), causing decimal inputs like "3.5" to be
truncated; replace the parseInt call with the same helper (assign amount =
getStringAsNumberOrZero(amountStr)) so parsing is consistent across validation
and submission (or, if integers are required, normalize/round using the same
helper in validation and here). Ensure you update the amount variable in
onGrantRep and keep references to amountStr, getStringAsNumberOrZero, and
onGrantRep aligned.

In `@components/user/rep/reps/table/UserPageRepRepsTableItem.tsx`:
- Around line 97-109: The conditional is using a truthy check on
rep.rater_contribution which treats 0 as falsy and renders "-" incorrectly;
update the condition in UserPageRepRepsTableItem to check for null/undefined
instead (e.g., use a nullish check against rep.rater_contribution) so that 0 is
formatted via formatNumberWithCommas and only null/undefined produce the "-"
fallback.

---

Nitpick comments:
In `@components/profile-activity/filter/ProfileActivityLogsFilter.tsx`:
- Around line 10-20: The ProfileActivityLogsFilter component has an unused prop
"user"; remove "user" from its props type and parameter list in the
ProfileActivityLogsFilter declaration, and update both call sites in
ProfileActivityLogs that still pass a user argument to stop passing it.
Specifically, edit the ProfileActivityLogsFilter function signature to drop the
"user" property and its type (keep selected, options, setSelected), and remove
the corresponding argument from the two places in ProfileActivityLogs where
ProfileActivityLogsFilter(...) is invoked so the caller matches the new
signature.

In
`@components/profile-activity/list/items/ProfileActivityLogGeneralStatement.tsx`:
- Line 15: In ProfileActivityLogGeneralStatement (the span with className
starting "tw-min-w-0 tw-truncate tw-overflow-hidden tw-text-md..."), remove the
redundant "tw-overflow-hidden" utility since "tw-truncate" already applies
overflow:hidden; update the className to drop "tw-overflow-hidden" so the span
still uses "tw-min-w-0 tw-truncate tw-text-md tw-font-medium tw-text-iron-200".

In `@components/user/identity/header/cic-rate/UserPageIdentityHeaderCICRate.tsx`:
- Around line 96-101: The onError handler currently casts the incoming error
unsafely with "error as unknown as string" when calling setToast; update the
onError implementation (the onError callback that calls setToast) to normalize
the error value by checking if error is an instance of Error and using
error.message, otherwise convert with String(error) (or provide a fallback like
"Unknown error") before passing to setToast so toast messages are readable and
safe.
- Around line 238-254: tooltipButtonContent and fullButtonContent duplicate
logic for the mutating state; extract a small helper (e.g., a ButtonContent
component or a getButtonContent function) that returns the loader when mutating
and "Rate" otherwise, and use it for both tooltipButtonContent and
fullButtonContent, adding the extra centering wrapper only where needed (keep
CircleLoader as the loader component and preserve the centering wrapper in
fullButtonContent's usage).

In
`@components/user/identity/statements/consolidated-addresses/UserPageIdentityStatementsConsolidatedAddressesItem.tsx`:
- Around line 170-232: The tooltip data-attribute spread is inconsistent:
replace the flipped ternary used on the Opensea button (currently using
isTouchScreen ? null : { "data-tooltip-content": "Opensea" }) so it matches the
other buttons' pattern (!isTouchScreen ? { "data-tooltip-content": "…" } :
null); update the JSX for the Opensea button (near onClick={goToOpensea} /
id={`opensea-tooltip-${address.wallet}`}) to use the same conditional expression
as the Etherscan and Copy buttons (which use !isTouchScreen), ensuring
consistent style around the data-tooltip-content prop while leaving
goToEtherscan, goToOpensea, handleCopy and the Tooltip components unchanged.
- Around line 170-232: Extract the repeated icon-button+tooltip into a small
reusable ActionIconButton component that accepts props like onClick, ariaLabel,
tooltipText (or null), tooltipIdSuffix (or id), isTouchScreen, icon (children)
and uses TOOLTIP_STYLES and address.wallet to build the data-tooltip-id; inside
it render the <button ... data-tooltip-id=... {...(isTouchScreen ? null :
{"data-tooltip-content": tooltipText})}> wrapper with the icon child and
conditionally render the <Tooltip id=... place="top" positionStrategy="fixed"
offset={8} opacity={1} style={TOOLTIP_STYLES} /> when !isTouchScreen. Replace
the three instances that currently use EtherscanIcon/goToEtherscan,
OpenseaIcon/goToOpensea, and CopyIcon/handleCopy (and their aria-labels and
tooltip texts) with calls to ActionIconButton passing the corresponding props
(use address.wallet for unique ids).

In `@components/user/rep/header/UserPageRepHeader.tsx`:
- Around line 40-56: Move the pure comparator function sortReps out of the
component body to module scope so it isn't recreated each render; update nothing
else in the effect logic — keep the useEffect that calls
setTopReps(sortReps(repRates?.rating_stats ?? []).slice(0, TOP_REPS_COUNT)) and
its dependency on repRates?.rating_stats unchanged so ESLint's
react-hooks/exhaustive-deps no longer flags the missing dependency. Ensure
sortReps still accepts items: RatingStats[] and preserves the existing sort
logic used by topReps/setTopReps and TOP_REPS_COUNT.

In `@components/user/rep/new-rep/UserPageRepNewRepSearch.tsx`:
- Line 445: The onFocus handler currently calls setIsOpen(true) unconditionally
causing the dropdown to open even after a category is selected; change the
handler to only open when it makes sense by checking the same guard used in
categoriesToDisplay: call setIsOpen(true) only when debouncedValue.length > 0
and selectedCategory is falsy (or use the existing selectedCategory state) so
focusing the input to edit amount won't flash the dropdown; update the onFocus
reference near the existing setIsOpen and ensure behavior matches the
categoriesToDisplay effect.
- Around line 247-253: The code computes getStringAsNumberOrZero(amountStr)
twice as amountNum and newRating; consolidate into a single variable (e.g.,
parsedAmount) and replace both amountNum and newRating with that variable, then
update the UserRateAdjustmentHelper usage to pass parsedAmount into its
adjustedValue prop (reference: getStringAsNumberOrZero, amountNum, newRating,
and the UserRateAdjustmentHelper adjustedValue prop).
- Around line 131-238: Replace the derived-state pattern (getRepState,
getProxyAvailableCredit, getHeroAvailableRep, getMinValue, getMaxValue and the
minMaxValues state) which currently uses useState + useEffect pairs with useMemo
computations: compute repState, proxyAvailableCredit, heroAvailableRep, and the
min/max values via useMemo with correct dependency arrays (selectedCategory,
activeProfileProxy, proxyGrantorRepRates, connectedProfileRepRates, etc.), then
remove the corresponding useState initializations and useEffect synchronizers so
the component returns the same values but without extra renders or redundant
deps.

In `@components/user/rep/reps/table/UserPageRepRepsTable.tsx`:
- Line 71: sortReps is calling Array.prototype.sort on the props array which
mutates the parent's reps; make a defensive copy before sorting (e.g., use
slice() or spread) inside sortReps and likewise ensure displayedReps and maxRep
derive from a copied array before sorting/filtering so the original props aren't
mutated; update references to sortReps, displayedReps and maxRep to operate on
the copied array.

In `@components/user/utils/raters-table/ProfileRatersTableItem.tsx`:
- Around line 23-25: The helper getProfileRoute is a useless zero-argument
wrapper around a template literal; inline its result by replacing the separate
getProfileRoute() declaration and call with a direct assignment to profileRoute
(e.g., const profileRoute = `/${rating.handle}/rep`), removing the
getProfileRoute function entirely and keeping any usage of profileRoute
unchanged.

In `@components/utils/table/paginator/CommonTablePagination.tsx`:
- Around line 8-9: The current CommonTablePagination change adds showTopBorder =
true and appends className using className ?? "" which can produce a trailing
space when className is undefined (e.g. resulting in "tw-pt-4 tw-pb-3 "). Update
the class string assembly in CommonTablePagination (where showTopBorder and
className are used) to include className conditionally instead of always
appending an empty string so the trailing space is avoided; keep the same
default for showTopBorder and preserve the existing conditional for the top
border (small && showTopBorder).

Comment thread components/user/identity/statements/UserPageIdentityStatements.tsx Outdated
Comment thread components/user/rep/new-rep/UserPageRepNewRepSearch.tsx Outdated
Comment thread components/user/rep/new-rep/UserPageRepNewRepSearch.tsx
Comment thread components/user/rep/reps/table/UserPageRepRepsTable.tsx
Comment thread components/user/rep/reps/table/UserPageRepRepsTableItem.tsx Outdated
Comment thread components/user/utils/rate/UserPageRateInput.tsx
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: 2

Caution

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

⚠️ Outside diff range comments (2)
components/user/rep/reps/UserPageRepReps.tsx (1)

70-77: ⚠️ Potential issue | 🟠 Major

activeProfileProxy is missing from the useEffect dependency array — stale permission state.

getCanEditRep closes over activeProfileProxy (line 49–56) to determine whether a proxy can allocate rep, but activeProfileProxy is absent from the dependency array on line 77. If activeProfileProxy changes (e.g., user switches proxy or it clears) while connectedProfile and profile remain the same, canEditRep will not be recomputed and the edit UI will reflect stale proxy permissions.

🐛 Proposed fix
-  }, [connectedProfile, profile]);
+  }, [connectedProfile, profile, activeProfileProxy]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/rep/reps/UserPageRepReps.tsx` around lines 70 - 77, The
useEffect that updates setCanEditRep currently depends on [connectedProfile,
profile] but getCanEditRep closes over activeProfileProxy, so add
activeProfileProxy to the dependency array to ensure canEditRep is recomputed
when the proxy changes; update the dependency list for the useEffect that calls
getCanEditRep (the effect that sets canEditRep) to include activeProfileProxy
alongside connectedProfile and profile so UI reflects current proxy permissions.
components/user/identity/statements/utils/UserPageIdentityStatementsStatement.tsx (1)

27-33: ⚠️ Potential issue | 🟡 Minor

handleCopy may copy the string "Copied!" to the clipboard.

copyToClipboard(title) reads the current state value. If the user clicks the copy button a second time within the 1-second feedback window, title is already "Copied!" at that point, so that literal string — not the actual statement value — is written to the clipboard.

Fix: always copy from the prop, not from state:

🐛 Proposed fix
 const handleCopy = () => {
-  copyToClipboard(title);
+  copyToClipboard(statement.statement_value);
   setTitle("Copied!");
   setTimeout(() => {
     setTitle(statement.statement_value);
   }, 1000);
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/user/identity/statements/utils/UserPageIdentityStatementsStatement.tsx`
around lines 27 - 33, The handleCopy function currently calls
copyToClipboard(title) which can copy the temporary "Copied!" UI text if the
user clicks again during the feedback window; change handleCopy to always copy
from the prop value (statement.statement_value) instead of the local state, i.e.
call copyToClipboard(statement.statement_value) and keep the existing
setTitle("Copied!")/setTimeout(() => setTitle(statement.statement_value), 1000)
behavior so the UI feedback remains correct while clipboard content is always
the actual statement.
🧹 Nitpick comments (8)
components/user/identity/header/UserPageIdentityHeaderCIC.tsx (1)

14-18: Redundant useState/useEffect derived-state pattern

cicRating is never mutated locally — it only mirrors profile.cic. The useState + useEffect combo can be dropped in favour of reading profile.cic directly, which also eliminates the one-render stale-value window that the current pattern creates (the initial render and any render before the effect fires show profile.cic from the previous closure, not the latest prop).

♻️ Proposed simplification
-  const [cicRating, setCicRating] = useState<number>(profile.cic);
-
-  useEffect(() => {
-    setCicRating(profile.cic);
-  }, [profile]);
-
   return (
     <div className="tw-mb-8">
       ...
-        {formatNumberWithCommas(cicRating)}
+        {formatNumberWithCommas(profile.cic)}
       ...
-        <UserCICStatus cic={cicRating} />
+        <UserCICStatus cic={profile.cic} />

If there is a deliberate intent to keep a local copy (e.g. optimistic updates planned in a follow-up), narrowing the dependency to [profile.cic] instead of [profile] would at least avoid re-running the effect on unrelated profile field changes.

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

In `@components/user/identity/header/UserPageIdentityHeaderCIC.tsx` around lines
14 - 18, Remove the redundant derived state: delete the useState hook
(cicRating, setCicRating) and the useEffect that copies profile.cic, and read
profile.cic directly where cicRating is used; if a local mutable copy is
intended later, keep a state but change the effect dependency to [profile.cic]
instead of [profile] to avoid re-running on unrelated profile changes (update
references to cicRating to use profile.cic or the kept state accordingly).
components/user/rep/reps/UserPageRepReps.tsx (1)

22-28: sortReps is redefined on every render; consider hoisting it.

sortReps is a pure function with no component-scope dependencies. Defining it inside the component means a new function object is allocated on every render. Hoisting it outside the component body eliminates the allocation and makes the intent clearer.

♻️ Proposed refactor
+"use client";
+
+import { useContext, useEffect, useState } from "react";
+// ... other imports ...
+
+const sortReps = (items: RatingStats[]) =>
+  [...items].sort((a, d) => {
+    if (a.rating === d.rating) {
+      return d.contributor_count - a.contributor_count;
+    }
+    return d.rating - a.rating;
+  });
+
 export default function UserPageRepReps({ ... }) {
-  const sortReps = (items: RatingStats[]) =>
-    [...items].sort((a, d) => {
-      if (a.rating === d.rating) {
-        return d.contributor_count - a.contributor_count;
-      }
-      return d.rating - a.rating;
-    });
   // ...

Also applies to: 34-37

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

In `@components/user/rep/reps/UserPageRepReps.tsx` around lines 22 - 28, Hoist the
pure sorting helper out of the component by moving the sortReps function
definition (currently a const sortReps = (items: RatingStats[]) =>
[...items].sort(...)) to module scope so it is not re-created on every render;
update any references in the component to use the top-level sortReps, and apply
the same hoisting refactor to the other similar inline helper at lines 34-37
(the second sorter) so both helpers live in module scope with no component-scope
captures.
components/user/identity/statements/nft-accounts/UserPageIdentityStatementsNFTAccounts.tsx (1)

22-22: Minor: singular/plural inconsistency between label and empty-state message.

The section heading reads "NFT Accounts" (plural) but the empty-state message reads "No NFT Account added yet" (singular). Consider making these consistent.

✏️ Proposed fix
-        noItemsMessage="No NFT Account added yet"
+        noItemsMessage="No NFT Accounts added yet"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/user/identity/statements/nft-accounts/UserPageIdentityStatementsNFTAccounts.tsx`
at line 22, The heading in UserPageIdentityStatementsNFTAccounts reads "NFT
Accounts" (plural) but the empty-state prop noItemsMessage is "No NFT Account
added yet" (singular); update the noItemsMessage string to use the plural form
(for example "No NFT Accounts added yet") so it matches the section heading, or
alternatively change the heading to singular—locate the noItemsMessage prop in
the UserPageIdentityStatementsNFTAccounts component and make the wording
consistent.
components/user/identity/statements/utils/UserPageIdentityStatementsStatement.tsx (1)

24-24: Optional: replace string state with a boolean isCopied flag.

title is initialised to statement.statement_value but the non-copied display branch (line 56) reads statement.statement_value directly from the prop — it never actually renders title. The string state exists solely to detect the "Copied!" condition. A boolean is simpler and eliminates the stale-closure risk in the setTimeout reset.

♻️ Proposed refactor
- const [title, setTitle] = useState(statement.statement_value);
+ const [isCopied, setIsCopied] = useState(false);

  const handleCopy = () => {
-   copyToClipboard(title);
-   setTitle("Copied!");
+   copyToClipboard(statement.statement_value);
+   setIsCopied(true);
    setTimeout(() => {
-     setTitle(statement.statement_value);
+     setIsCopied(false);
    }, 1000);
  };
- {title === "Copied!" ? (
-   <span className="tw-text-primary-400">{title}</span>
+ {isCopied ? (
+   <span className="tw-text-primary-400">Copied!</span>
  ) : (
    statement.statement_value
  )}

Also applies to: 52-58

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

In
`@components/user/identity/statements/utils/UserPageIdentityStatementsStatement.tsx`
at line 24, Replace the string state title/setTitle with a boolean
isCopied/setIsCopied in the UserPageIdentityStatementsStatement component:
remove useState(statement.statement_value) and instead use useState(false);
update the copy handler to setIsCopied(true) when copied and schedule
setIsCopied(false) with the timeout (avoiding closure over a string), and change
the render branch that currently compares against "Copied!" to read
statement.statement_value directly for non-copied output and use isCopied to
show the "Copied!" UI; ensure any references to title or setTitle are removed
and only statement.statement_value, isCopied and setIsCopied remain.
components/profile-activity/ProfileActivityLogs.tsx (1)

221-231: Consider adding type="button" to filter buttons.

These <button> elements lack an explicit type attribute. While they're likely not inside a <form> today, buttons default to type="submit" per the HTML spec, which can cause unintended form submissions if the component is ever reused inside a form context.

Example for one button (apply to all button elements in this block)
                  <button
                    key={opt.key}
+                   type="button"
                    onClick={() => onMatterChange(opt.value)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/profile-activity/ProfileActivityLogs.tsx` around lines 221 - 231,
The filter buttons in the ProfileActivityLogs component default to type="submit"
which can cause accidental form submissions; update the button elements (the
ones using opt.key, onClick={() => onMatterChange(opt.value)}, and checking
matter === opt.value) to include an explicit type="button" attribute on each
button in that block so they behave as non-submitting controls even if the
component is rendered inside a form.
components/user/rep/new-rep/UserPageRepNewRepSearch.tsx (1)

193-238: Inline helpers used inside useEffect bodies are not included in the dependency arrays — consider memoizing with useCallback.

getRepState, getProxyAvailableCredit, getHeroAvailableRep, getMinValue, and getMaxValue are plain functions recreated every render. The effects that call them enumerate the underlying data deps directly (e.g., selectedCategory, proxyGrantorRepRates, etc.) rather than the function refs, which is semantically correct but will trigger react-hooks/exhaustive-deps lint warnings and can drift silently if any helper's internal dependencies change without the effect dep array being updated.

Wrapping each helper with useCallback (or inlining the logic directly into each effect) eliminates both the lint noise and the fragility:

♻️ Example refactor for getRepState
-  const getRepState = (): RatingStats | null => {
+  const getRepState = useCallback((): RatingStats | null => {
     if (!selectedCategory) return null;
     // ... rest of logic unchanged
-  };
+  }, [selectedCategory, activeProfileProxy, proxyGrantorRepRates, connectedProfileRepRates]);

  useEffect(() => {
    setRepState(getRepState());
- }, [
-   selectedCategory,
-   proxyGrantorRepRates,
-   activeProfileProxy,
-   connectedProfileRepRates,
- ]);
+ }, [getRepState]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/rep/new-rep/UserPageRepNewRepSearch.tsx` around lines 193 -
238, The inline helper functions (getRepState, getProxyAvailableCredit,
getHeroAvailableRep, getMinValue, getMaxValue) are recreated every render which
will trigger react-hooks/exhaustive-deps warnings and can drift; wrap each
helper in useCallback with the correct internal dependencies (e.g., getRepState
depends on selectedCategory, proxyGrantorRepRates, activeProfileProxy,
connectedProfileRepRates; getProxyAvailableCredit depends on activeProfileProxy;
getHeroAvailableRep depends on activeProfileProxy, proxyGrantorRepRates,
connectedProfileRepRates; getMinValue/getMaxValue depend on repState,
heroAvailableRep, proxyAvailableCredit) and then update the useEffect dependency
arrays to depend on these memoized callbacks (instead of raw props) so the
effects run only when their helper logic actually changes.
components/user/rep/header/UserPageRepHeader.tsx (1)

44-50: sortReps has no component dependencies and can be hoisted outside the component.

The function is a pure comparator with no closure over props/state/context. Defining it at module scope avoids re-creating it on every render and improves readability.

♻️ Proposed refactor

Move sortReps above the component declaration (before line 35):

+const sortReps = (items: RatingStats[]) =>
+  [...items].sort((a, d) => {
+    if (a.rating === d.rating) {
+      return d.contributor_count - a.contributor_count;
+    }
+    return d.rating - a.rating;
+  });

 export default function UserPageRepHeader({ ... }) {
   ...
-  const sortReps = (items: RatingStats[]) =>
-    [...items].sort((a, d) => {
-      if (a.rating === d.rating) {
-        return d.contributor_count - a.contributor_count;
-      }
-      return d.rating - a.rating;
-    });
🤖 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 44 - 50, Hoist
the pure comparator function sortReps out of the component by moving its
declaration to module scope above the component declaration; it has no closure
over props/state and only depends on the RatingStats type, so define const
sortReps = (items: RatingStats[]) => ... at top-level (before the component) to
avoid re-creating it on every render and keep its signature and types unchanged.
components/user/rep/UserPageRep.tsx (1)

24-28: Commented-out code references stale variable names.

The four parameters have been renamed with an _ prefix internally, but the commented-out JSX blocks at lines 78, 83, 117, and 121 still reference the old names (initialRepGivenParams, initialRepReceivedParams, initialCICGivenParams, initialCICReceivedParams). If the dead code blocks are ever uncommented as-is they will fail to compile. Consider either updating the variable names in the comments to match the current destructured identifiers, or removing the commented blocks entirely if they are not intended to be reinstated.

🤖 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 24 - 28, The commented-out
JSX blocks still reference the old variable names (initialRepGivenParams,
initialRepReceivedParams, initialCICGivenParams, initialCICReceivedParams) which
were renamed in the destructure to _initialRepGivenParams,
_initialRepReceivedParams, _initialCICGivenParams, _initialCICReceivedParams;
either update those commented references to the new underscored identifiers or
remove the commented blocks entirely so they cannot be accidentally uncommented
and cause compile errors—search for the commented JSX that mentions the four old
names and replace them with their corresponding underscored names or delete the
dead code.
🤖 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 153-179: UserPageRepHeader renders TopRaterAvatars inside a
<button>, causing nested interactive <a> elements; add a prop (e.g. disableLinks
or renderAsSpan) to TopRaterAvatars and propagate it to OverlappingAvatars so
that when this prop is true the avatar items render non-interactive spans
instead of Link/<a>, then in UserPageRepHeader pass that prop when calling
TopRaterAvatars inside the button and keep navigation handled by the button's
onClick (setEditCategory). Update OverlappingAvatars to branch on the new prop
when creating each avatar element so it emits a span with the same visual markup
but no href; keep the original link behavior default when the prop is falsy.

In `@components/user/rep/new-rep/UserPageRepNewRepSearch.tsx`:
- Around line 241-245: The effect that sets amountStr from repState (useEffect
watching repState) can overwrite user-typed input when repState refetches; add a
useRef flag (e.g., userEditedAmount) that you set to true from the
UserPageRateInput onChange wrapper (the handler that calls setAmountStr) and
then guard the effect so it only calls setAmountStr when
userEditedAmount.current is false; also reset userEditedAmount.current = false
when a new category is selected inside onRepSelect immediately after
setSelectedCategory so selections still prefill.

---

Outside diff comments:
In
`@components/user/identity/statements/utils/UserPageIdentityStatementsStatement.tsx`:
- Around line 27-33: The handleCopy function currently calls
copyToClipboard(title) which can copy the temporary "Copied!" UI text if the
user clicks again during the feedback window; change handleCopy to always copy
from the prop value (statement.statement_value) instead of the local state, i.e.
call copyToClipboard(statement.statement_value) and keep the existing
setTitle("Copied!")/setTimeout(() => setTitle(statement.statement_value), 1000)
behavior so the UI feedback remains correct while clipboard content is always
the actual statement.

In `@components/user/rep/reps/UserPageRepReps.tsx`:
- Around line 70-77: The useEffect that updates setCanEditRep currently depends
on [connectedProfile, profile] but getCanEditRep closes over activeProfileProxy,
so add activeProfileProxy to the dependency array to ensure canEditRep is
recomputed when the proxy changes; update the dependency list for the useEffect
that calls getCanEditRep (the effect that sets canEditRep) to include
activeProfileProxy alongside connectedProfile and profile so UI reflects current
proxy permissions.

---

Duplicate comments:
In `@components/user/rep/header/UserPageRepHeader.tsx`:
- Line 73: In getCanEditRep replace the accidental use of the component-level
variable profile with the function parameter targetProfile: update the
conditional that currently reads profile.handle ===
activeProfileProxy.created_by.handle to use targetProfile.handle instead so the
function operates on the passed-in profile; ensure any other references inside
getCanEditRep consistently use targetProfile rather than the outer profile
variable.
- Around line 17-33: The formatCompact function currently emits "1000.0K" when
values like 999_999 round up; fix by detecting rounding overflow after computing
k = abs/1_000 and before returning the "K" string: compute the rounded value you
would display (e.g., using toFixed(0) or toFixed(1) as the code does), parse it
back to a number and if that rounded value is >= 1000 then switch to the "M"
branch (compute m = abs/1_000_000 and format as before); otherwise return the
"K" string. Keep references to formatCompact and formatNumberWithCommas when
making the change.

In `@components/user/rep/new-rep/UserPageRepNewRepSearch.tsx`:
- Around line 315-331: The onGrantRep handler currently awaits
addRepMutation.mutateAsync(...) without catching its rejection, causing
unhandled promise rejections despite the finally block; update onGrantRep to
wrap the mutateAsync call in a try/catch (inside the existing try) or append a
.catch to the promise so any rejection is consumed (you can rely on
addRepMutation's onError to show the toast) while preserving the existing
finally that calls setMutating(false) and keeping early returns for !success and
invalid input.
- Around line 310-312: The onError callback is still using an unsafe cast
(`error as unknown as string`) which yields “[object Object]”; update the
onError in UserPageRepNewRepSearch so it extracts a real message (e.g., if
(error instanceof Error) use error.message, else use String(error) or a small
helper like getErrorMessage) and pass that string to setToast({ message, type:
"error" }) instead of the double-cast; locate the onError handler and replace
the cast with this proper extraction logic.

In `@components/user/rep/reps/table/UserPageRepRepsTableItem.tsx`:
- Around line 9-25: The formatCompact function has an unreachable k >= 1000
branch and still misformats values like 999_950 as "1000.0K"; update
formatCompact to compute the rounded thousands value (e.g.,
parseFloat(k.toFixed(1)) or use Math.round(k * 10) / 10) and then check that
rounded value >= 1000 to promote to millions (compute m = roundedK / 1000 and
return "1M" or formatted M string); keep the existing sign handling and fall
back to formatNumberWithCommas for small numbers, and apply this change inside
formatCompact (referencing k and m) to ensure correct rollover from K to M.
- Around line 101-113: The conditional is using a truthy check on
rep.rater_contribution which treats 0 as falsy and renders "-" incorrectly;
update the ternary in UserPageRepRepsTableItem (the rep.rater_contribution
check) to explicitly test for null/undefined (e.g. rep.rater_contribution !=
null) or typeof rep.rater_contribution === "number" so that 0 is displayed via
formatNumberWithCommas(rep.rater_contribution) and only null/undefined renders
the "-" fallback.

---

Nitpick comments:
In `@components/profile-activity/ProfileActivityLogs.tsx`:
- Around line 221-231: The filter buttons in the ProfileActivityLogs component
default to type="submit" which can cause accidental form submissions; update the
button elements (the ones using opt.key, onClick={() =>
onMatterChange(opt.value)}, and checking matter === opt.value) to include an
explicit type="button" attribute on each button in that block so they behave as
non-submitting controls even if the component is rendered inside a form.

In `@components/user/identity/header/UserPageIdentityHeaderCIC.tsx`:
- Around line 14-18: Remove the redundant derived state: delete the useState
hook (cicRating, setCicRating) and the useEffect that copies profile.cic, and
read profile.cic directly where cicRating is used; if a local mutable copy is
intended later, keep a state but change the effect dependency to [profile.cic]
instead of [profile] to avoid re-running on unrelated profile changes (update
references to cicRating to use profile.cic or the kept state accordingly).

In
`@components/user/identity/statements/nft-accounts/UserPageIdentityStatementsNFTAccounts.tsx`:
- Line 22: The heading in UserPageIdentityStatementsNFTAccounts reads "NFT
Accounts" (plural) but the empty-state prop noItemsMessage is "No NFT Account
added yet" (singular); update the noItemsMessage string to use the plural form
(for example "No NFT Accounts added yet") so it matches the section heading, or
alternatively change the heading to singular—locate the noItemsMessage prop in
the UserPageIdentityStatementsNFTAccounts component and make the wording
consistent.

In
`@components/user/identity/statements/utils/UserPageIdentityStatementsStatement.tsx`:
- Line 24: Replace the string state title/setTitle with a boolean
isCopied/setIsCopied in the UserPageIdentityStatementsStatement component:
remove useState(statement.statement_value) and instead use useState(false);
update the copy handler to setIsCopied(true) when copied and schedule
setIsCopied(false) with the timeout (avoiding closure over a string), and change
the render branch that currently compares against "Copied!" to read
statement.statement_value directly for non-copied output and use isCopied to
show the "Copied!" UI; ensure any references to title or setTitle are removed
and only statement.statement_value, isCopied and setIsCopied remain.

In `@components/user/rep/header/UserPageRepHeader.tsx`:
- Around line 44-50: Hoist the pure comparator function sortReps out of the
component by moving its declaration to module scope above the component
declaration; it has no closure over props/state and only depends on the
RatingStats type, so define const sortReps = (items: RatingStats[]) => ... at
top-level (before the component) to avoid re-creating it on every render and
keep its signature and types unchanged.

In `@components/user/rep/new-rep/UserPageRepNewRepSearch.tsx`:
- Around line 193-238: The inline helper functions (getRepState,
getProxyAvailableCredit, getHeroAvailableRep, getMinValue, getMaxValue) are
recreated every render which will trigger react-hooks/exhaustive-deps warnings
and can drift; wrap each helper in useCallback with the correct internal
dependencies (e.g., getRepState depends on selectedCategory,
proxyGrantorRepRates, activeProfileProxy, connectedProfileRepRates;
getProxyAvailableCredit depends on activeProfileProxy; getHeroAvailableRep
depends on activeProfileProxy, proxyGrantorRepRates, connectedProfileRepRates;
getMinValue/getMaxValue depend on repState, heroAvailableRep,
proxyAvailableCredit) and then update the useEffect dependency arrays to depend
on these memoized callbacks (instead of raw props) so the effects run only when
their helper logic actually changes.

In `@components/user/rep/reps/UserPageRepReps.tsx`:
- Around line 22-28: Hoist the pure sorting helper out of the component by
moving the sortReps function definition (currently a const sortReps = (items:
RatingStats[]) => [...items].sort(...)) to module scope so it is not re-created
on every render; update any references in the component to use the top-level
sortReps, and apply the same hoisting refactor to the other similar inline
helper at lines 34-37 (the second sorter) so both helpers live in module scope
with no component-scope captures.

In `@components/user/rep/UserPageRep.tsx`:
- Around line 24-28: The commented-out JSX blocks still reference the old
variable names (initialRepGivenParams, initialRepReceivedParams,
initialCICGivenParams, initialCICReceivedParams) which were renamed in the
destructure to _initialRepGivenParams, _initialRepReceivedParams,
_initialCICGivenParams, _initialCICReceivedParams; either update those commented
references to the new underscored identifiers or remove the commented blocks
entirely so they cannot be accidentally uncommented and cause compile
errors—search for the commented JSX that mentions the four old names and replace
them with their corresponding underscored names or delete the dead code.

Comment thread components/user/rep/header/UserPageRepHeader.tsx Outdated
Comment thread components/user/rep/new-rep/UserPageRepNewRepSearch.tsx
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.

🧹 Nitpick comments (1)
components/user/rep/new-rep/UserPageRepNewRepSearch.tsx (1)

247-253: amountNum and newRating compute the same value — consolidate.

Both call getStringAsNumberOrZero(amountStr) on the same input, doubling the work needlessly.

♻️ Proposed refactor
- const amountNum = getStringAsNumberOrZero(amountStr);
+ const currentAmount = getStringAsNumberOrZero(amountStr);
  const isValidValue =
    !!activeProfileProxy ||
-   (amountNum >= minMaxValues.min && amountNum <= minMaxValues.max);
+   (currentAmount >= minMaxValues.min && currentAmount <= minMaxValues.max);

- const newRating = getStringAsNumberOrZero(amountStr);
+ const newRating = currentAmount;
  const haveChanged = newRating !== (repState?.rater_contribution ?? 0);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/rep/new-rep/UserPageRepNewRepSearch.tsx` around lines 247 -
253, The code calls getStringAsNumberOrZero(amountStr) twice (amountNum and
newRating); consolidate by computing it once (keep amountNum or rename to
amountValue) and reuse that single numeric value for both isValidValue and
haveChanged checks (replace newRating usage in haveChanged with amountNum and
remove the redundant newRating declaration), ensuring the comparisons against
minMaxValues and repState?.rater_contribution remain unchanged.
🤖 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/new-rep/UserPageRepNewRepSearch.tsx`:
- Around line 241-245: The effect that sets amount string from repState
(`useEffect` setting setAmountStr(`${repState.rater_contribution}`)`) can
overwrite user edits because repState refetches; add an edit-guard using a
useRef flag (e.g., amountEditedRef) so the effect only updates amount when the
user hasn't edited. In handleRepSearchChange clear the flag when the category is
cleared, in UserPageRateInput's onChange set amountEditedRef.current = true, and
in onRepSelect (after setSelectedCategory(rep)) reset amountEditedRef.current =
false so selecting a new category pre-fills but background refetchs do not stomp
in-flight user typing.
- Around line 91-127: The queryKey construction for proxyGrantorRepRates and
connectedProfileRepRates uses profile?.handle while their queryFn and endpoint
use profile?.query, causing cache-key/endpoint mismatch; update the queryKey
entries in UserPageRepNewRepSearch (specifically the objects passed to
QueryKey.PROFILE_REP_RATINGS for proxyGrantorRepRates and
connectedProfileRepRates) to use handleOrWallet: profile?.query instead of
profile?.handle, and apply the same replacement in
UserPageRepNewRepSearchHeader.tsx and UserPageRepModifyModal.tsx where
PROFILE_REP_RATINGS keys are built.
- Around line 315-331: The onGrantRep async handler leaves a rejected promise
when addRepMutation.mutateAsync throws (even though mutation onError shows a
toast), so update onGrantRep to catch errors from mutateAsync and swallow or
handle them instead of letting them propagate; specifically, wrap the await
addRepMutation.mutateAsync({ amount, category: selectedCategory }) call in a
try/catch (or add a .catch()) inside the existing try block in onGrantRep so
errors are consumed, keep the existing finally that calls setMutating(false),
and do not rethrow the error so the click caller doesn't receive an unhandled
rejection.
- Around line 310-312: The onError handler in UserPageRepNewRepSearch.tsx is
casting the error unsafely with error as unknown as string which yields “[object
Object]”; change the handler for onError to extract a safe text message (e.g.,
if (error instanceof Error) msg = error.message; else msg = String(error)) and
pass that msg into setToast so setToast({ message: msg, type: "error" }) is
used; update the onError callback and/or add a small helper (getErrorMessage) to
centralize this logic for clarity.

---

Nitpick comments:
In `@components/user/rep/new-rep/UserPageRepNewRepSearch.tsx`:
- Around line 247-253: The code calls getStringAsNumberOrZero(amountStr) twice
(amountNum and newRating); consolidate by computing it once (keep amountNum or
rename to amountValue) and reuse that single numeric value for both isValidValue
and haveChanged checks (replace newRating usage in haveChanged with amountNum
and remove the redundant newRating declaration), ensuring the comparisons
against minMaxValues and repState?.rater_contribution remain unchanged.

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: 8

Caution

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

⚠️ Outside diff range comments (3)
components/profile-activity/list/items/ProfileActivityLogHandle.tsx (1)

29-29: ⚠️ Potential issue | 🟡 Minor

Typo: currentcOLOR should be currentColor.

While SVG/CSS color keywords are case-insensitive in browsers, currentcOLOR is clearly a typo and should be corrected for readability and spec-compliance.

✏️ Proposed fix
-              stroke="currentcOLOR"
+              stroke="currentColor"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/profile-activity/list/items/ProfileActivityLogHandle.tsx` at line
29, In the ProfileActivityLogHandle component update the SVG stroke attribute
value from the mistyped "currentcOLOR" to the correct "currentColor"; locate the
JSX in ProfileActivityLogHandle.tsx where stroke="currentcOLOR" is set and
replace it with stroke="currentColor" to fix the typo and ensure
spec-compliant/readable code.
components/user/utils/user-cic-type/icons/UserCICProbablyAccurateIcon.tsx (1)

4-23: ⚠️ Potential issue | 🟠 Major

Both UserCICProbablyAccurateIcon and UserCICAccurateIcon render identical SVG.

The SVG structure, viewBox="0 0 183 183", the ellipse, and all three <path> elements are exactly the same between both components. These should be distinct visual icons; consolidate them into a single component or correct one of the SVGs.

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

In `@components/user/utils/user-cic-type/icons/UserCICProbablyAccurateIcon.tsx`
around lines 4 - 23, Both UserCICProbablyAccurateIcon and UserCICAccurateIcon
currently render identical SVG markup; fix by either consolidating into one
reusable component (e.g., UserCICIcon with a variant prop) or by replacing the
SVG in one component with the correct distinct SVG. Locate the two components
(UserCICProbablyAccurateIcon and UserCICAccurateIcon), extract the shared SVG
into a single component or a helper (e.g., CICIcon or renderSVGForVariant),
update imports/usage to use the new shared component or swap in the correct
alternate SVG for the mistaken component, and remove the duplicated markup so
each component renders its intended distinct visual.
components/profile-activity/list/items/ProfileActivityLogPfp.tsx (1)

39-52: ⚠️ Potential issue | 🟡 Minor

Pre-existing: stroke="currentcOLOR" has incorrect casing (Line 47).

CSS keyword currentColor is case-insensitive so browsers handle it, but the inconsistent casing is a code quality nit worth fixing.

🔧 Proposed fix
-            stroke="currentcOLOR"
+            stroke="currentColor"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/profile-activity/list/items/ProfileActivityLogPfp.tsx` around
lines 39 - 52, In ProfileActivityLogPfp (the SVG path element inside the
component), fix the stroke attribute value from the misspelled "currentcOLOR" to
the correct "currentColor" to normalize casing and improve code quality; locate
the <path> with d="M4 12H20M20 12L14 6M20 12L14 18" and update its stroke
attribute accordingly.
🧹 Nitpick comments (9)
components/user/identity/statements/UserPageIdentityStatements.tsx (1)

117-117: Vestigial xl:tw-grid-cols-5 on the outer wrapper.

The grid is now effectively single-column — the only child (tw-col-span-full) always spans all tracks — so the xl:tw-grid-cols-5 declaration generates five unused column tracks and is dead CSS.

♻️ Proposed cleanup
-          <div className="tw-relative tw-pb-6 lg:tw-pb-8 tw-mx-auto tw-grid tw-grid-cols-1 xl:tw-gap-x-8 xl:tw-gap-y-2 lg:tw-mx-0 xl:tw-grid-cols-5">
+          <div className="tw-relative tw-pb-6 lg:tw-pb-8 tw-mx-auto lg:tw-mx-0">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/identity/statements/UserPageIdentityStatements.tsx` at line
117, The outer wrapper div in UserPageIdentityStatements.tsx uses a vestigial
grid class "xl:tw-grid-cols-5" despite the layout being a single-column grid
(its sole child uses "tw-col-span-full"), producing unused column tracks; remove
"xl:tw-grid-cols-5" from the className on the wrapper div (the element with
className starting "tw-relative tw-pb-6 lg:tw-pb-8 tw-mx-auto tw-grid...") so
the grid only defines the required single column and avoids dead CSS.
components/user/rep/reps/table/UserPageRepRepsTableItem.tsx (1)

28-29: progressPercent can exceed 100 if |rep.rating| > maxRep.

If a data race or upstream calculation change ever makes Math.abs(rep.rating) > maxRep, the bar width exceeds 100%. The parent's tw-overflow-hidden clips it visually, so this isn't user-visible today, but clamping makes the intent explicit.

Optional: clamp to 100
  const progressPercent =
-    maxRep > 0 ? (Math.abs(rep.rating) / maxRep) * 100 : 0;
+    maxRep > 0 ? Math.min((Math.abs(rep.rating) / maxRep) * 100, 100) : 0;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/rep/reps/table/UserPageRepRepsTableItem.tsx` around lines 28
- 29, Compute progressPercent safely by clamping the calculated value to the
0–100 range: replace the current calculation of progressPercent with a clamped
version that first computes rawPercent = (Math.abs(rep.rating) / maxRep) * 100
(guarding for maxRep <= 0), then set progressPercent = Math.min(100, Math.max(0,
rawPercent)). Update the variable named progressPercent in
UserPageRepRepsTableItem (where rep.rating and maxRep are used) so the progress
bar cannot exceed 100% or go below 0 even if rep.rating or maxRep change.
components/profile-activity/list/items/ProfileActivityLogGeneralStatement.tsx (1)

15-16: tw-overflow-hidden is redundant alongside tw-truncate

tw-truncate already expands to overflow: hidden; text-overflow: ellipsis; white-space: nowrap, so tw-overflow-hidden has no additional effect.

♻️ Proposed cleanup
-<span className="tw-min-w-0 tw-truncate tw-overflow-hidden tw-text-sm md:tw-text-md tw-font-medium tw-text-iron-200">
+<span className="tw-min-w-0 tw-truncate tw-text-sm md:tw-text-md tw-font-medium tw-text-iron-200">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/profile-activity/list/items/ProfileActivityLogGeneralStatement.tsx`
around lines 15 - 16, In ProfileActivityLogGeneralStatement.tsx, the span
element that renders {log.contents.statement.statement_value} includes a
redundant class "tw-overflow-hidden" alongside "tw-truncate"; remove
"tw-overflow-hidden" from the className on that span so it only uses
"tw-truncate" (and keep the other classes intact) to avoid duplicate CSS rules
and simplify the markup.
components/profile-activity/list/items/ProfileActivityLogRate.tsx (1)

121-123: empty:tw-hidden guard is unnecessary given full enum coverage

LOG_MATTER_STR covers all three RateMatter variants (REP, NIC, DROP_REP), so this span will never be empty at runtime. The guard is harmless but misleading about the invariant. Consider removing it, or document the intent.

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

In `@components/profile-activity/list/items/ProfileActivityLogRate.tsx` around
lines 121 - 123, The span showing rating text includes an unnecessary Tailwind
empty:tw-hidden guard; remove the "empty:tw-hidden" token from the className on
the span in the ProfileActivityLogRate component (the element that renders
{LOG_MATTER_STR[log.contents.rating_matter]}) since LOG_MATTER_STR covers all
RateMatter variants, or if you want to keep it for clarity add a comment near
LOG_MATTER_STR explaining the full enum coverage; update the className
accordingly and ensure no other code relies on that empty-state behavior.
components/brain/notifications/identity-rating/NotificationIdentityRating.tsx (1)

42-45: getProfileLink can be simplified to a ternary now that the isRep branch is removed.

The function wrapper adds indirection with no remaining conditional logic.

♻️ Proposed simplification
-  const getProfileLink = (): string | null => {
-    if (!myHandle) return null;
-    return `/${myHandle}/identity`;
-  };
-  const linkHref = getProfileLink();
+  const linkHref = myHandle ? `/${myHandle}/identity` : null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/brain/notifications/identity-rating/NotificationIdentityRating.tsx`
around lines 42 - 45, The getProfileLink function currently uses an if statement
but can be simplified to a single-line ternary; replace the function body in
NotificationIdentityRating.tsx (getProfileLink) to return myHandle ?
`/${myHandle}/identity` : null so remove the explicit if and return directly.
components/user/rep/reps/UserPageRepReps.tsx (1)

39-61: getCanEditRep references the outer profile prop instead of the targetProfile parameter.

Line 50 uses profile.handle from the component's closure rather than targetProfile.handle from the function parameter. Currently harmless since the only call site passes profile as targetProfile, but this defeats the purpose of parameterizing the function.

Proposed fix
     if (activeProfileProxy) {
-      if (profile.handle === activeProfileProxy.created_by.handle) {
+      if (targetProfile.handle === activeProfileProxy.created_by.handle) {
         return false;
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/user/rep/reps/UserPageRepReps.tsx` around lines 39 - 61, The
helper getCanEditRep incorrectly reads profile.handle from the outer scope
instead of using the targetProfile parameter; update the function to reference
targetProfile.handle wherever it currently uses profile.handle (particularly in
the activeProfileProxy created_by check) so the function truly depends only on
its arguments (symbols: getCanEditRep, activeProfileProxy,
activeProfileProxy.created_by.handle, targetProfile.handle).
components/profile-activity/ProfileActivityLogs.tsx (1)

134-156: disableActiveGroup and initialParams.pageSize are missing from the dependency array.

The useEffect at line 134 reads disableActiveGroup and initialParams.pageSize inside the callback but neither appears in the dependency array. If either value changes across renders, params will go stale.

In practice both are likely static per mount, so this is low-risk — but it may trigger the react-hooks/exhaustive-deps lint rule.

Proposed fix
   }, [
     currentPage,
     selectedFilters,
     initialParams.handleOrWallet,
+    initialParams.pageSize,
     targetType,
     activeGroupId,
     matter,
+    disableActiveGroup,
   ]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/profile-activity/ProfileActivityLogs.tsx` around lines 134 - 156,
The useEffect that calls setParams with convertActivityLogParams reads
disableActiveGroup and initialParams.pageSize inside the callback but doesn't
include them in its dependency array, which can lead to stale params or lint
failures; update the dependency array for the useEffect containing
setParams/convertActivityLogParams to also include disableActiveGroup and
initialParams.pageSize so the effect re-runs when those values change.
components/user/rep/header/UserPageRepHeader.tsx (1)

26-32: sortReps is a pure function — consider hoisting outside the component.

sortReps doesn't depend on any props or state but is re-created on every render. Extracting it as a module-level function avoids unnecessary allocations and makes it independently testable.

Proposed move
+const sortReps = (items: RatingStats[]) =>
+  [...items].sort((a, d) => {
+    if (a.rating === d.rating) {
+      return d.contributor_count - a.contributor_count;
+    }
+    return d.rating - a.rating;
+  });
+
 export default function UserPageRepHeader({
   repRates,
   profile,
 ...
-  const sortReps = (items: RatingStats[]) =>
-    [...items].sort((a, d) => {
-      if (a.rating === d.rating) {
-        return d.contributor_count - a.contributor_count;
-      }
-      return d.rating - a.rating;
-    });
🤖 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 26 - 32, The
helper sortReps is a pure, render-independent function defined inside the
component; hoist it to module scope as a standalone function (e.g., export it if
tests need it) so it isn’t re-created on each render and can be unit-tested
independently; move the existing const sortReps = (items: RatingStats[]) => ...
out of the component body, keep its signature identical, update any
imports/exports if required, and ensure all references inside UserPageRepHeader
still call the hoisted sortReps.
components/user/rep/UserPageRep.tsx (1)

84-97: Commented-out raters tables are dead code — consider removing or tracking with a TODO/issue.

Two large commented-out blocks reference ProfileRatersTableWrapper which isn't even imported. If this is intentional future work, a single // TODO(#issue): re-enable raters tables would be clearer than leaving JSX in a block comment.

Also applies to: 124-135

🤖 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 84 - 97, Remove the dead
commented-out JSX blocks that reference ProfileRatersTableWrapper (and the
associated initialRepGivenParams / initialRepReceivedParams) and replace each
large block with a single concise comment like // TODO(`#issue`): re-enable raters
tables so the intent is tracked; if you truly intend to keep the code for
near-term work instead, add a TODO referencing the issue/PR number and import
ProfileRatersTableWrapper only when re-enabled. Ensure both duplicated blocks
(the one around ProfileRatersTableWrapper and the other similar block) are
handled the same way.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/`[user]/identity/page.tsx:
- Around line 43-68: The boolean values for the initial rater params are
inverted; update the getInitialRatersParams calls so "Given" params pass given:
true and "Received" params pass given: false: change initialRepGivenParams to
call getInitialRatersParams(... given: true ...), initialRepReceivedParams to
use given: false, and likewise change initialCICGivenParams to given: true and
initialCICReceivedParams to given: false so they align with
ProfileRatersTableWrapper.getType() mapping; leave
getInitialActivityLogParams(handleOrWallet) unchanged.

In `@components/user/layout/userTabs.config.ts`:
- Line 30: The `collected` tab object in the TAB_DEFINITIONS array is missing
the leading indentation that other tab entries use; locate the TAB_DEFINITIONS
constant and the `collected` entry and reformat it to match the same
indentation/spacing style as the surrounding tab objects (align braces and
properties with the other entries) so the array is consistently indented.

In `@components/user/rep/UserPageRep.tsx`:
- Around line 23-37: Remove the four unused props from the UserPageRep component
signature and its typed props object: _initialRepReceivedParams,
_initialRepGivenParams, _initialCICReceivedParams, and _initialCICGivenParams
(all typed as ProfileRatersParams). Delete them from the destructured parameters
in the export default function UserPageRep(...) and from the corresponding type
annotation block so the component no longer expects or receives these unused
values; then update any callers to stop constructing/passing those params until
the raters tables (the commented references) are re-enabled.

In `@components/user/rep/UserPageRepMobile.tsx`:
- Around line 38-40: The editCategory state (editCategory and setEditCategory)
is dead—never set to a non-null value—and the modal that depends on it never
renders; remove the unused state declaration and the corresponding modal JSX
(the modal that only uses editCategory and onClose to set it back to null), or
alternatively, wire the parent-level editCategory/setEditCategory into the child
component (UserPageRepRepsTableItem) so the child opens the modal by calling
setEditCategory(category) and the parent renders the modal; choose one approach
and update imports/props accordingly to eliminate the dangling state and dead
modal.
- Around line 44-76: Extract sortReps and getCanEditRep into a shared helper
(e.g., helpers/rep.helpers.ts) and import them into UserPageRepMobile.tsx and
UserPageRepHeader.tsx to remove duplication; in getCanEditRep inside
UserPageRepMobile replace the incorrect reference profile.handle with
targetProfile.handle; move the TopRaterAvatars component out of the Button
wrapper (or restructure the Button so it doesn't contain another interactive
component) to avoid nested interactive elements; and fix the dead editCategory
state by either removing editCategory/edit modal code or enabling it—pass
setEditCategory down to UserPageRepRepsTable (or otherwise set editCategory) so
the edit modal can be opened, ensuring any references to editCategory and
setEditCategory are updated accordingly.

In `@components/user/utils/user-cic-type/icons/UserCICInaccurateIcon.tsx`:
- Line 2: The default-exported React component function is misnamed: the file is
UserCICInaccurateIcon.tsx but the function is declared as UserCICAccurateIcon,
causing a name collision in DevTools; fix by renaming the function declaration
to UserCICInaccurateIcon (or alternatively set the component's displayName =
'UserCICInaccurateIcon') and update any internal references in that file so the
exported default and runtime component name match the filename; ensure you
reference the symbol UserCICInaccurateIcon (or set displayName) instead of
UserCICAccurateIcon.

In `@helpers/Helpers.ts`:
- Around line 152-159: Add clear doc comments above both formatLargeNumber and
formatNumberCompact (and the compactFormatter constant) explaining their
distinct purposes: note that formatLargeNumber is a hand-rolled formatter
(supports only K/M, explicitly handles negatives, used in many components and
must be preserved) and formatNumberCompact delegates to Intl.NumberFormat
(supports K/M/B/T automatically, cleaner API for new use cases). In the comments
indicate when to prefer each (keep existing callers of formatLargeNumber; use
formatNumberCompact for broader compact formatting), mention any edge-cases
(negative numbers behavior) and that compactFormatter is the Intl instance used
by formatNumberCompact.
- Around line 157-159: The formatter formatNumberCompact currently returns
compactFormatter.format(n) which yields "NaN" or "∞" for invalid inputs; guard
formatNumberCompact by checking Number.isFinite(n) (or isFinite) and return "-"
for non-finite values before calling compactFormatter.format(n), so update the
function (formatNumberCompact) to validate n and only call compactFormatter when
n is a finite number.

---

Outside diff comments:
In `@components/profile-activity/list/items/ProfileActivityLogHandle.tsx`:
- Line 29: In the ProfileActivityLogHandle component update the SVG stroke
attribute value from the mistyped "currentcOLOR" to the correct "currentColor";
locate the JSX in ProfileActivityLogHandle.tsx where stroke="currentcOLOR" is
set and replace it with stroke="currentColor" to fix the typo and ensure
spec-compliant/readable code.

In `@components/profile-activity/list/items/ProfileActivityLogPfp.tsx`:
- Around line 39-52: In ProfileActivityLogPfp (the SVG path element inside the
component), fix the stroke attribute value from the misspelled "currentcOLOR" to
the correct "currentColor" to normalize casing and improve code quality; locate
the <path> with d="M4 12H20M20 12L14 6M20 12L14 18" and update its stroke
attribute accordingly.

In `@components/user/utils/user-cic-type/icons/UserCICProbablyAccurateIcon.tsx`:
- Around line 4-23: Both UserCICProbablyAccurateIcon and UserCICAccurateIcon
currently render identical SVG markup; fix by either consolidating into one
reusable component (e.g., UserCICIcon with a variant prop) or by replacing the
SVG in one component with the correct distinct SVG. Locate the two components
(UserCICProbablyAccurateIcon and UserCICAccurateIcon), extract the shared SVG
into a single component or a helper (e.g., CICIcon or renderSVGForVariant),
update imports/usage to use the new shared component or swap in the correct
alternate SVG for the mistaken component, and remove the duplicated markup so
each component renders its intended distinct visual.

---

Duplicate comments:
In
`@components/profile-activity/list/items/utils/ProfileActivityLogItemWrapper.tsx`:
- Around line 36-41: ProfileActivityLogItemWrapper currently calls useIdentity
per instance (const { profile } = useIdentity({...}); const pfp = profile?.pfp
?? null;) causing N API calls for N users; change this so the wrapper does not
trigger individual requests: either accept a resolved profile prop (e.g.,
profile) from the parent or switch to a batched/shared lookup (implement a new
useIdentities hook that accepts an array of handleOrWallets and returns a map of
profiles), then update ProfileActivityLogItemWrapper to read pfp from the
provided profile or from profilesMap[handleOrWallet] and keep the isArchived
nulling logic intact so archived items skip lookups.

In `@components/user/identity/statements/UserPageIdentityStatements.tsx`:
- Around line 93-114: The rightAccessory div in UserPageIdentityStatements (the
element with role="button", tabIndex={0}, aria-label="Statements help" and
data-tooltip-id="statements-help") lacks activation handlers; either remove role
and tabIndex if it’s purely decorative, or add accessible activation handlers:
implement an onClick that triggers the tooltip/action and an onKeyDown that
listens for Enter/Space and calls the same handler, and ensure the element has
an appropriate aria-pressed/state if needed; update the component
(UserPageIdentityStatements) to wire that handler so keyboard and assistive tech
can activate the control.

In `@components/user/rep/header/UserPageRepHeader.tsx`:
- Around line 44-67: getCanEditRep is using the outer closure variable profile
instead of the function parameter targetProfile which breaks the function
contract; update the conditional that checks the proxy owner (currently
comparing profile.handle to activeProfileProxy.created_by.handle) to compare
targetProfile.handle instead, ensuring all subsequent comparisons in
getCanEditRep consistently use the targetProfile parameter rather than the
component-scoped profile variable.
- Around line 134-162: TopRaterAvatars is rendering anchor/Link elements via
OverlappingAvatars and those Links are being nested inside a <button> (in
UserPageRepHeader's topReps map), which is invalid HTML; add a boolean prop
(e.g., disableLinks) to OverlappingAvatars and TopRaterAvatars (default false)
so when true they render non-interactive <span> wrappers instead of Link/anchor,
update prop types/signatures accordingly, and pass disableLinks={true} from
UserPageRepHeader where TopRaterAvatars is used inside the button; ensure the
new prop is threaded through OverlappingAvatars -> avatar rendering logic and
that accessible text/alt attributes are preserved on the spans.

In `@components/user/rep/reps/table/UserPageRepRepsTable.tsx`:
- Line 29: The component UserPageRepRepsTable currently keeps a persistent
showAll state that isn't reset when the reps prop changes; add a useEffect
inside UserPageRepRepsTable that listens for changes to reps and calls
setShowAll(false) to reset expansion state whenever reps updates (use the
existing showAll/setShowAll state variables so the effect only runs on reps
change).

In `@components/user/rep/reps/table/UserPageRepRepsTableItem.tsx`:
- Around line 83-95: The JSX uses a truthy check on rep.rater_contribution which
treats 0 as falsy; update the conditional in UserPageRepRepsTableItem to check
for null/undefined instead (e.g., rep.rater_contribution !== null &&
rep.rater_contribution !== undefined or using the nullish coalescing check) so
that 0 renders via formatNumberWithCommas(rep.rater_contribution) instead of the
"-" fallback; keep the same span styles and fallback only when the value is
actually null/undefined.

In `@components/user/rep/UserPageRepMobile.tsx`:
- Around line 68-69: The conditional in UserPageRepMobile incorrectly references
the outer closure variable profile instead of the function parameter
targetProfile; update the check inside the activeProfileProxy branch (the
comparison currently using profile.handle) to use targetProfile.handle so the
function compares the passed-in targetProfile against
activeProfileProxy.created_by.handle (same fix as in UserPageRepHeader).
- Around line 95-135: The Rep score button in UserPageRepMobile nests the
TopRaterAvatars component (which renders <a> links), violating HTML rules
against nested interactive elements; fix by removing interactive elements from
inside the <button>: either move TopRaterAvatars out of the button (place it
adjacent to the button) or modify TopRaterAvatars to accept a prop (e.g.,
nonInteractive=true) so it renders non-interactive avatars/spans when used
inside UserPageRepMobile; update the UserPageRepMobile JSX around the rep button
and the TopRaterAvatars usage accordingly.

---

Nitpick comments:
In
`@components/brain/notifications/identity-rating/NotificationIdentityRating.tsx`:
- Around line 42-45: The getProfileLink function currently uses an if statement
but can be simplified to a single-line ternary; replace the function body in
NotificationIdentityRating.tsx (getProfileLink) to return myHandle ?
`/${myHandle}/identity` : null so remove the explicit if and return directly.

In
`@components/profile-activity/list/items/ProfileActivityLogGeneralStatement.tsx`:
- Around line 15-16: In ProfileActivityLogGeneralStatement.tsx, the span element
that renders {log.contents.statement.statement_value} includes a redundant class
"tw-overflow-hidden" alongside "tw-truncate"; remove "tw-overflow-hidden" from
the className on that span so it only uses "tw-truncate" (and keep the other
classes intact) to avoid duplicate CSS rules and simplify the markup.

In `@components/profile-activity/list/items/ProfileActivityLogRate.tsx`:
- Around line 121-123: The span showing rating text includes an unnecessary
Tailwind empty:tw-hidden guard; remove the "empty:tw-hidden" token from the
className on the span in the ProfileActivityLogRate component (the element that
renders {LOG_MATTER_STR[log.contents.rating_matter]}) since LOG_MATTER_STR
covers all RateMatter variants, or if you want to keep it for clarity add a
comment near LOG_MATTER_STR explaining the full enum coverage; update the
className accordingly and ensure no other code relies on that empty-state
behavior.

In `@components/profile-activity/ProfileActivityLogs.tsx`:
- Around line 134-156: The useEffect that calls setParams with
convertActivityLogParams reads disableActiveGroup and initialParams.pageSize
inside the callback but doesn't include them in its dependency array, which can
lead to stale params or lint failures; update the dependency array for the
useEffect containing setParams/convertActivityLogParams to also include
disableActiveGroup and initialParams.pageSize so the effect re-runs when those
values change.

In `@components/user/identity/statements/UserPageIdentityStatements.tsx`:
- Line 117: The outer wrapper div in UserPageIdentityStatements.tsx uses a
vestigial grid class "xl:tw-grid-cols-5" despite the layout being a
single-column grid (its sole child uses "tw-col-span-full"), producing unused
column tracks; remove "xl:tw-grid-cols-5" from the className on the wrapper div
(the element with className starting "tw-relative tw-pb-6 lg:tw-pb-8 tw-mx-auto
tw-grid...") so the grid only defines the required single column and avoids dead
CSS.

In `@components/user/rep/header/UserPageRepHeader.tsx`:
- Around line 26-32: The helper sortReps is a pure, render-independent function
defined inside the component; hoist it to module scope as a standalone function
(e.g., export it if tests need it) so it isn’t re-created on each render and can
be unit-tested independently; move the existing const sortReps = (items:
RatingStats[]) => ... out of the component body, keep its signature identical,
update any imports/exports if required, and ensure all references inside
UserPageRepHeader still call the hoisted sortReps.

In `@components/user/rep/reps/table/UserPageRepRepsTableItem.tsx`:
- Around line 28-29: Compute progressPercent safely by clamping the calculated
value to the 0–100 range: replace the current calculation of progressPercent
with a clamped version that first computes rawPercent = (Math.abs(rep.rating) /
maxRep) * 100 (guarding for maxRep <= 0), then set progressPercent =
Math.min(100, Math.max(0, rawPercent)). Update the variable named
progressPercent in UserPageRepRepsTableItem (where rep.rating and maxRep are
used) so the progress bar cannot exceed 100% or go below 0 even if rep.rating or
maxRep change.

In `@components/user/rep/reps/UserPageRepReps.tsx`:
- Around line 39-61: The helper getCanEditRep incorrectly reads profile.handle
from the outer scope instead of using the targetProfile parameter; update the
function to reference targetProfile.handle wherever it currently uses
profile.handle (particularly in the activeProfileProxy created_by check) so the
function truly depends only on its arguments (symbols: getCanEditRep,
activeProfileProxy, activeProfileProxy.created_by.handle, targetProfile.handle).

In `@components/user/rep/UserPageRep.tsx`:
- Around line 84-97: Remove the dead commented-out JSX blocks that reference
ProfileRatersTableWrapper (and the associated initialRepGivenParams /
initialRepReceivedParams) and replace each large block with a single concise
comment like // TODO(`#issue`): re-enable raters tables so the intent is tracked;
if you truly intend to keep the code for near-term work instead, add a TODO
referencing the issue/PR number and import ProfileRatersTableWrapper only when
re-enabled. Ensure both duplicated blocks (the one around
ProfileRatersTableWrapper and the other similar block) are handled the same way.

Comment thread app/[user]/identity/page.tsx Outdated
Comment thread components/user/layout/userTabs.config.ts
Comment thread components/user/rep/UserPageRep.tsx
Comment thread components/user/rep/UserPageRepMobile.tsx Outdated
Comment thread components/user/rep/UserPageRepMobile.tsx Outdated
Comment thread components/user/utils/user-cic-type/icons/UserCICInaccurateIcon.tsx Outdated
Comment thread helpers/Helpers.ts Outdated
Comment thread helpers/Helpers.ts Outdated
Signed-off-by: ragnep <ragneinfo@gmail.com>
Signed-off-by: ragnep <ragneinfo@gmail.com>
Signed-off-by: ragnep <ragneinfo@gmail.com>
Signed-off-by: ragnep <ragneinfo@gmail.com>
Signed-off-by: ragnep <ragneinfo@gmail.com>
Signed-off-by: ragnep <ragneinfo@gmail.com>
Signed-off-by: ragnep <ragneinfo@gmail.com>
Signed-off-by: ragnep <ragneinfo@gmail.com>
@sonarqubecloud
Copy link
Copy Markdown

@ragnep ragnep merged commit 30924cc into main Feb 23, 2026
6 checks passed
@ragnep ragnep deleted the profile-page-identity-rep-combined branch February 23, 2026 15:17
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