Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR consolidates the Identity and Rep user profile pages by removing separate Identity tab components and redirecting the Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 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 | 🟠 MajorIncomplete refactor:
typestill required by the TypeScript type contractThe destructured parameter list (line 10) correctly drops
type, but line 13 keepsreadonly type: ProfileRatersTableType;in the interface. TypeScript uses the interface as the public contract, so all callers (ProfileRatersTableBody.tsx, test files) are still required to supplytypeeven though the value is silently discarded. TheProfileRatersTableTypeimport on line 5 is now dead code.Additionally,
getProfileRouteon 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 | 🟡 MinorInconsistent
stopPropagationbetweengoToOpenseaandgoToEtherscan.
goToOpensea(Line 33) callse.stopPropagation()butgoToEtherscan(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 | 🟠 MajorBug:
handleCopycopies thetitlestate, 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 | 🟡 MinorMissing indentation on the
collectedtab's opening brace.Line 30 has
{at column 0 rather than the standard 2-space indent used by every other tab entry inTAB_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 | 🔴 CriticalAdd 301 redirect from
/[user]/identityto/[user]/repto avoid breaking existing links.The
identitytab has been cleanly removed—no lingering string references, noTAB_IDS.IDENTITYpatterns, and the route file is gone. However, there is no configured 301 redirect. Without it, existing bookmarks and links to/[user]/identitywill return 404 instead of redirecting to the new/[user]/reproute, damaging SEO and user experience.Configure the redirect in
next.config.tsby adding aredirects()function tosharedConfig: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 | 🟠 MajorRemove dead code:
onRepSearch, modal state, and modal rendering are unreachable.
onRepSearch(line 24) is the only place that setsisAddNewRepModalOpentotrueandrepToAddto a value, but it is never called — it's no longer passed toUserPageRepNewRepSearch. This meansisAddNewRepModalOpenstaysfalseandrepToAddstaysnull, so the condition on line 48 is never satisfied andUserPageRepModifyModalnever renders.
UserPageRepNewRepSearchnow handles the rep search and grant flow internally (usingonRepSelectandonGrantRep), 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:getProfileRoutewrapper is now a no-op abstractionAfter removing the CIC/REP branching,
getProfileRouteis 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-solidis redundant.Tailwind's preflight overrides default border styles for all elements with
*, ::before, ::after { border-width: 0; border-style: solid; ... }. Sinceborder-style: solidis already set globally,tw-border-solidis a no-op alongsidetw-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-solidis redundant alongsidetw-border.Tailwind's preflight applies
border: 0 solid;globally to all elements (*, ::before, ::after, ::backdrop, ::file-selector-button), which setsborder-style: solidby default. When you add thetw-borderutility (which only setsborder-width: 1px), the solid style is already inherited from preflight, makingtw-border-solidunnecessary.♻️ 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!activeProfileProxyconditions here.Lines 105-113 and 115-124 both check
!activeProfileProxyindependently. 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 smallIconButtonWithTooltipcomponent 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: Vestigialxl:tw-grid-cols-5— the sole child spans full width.Line 91 declares
xl:tw-grid-cols-5but the only child (Line 92) usestw-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: Redundanttw-rounded-fullonimgelement — remove the explicit one.
avatarRing(line 48) already containstw-rounded-full, so the explicittw-rounded-fullprepended on line 68 duplicates it. Tailwind handles duplicates harmlessly, but it's noisy and could cause confusion if the class is ever changed inavatarRing.♻️ 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:queryKeynormalizeshandleOrWalletto lowercase but the endpoint URL does not.The query key uses
handleOrWallet.toLowerCase()(line 38), while the API endpoint on line 42 uses the originalhandleOrWallet. 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:getProfileLinkcan be flattened to a computed value.With the
/identitybranch 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 toUserPageRepNewRepSearch, a dedicated test exercising themutateAsynccall 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.
useIdentityfires 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 samehandleOrWallet, 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:getValueStrdoesn't strip a leading zero after a minus sign (e.g., pasted"-05").If a user pastes a value like
"-05", the checkval.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 byhandleChange, 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:ProfileRatersTableWrapperis 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 frompage.tsxthroughUserPageRepWrapperto here has no runtime effect.Either:
- Remove these props and their upstream wiring until the tables are ready, or
- Keep them but add a brief
// TODOnote 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:userEventis 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, andisSaveDisabledare each stored as state and synchronized viauseEffect, but they could be simple derivedconstvalues (likeisValidValuenow 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:amountStrpre-fill effect can cause subtle UX issues.When
repStatechanges (e.g., query data refetches in the background), lines 241-245 overwriteamountStr— 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 viauseState+useEffect— preferuseMemofor computed values.
repState,proxyAvailableCredit,heroAvailableRep, andminMaxValuesare all purely derived from other state/query data. UsinguseState+useEffectintroduces an extra render cycle where these values are stale (the render that triggers the effect sees old derived state).useMemocomputes 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:
useMemowould need to be added to theuseRef, useStateimport fromreactand the helper functions (getRepState, etc.) would need to be stabilized withuseCallbackor inlined into theuseMemo.🤖 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:sortRepsmutates therepsprop array in place.
Array.prototype.sort()sorts in place. Sinceitemsis therepsprop 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 samerepsarray is also used formaxRepcomputation andhasMorecheck.♻️ 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 informatCompact.When
absis between ~999,950 and 999,999,(abs / 1_000).toFixed(1)rounds up to"1000.0", producing"1000.0K"instead of"1M". Consider usingIntl.NumberFormatwithnotation: "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:AnimatePresenceandCommonAnimationOpacity.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: ConsideruseMemoinstead ofuseState+useEffectfor derived data.
topRepsis a pure derivation ofrepRates?.rating_stats. The current pattern (state + effect) causes an extra render cycle with stale data before the effect fires. AuseMemoavoids 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.
There was a problem hiding this comment.
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 | 🔴 CriticalTypo in
strokeattribute — 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 thetw-text-iron-400color 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 = truepreserves 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 oldsmallcheck, which is the intended opt-out behavior.One cosmetic note: when
classNameisundefined, 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,classNamecan 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-hiddenis redundant alongsidetw-truncate.
tw-truncatealready setsoverflow: hidden(plustext-overflow: ellipsisandwhite-space: nowrap), sotw-overflow-hiddenis 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:getProfileRoutecan be inlined — the function wrapper adds no value.With the switch removed,
getProfileRouteis 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:userprop is dead code — remove it from the interface and both call sites.The
userparameter (line 16) is never referenced anywhere in the component. Two call sites inProfileActivityLogs.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: MovesortRepsoutside the component — it's a pure comparator and triggers anexhaustive-depsviolation.
sortRepscloses over no state or props (it only touches itsitemsparameter andRatingStatsfields), so there is no reason to recreate it on every render. More concretely, because it is redefined on each render but omitted from theuseEffectdependency array at line 56, ESLint'sreact-hooks/exhaustive-depsrule 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
sortRepsat module scope theuseEffectdependency 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 toisTouchScreen ? 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 smallActionIconButtoncomponent (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:sortRepsmutates therepsprop array in-place.
Array.prototype.sort()mutates its receiver. Sincereps(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 thatdisplayedRepsandmaxRepalso 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
copysubstitution 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 inonError.
error as unknown as stringwill produce garbled toast messages if the API returns a non-string error (e.g., anErrorobject →"[object Object]"). Consider usingerror 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:tooltipButtonContentandfullButtonContentare 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:onFocusunconditionally opens the dropdown even when a category is already selected
onFocus={() => setIsOpen(true)}bypasses the guard in thecategoriesToDisplayeffect (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:amountNumandnewRatingare the same computation — merge into one variableBoth 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
parsedAmountinUserRateAdjustmentHelper'sadjustedValueprop 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 fiveuseState+useEffectpairs for derived state withuseMemo
getRepState,getProxyAvailableCredit,getHeroAvailableRep,getMinValue, andgetMaxValueare all pure derivations of existing state/context. Storing them inuseStateand keeping them in sync viauseEffectintroduces an extra render cycle on every dependency change and makes the helper functions appear in none of theuseEffectdependency arrays (which would be flagged byreact-hooks/exhaustive-deps). The idiomatic React pattern isuseMemo: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 patternThis collapses the 10 lines of
useState+ the fiveuseEffectblocks entirely and is lint-clean.Note: the
minMaxValuesuseEffectdependency array at line 237 also includesproxyGrantorRepRateseven thoughgetMinValue/getMaxValuedon't directly read it — a redundant dep that theuseMemoapproach 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).
There was a problem hiding this comment.
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
activeProfileProxyis missing from theuseEffectdependency array — stale permission state.
getCanEditRepcloses overactiveProfileProxy(line 49–56) to determine whether a proxy can allocate rep, butactiveProfileProxyis absent from the dependency array on line 77. IfactiveProfileProxychanges (e.g., user switches proxy or it clears) whileconnectedProfileandprofileremain the same,canEditRepwill 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
handleCopymay 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,titleis 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: RedundantuseState/useEffectderived-state pattern
cicRatingis never mutated locally — it only mirrorsprofile.cic. TheuseState+useEffectcombo can be dropped in favour of readingprofile.cicdirectly, which also eliminates the one-render stale-value window that the current pattern creates (the initial render and any render before the effect fires showprofile.cicfrom 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:sortRepsis redefined on every render; consider hoisting it.
sortRepsis 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 booleanisCopiedflag.
titleis initialised tostatement.statement_valuebut the non-copied display branch (line 56) readsstatement.statement_valuedirectly from the prop — it never actually renderstitle. The string state exists solely to detect the"Copied!"condition. A boolean is simpler and eliminates the stale-closure risk in thesetTimeoutreset.♻️ 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 addingtype="button"to filter buttons.These
<button>elements lack an explicittypeattribute. While they're likely not inside a<form>today, buttons default totype="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 insideuseEffectbodies are not included in the dependency arrays — consider memoizing withuseCallback.
getRepState,getProxyAvailableCredit,getHeroAvailableRep,getMinValue, andgetMaxValueare 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 triggerreact-hooks/exhaustive-depslint 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:sortRepshas 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
sortRepsabove 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.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
components/user/rep/new-rep/UserPageRepNewRepSearch.tsx (1)
247-253:amountNumandnewRatingcompute 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.
There was a problem hiding this comment.
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 | 🟡 MinorTypo:
currentcOLORshould becurrentColor.While SVG/CSS color keywords are case-insensitive in browsers,
currentcOLORis 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 | 🟠 MajorBoth
UserCICProbablyAccurateIconandUserCICAccurateIconrender 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 | 🟡 MinorPre-existing:
stroke="currentcOLOR"has incorrect casing (Line 47).CSS keyword
currentColoris 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: Vestigialxl:tw-grid-cols-5on the outer wrapper.The grid is now effectively single-column — the only child (
tw-col-span-full) always spans all tracks — so thexl:tw-grid-cols-5declaration 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:progressPercentcan 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'stw-overflow-hiddenclips 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-hiddenis redundant alongsidetw-truncate
tw-truncatealready expands tooverflow: hidden; text-overflow: ellipsis; white-space: nowrap, sotw-overflow-hiddenhas 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-hiddenguard is unnecessary given full enum coverage
LOG_MATTER_STRcovers all threeRateMattervariants (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:getProfileLinkcan be simplified to a ternary now that theisRepbranch 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:getCanEditRepreferences the outerprofileprop instead of thetargetProfileparameter.Line 50 uses
profile.handlefrom the component's closure rather thantargetProfile.handlefrom the function parameter. Currently harmless since the only call site passesprofileastargetProfile, 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:disableActiveGroupandinitialParams.pageSizeare missing from the dependency array.The
useEffectat line 134 readsdisableActiveGroupandinitialParams.pageSizeinside the callback but neither appears in the dependency array. If either value changes across renders,paramswill go stale.In practice both are likely static per mount, so this is low-risk — but it may trigger the
react-hooks/exhaustive-depslint 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:sortRepsis a pure function — consider hoisting outside the component.
sortRepsdoesn'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
ProfileRatersTableWrapperwhich isn't even imported. If this is intentional future work, a single// TODO(#issue): re-enable raters tableswould 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.
|



Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Design Updates
Chores