Feature/UI revamp and referral system#440
Conversation
Tailwind palette aligned to the design-system swatches, globals.css utility layer (btn-primary, status pills, cards, toasts) with a btn-main compatibility alias until all pages migrate, dark theme enabled globally, AppKit dark theme, eslint/next config.
Icon set and social/brand logos from the POH NEW Figma file, normalized to currentColor; Gift replaced with the design-system filled variant.
Use settleChainQueries so one dead chain does not blank header/me identity, while keeping lowercased account ids for subgraph matching.
Use the social/notification badge assets in Footer and Header chrome, point Circles dark logo at gnosis-app.svg with its native 185×48 size, and make ExternalLink/CheckCircle scale via currentColor and className.
Bring header navigation, wallet section, options, notifications control, and footer social badges from the chrome layer so the foundation PR owns the full shell UI and SVG wiring.
Timeline renders every milestone's design glyph (flag-checkered, eye, challenge, vouch, check/close-circle, hourglass, transfer) tinted with the status palette. New StatusIcon implements the Figma States set and feeds the request card chip, the grid status filter and the action-bar tag (now the dark-pill Tag style). Header uses the poh lockup, footer the built-by-kleros lockup; kleros + poh-ID marks refreshed from Figma.
Render the new poh.svg lockup at its native 185×48 size and replace the legacy ethereum-era favicon with the human mark (poh-colored) via App Router icon assets.
Dark base text color instead of text-black (prevents unreadable flash), drop the duplicate .divider and redundant dark-mode button override, align explorerTxLink with explorerLink on SupportedChain, and extract isRegisterActive into utils/identifier for both navigations.
…ainer Header/footer inners and page content now share the same max-width and responsive gutters (kleros-v2 court / scout layout system), so all edges align at every viewport and content stays centered beyond 1400px. Removes the per-page viewport-width hacks in favor of .content.
Restyles the shared primitives — buttons, inputs, checkbox/radio/switch, dropdowns, generic modal, token badges, and the new CurrencyField atom — with spec hover/pressed states and icon-button treatments, and adopts them at all non-referral call sites.
Use request-grid breakpoints and wide (5:4) cards so home cells match feat/ui-revamp-referral-ui layout.
Default ExternalLink to primary/orange text colors and style the help menu so unstyled anchors no longer use the browser blue link color.
Override @tailwindcss/forms and native accent/focus so checkboxes and radios use the design palette (peach rest, orange hover/focus).
Grid cards intermittently showed the photo-unavailable fallback when the IPFS gateway rate-limited the initial request burst. Replace SWR with react-query (retry 3x, exponential backoff capped at 4s plus jitter) so cards recover instead of sticking until a refresh. Suspense callers use the new useSuspenseIPFS since react-query v5 splits suspense into its own hook.
Production builds failed on Social* SVG imports with "no loaders configured" because Next's default asset rule still claimed .svg. Follow the SVGR Next recipe: exclude SVG from the asset pipeline and route component imports through @svgr/webpack (keep *.svg?url on the asset path).
…styles - Introduced MediaFallback component for consistent loading and error states across images and videos. - Removed unused success and warning styles from globals.css. - Simplified ProfileTimelineHeader and Request/Card components by utilizing getDisplayName utility for name display logic. - Cleaned up integration components by removing dark mode logic and optimizing logo source selection. - Updated various components to use MediaFallback for better error handling and loading states.
…logic - Updated alt text for social media icons in the Footer component for better accessibility. - Refactored Header component to utilize isRegisterActive function for conditional styling based on registration status. - Simplified toggle logic in Review component for the "Submit for free" feature. - Improved aria-labels in SearchBar and Switch components for enhanced screen reader support. - Adjusted error handling in SignInButton and Previewed components for better user experience. - Optimized key assignment in RequestsGrid to ensure unique keys for mapped requests.
…rove component structure - Removed unnecessary props from StatusBadge component, consolidating status color and label logic within the component itself. - Updated Card component to utilize the simplified StatusBadge, enhancing readability and maintainability. - Cleaned up imports in both components for better organization.
Revamp/2 atom molecules
✅ Deploy Preview for proof-of-humanity-v2 ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
📝 WalkthroughWalkthroughThis PR updates configuration, theming, shared UI components, wallet and transaction workflows, IPFS fetching, media handling, cross-chain views, navigation, and profile/request layouts. It also adds reusable controls and standardizes disabled-state and tooltip handling. ChangesApplication foundation
Application interface
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. 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: 8
🧹 Nitpick comments (9)
src/components/Dropdown/Menu.tsx (1)
13-30: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winTrigger button missing
aria-expanded/aria-haspopup.
openis tracked locally but never exposed to assistive tech via ARIA attributes on the trigger button.Proposed fix
- <button className="bg-whiteBackground border-stroke text-primaryText flex min-h-12 w-full items-center justify-between rounded-btn border px-4 transition duration-200 ease-premium hover:bg-grey sm:w-64"> + <button + aria-haspopup="listbox" + aria-expanded={open} + className="bg-whiteBackground border-stroke text-primaryText flex min-h-12 w-full items-center justify-between rounded-btn border px-4 transition duration-200 ease-premium hover:bg-grey sm:w-64" + >🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/Dropdown/Menu.tsx` around lines 13 - 30, Update the trigger button in the Dropdown component to expose its menu state and relationship to assistive technology by adding aria-expanded based on open and aria-haspopup="menu".src/components/Request/Card.tsx (1)
207-217: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winInfo tooltip is hover-only, unreachable by keyboard.
The tooltip trigger
divhas notabIndex/focus state, so keyboard users can't revealtooltipcontent that mouse users see on hover.Proposed fix
- <div className="group/info relative flex h-6 w-6 items-center justify-center rounded-full border border-white/[0.08] bg-[`#2F333D`]/85 shadow-[inset_0_1px_0_rgba(255,255,255,0.04)] backdrop-blur-sm"> + <div + tabIndex={tooltip ? 0 : undefined} + className="group/info relative flex h-6 w-6 items-center justify-center rounded-full border border-white/[0.08] bg-[`#2F333D`]/85 shadow-[inset_0_1px_0_rgba(255,255,255,0.04)] backdrop-blur-sm focus-visible:outline-none" + > <InfoIcon className="h-4 w-4 stroke-current stroke-2 text-white drop-shadow-md" /> {tooltip && ( - <span className="tooltip-surface pointer-events-none absolute bottom-full left-1/2 z-50 mb-2 w-max max-w-[200px] -translate-x-1/2 whitespace-normal text-center text-sm opacity-0 transition-opacity group-hover/info:opacity-100"> + <span className="tooltip-surface pointer-events-none absolute bottom-full left-1/2 z-50 mb-2 w-max max-w-[200px] -translate-x-1/2 whitespace-normal text-center text-sm opacity-0 transition-opacity group-hover/info:opacity-100 group-focus-within/info:opacity-100"> {tooltip} </span> )}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/Request/Card.tsx` around lines 207 - 217, Update the tooltip trigger in the request card header, identified by the `group/info` wrapper around `InfoIcon`, to be keyboard focusable and reveal the tooltip on focus as well as hover. Add the appropriate focus-visible styling/state while preserving the existing hover behavior and tooltip content.src/app/Header/Options.tsx (1)
31-50: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueExtract the static array outside the component.
Defining the
HELP_LINKSarray inline within the component means it is recreated on every render. Extracting it as a static constant outside the component avoids unnecessary memory allocations and keeps the render function clean.♻️ Proposed refactor
+const HELP_LINKS = [ + ["https://t.me/proofhumanity", "Get Help (English)"], + ["https://t.me/proofofhumanityenespanol", "Get Help (Spanish)"], + ["https://gov.proofofhumanity.id/", "Forums"], + ["https://t.me/pohDebug", "Report Bugs (Telegram)"], + [ + "https://github.com/Proof-Of-Humanity/proof-of-humanity-web/issues", + "Report Bugs (Github)", + ], + [ + "https://kleros.gitbook.io/docs/products/proof-of-humanity/proof-of-humanity-tutorial", + "Tutorial", + ], + ["https://ethereum.org/en/wallets", "Crypto Beginner's Guide"], + [ + "https://kleros.gitbook.io/docs/products/proof-of-humanity/poh-faq", + "FAQ", + ], +] as const; + const Options: React.FC = () => ( <div className="mt-[16px] flex flex-row items-center md:mt-0"> <SettingsPopover /> <ExternalLink href="https://snapshot.org/#/poh.eth/" className="icon-btn ml-2 h-9 w-9" aria-label="Snapshot" > <SocialSnapshotIcon /> </ExternalLink> <Popover trigger={ <button type="button" className="icon-btn ml-2 h-9 w-9" aria-label="Open help links" > <SocialHelpIcon /> </button> } > <div className="grid h-fit grid-cols-1 gap-1 p-2"> - {( - [ - ["https://t.me/proofhumanity", "Get Help (English)"], - ["https://t.me/proofofhumanityenespanol", "Get Help (Spanish)"], - ["https://gov.proofofhumanity.id/", "Forums"], - ["https://t.me/pohDebug", "Report Bugs (Telegram)"], - [ - "https://github.com/Proof-Of-Humanity/proof-of-humanity-web/issues", - "Report Bugs (Github)", - ], - [ - "https://kleros.gitbook.io/docs/products/proof-of-humanity/proof-of-humanity-tutorial", - "Tutorial", - ], - ["https://ethereum.org/en/wallets", "Crypto Beginner's Guide"], - [ - "https://kleros.gitbook.io/docs/products/proof-of-humanity/poh-faq", - "FAQ", - ], - ] as const - ).map(([href, label]) => ( + {HELP_LINKS.map(([href, label]) => ( <ExternalLink key={href} href={href}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/Header/Options.tsx` around lines 31 - 50, Move the inline help-links array from the Options component into a module-level HELP_LINKS constant, preserving all existing URLs, labels, and ordering; update the component to reuse this constant during rendering.src/app/Header/index.tsx (1)
73-92: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate pill-CTA class string between Rewards and RegisterLink.
Both branches repeat the same long
hover:border-orange rounded-full border ...string, differing only in the active-state ternary. Consider extracting a small helper (similar tonavLinkinDesktopNavigation.tsx) to avoid divergence.♻️ Suggested refactor
+const mobileCtaLink = (active: boolean) => + `hover:border-orange rounded-full border border-white/[0.08] bg-[`#2F333D`] px-4 py-2 text-sm font-semibold text-white transition ${ + active ? "text-orange" : "" + }`; + ... - className={`hover:border-orange rounded-full border border-white/[0.08] bg-[`#2F333D`] px-4 py-2 text-sm font-semibold text-white transition ${ - pathname.startsWith("/app") ? "text-orange" : "" - }`} + className={mobileCtaLink(pathname.startsWith("/app"))} ... - className={`hover:border-orange rounded-full border border-white/[0.08] bg-[`#2F333D`] px-4 py-2 text-sm font-semibold text-white transition ${ - isRegisterActive(me?.pohId, pathname) ? "text-orange" : "" - }`} + className={mobileCtaLink(isRegisterActive(me?.pohId, pathname))}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/Header/index.tsx` around lines 73 - 92, Extract the shared pill CTA class string used by the Rewards Link and RegisterLink branches into a small helper, following the existing navLink pattern. Have the helper accept the active-state condition or class and preserve each branch’s current active styling while removing the duplicated base classes.src/app/Header/WalletSection.tsx (1)
21-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winHardcoded
#2F333Dduplicates the newly addedgreydesign token.tailwind.config.cjsnow definesdark.grey: "#2F333D", but both files still hardcode the raw hex instead of referencing that token, risking future drift.
src/app/Header/WalletSection.tsx#L21-L41: replacebg-[#2F333D]on both the connected pill wrapper (L23) and the disconnected "Connect" button (L41) with the theme'sgreytoken/utility.src/app/Header/index.tsx#L76-L94: replacebg-[#2F333D]on the Rewards link (L76), RegisterLink className (L88), and hamburger button (L94) with the same token.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/Header/WalletSection.tsx` around lines 21 - 41, Replace the hardcoded bg-[`#2F333D`] classes with the Tailwind dark.grey utility in src/app/Header/WalletSection.tsx lines 21-41 for both the connected pill wrapper and Connect button, and in src/app/Header/index.tsx lines 76-94 for the Rewards link, RegisterLink, and hamburger button. Use the existing theme token consistently at every affected site.src/components/Accordion.tsx (1)
26-32: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAlways fire
onToggleeven in uncontrolled mode.When the accordion is used as an uncontrolled component, consumers might still want to pass an
onTogglecallback to be notified of state changes. Currently,propsOnToggleis only invoked if the component is fully controlled (i.e.,isOpenis provided).♻️ Proposed refactor
const handleToggle = () => { - if (isControlled && propsOnToggle) { - propsOnToggle(); - } else { - setInternalOpen((o) => !o); - } + propsOnToggle?.(); + if (!isControlled) { + setInternalOpen((o) => !o); + } };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/Accordion.tsx` around lines 26 - 32, Update handleToggle so propsOnToggle is invoked whenever it is provided, regardless of isControlled; retain the internal state toggle for uncontrolled accordions and preserve controlled behavior without changing state management.src/components/Field.tsx (1)
45-63: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider adding
aria-invalidfor better accessibility.When the field is in an error state, adding
aria-invalid="true"helps screen readers announce the input as invalid.🎨 Proposed refactor
{textarea ? ( <textarea className={cn( "text-primaryText block w-full border-none bg-transparent px-4 py-3 font-medium transition ease-in-out", "focus:ring-0", className, )} + aria-invalid={status === "error"} {...(props as TextareaHTMLAttributes<HTMLTextAreaElement>)} /> ) : ( <input className={cn( "text-primaryText block min-h-12 w-full border-none bg-transparent px-4 py-3 font-medium", "focus:ring-0 focus-visible:outline-none", className, )} + aria-invalid={status === "error"} {...(props as InputHTMLAttributes<HTMLInputElement>)} /> )}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/Field.tsx` around lines 45 - 63, Update the textarea and input elements in the Field component to expose their error state through aria-invalid, setting it to true when the field has an error and preserving the valid-state behavior otherwise. Ensure the attribute is applied consistently in both branches.src/app/[pohid]/claim/Review.tsx (1)
334-348: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueEnhance text label accessibility.
While the
Switchcomponent itself is keyboard accessible, the adjacent text acts as a clickable label but is implemented as a genericspanwith anonClickhandler. This makes the text label inaccessible to keyboard users who might try to activate it directly.Consider updating the
Switchcomponent to accept anidand replacing thisspanwith a<label htmlFor="...">, or add a keyboard event handler androle="button"to thespan.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/`[pohid]/claim/Review.tsx around lines 334 - 348, Update the submit-for-free control in Review to use an accessible label: give the Switch a unique id and replace the clickable span with a label using matching htmlFor, preserving the existing text, styling, and toggle behavior.src/components/CurrencyField.tsx (1)
32-41: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueCouple the
Labelandinputfor better accessibility.To ensure the label is programmatically associated with the input for screen readers and allows users to click the label to focus the input, consider passing an
idprop and linking it via thehtmlForattribute on theLabel(if theLabelcomponent supports it).♻️ Proposed refactor
function CurrencyField({ label, symbol, onTokenClick, className, + id, ...props }: CurrencyFieldProps) { return ( <div className="flex w-full flex-col"> - {label && <Label>{label}</Label>} + {label && <Label htmlFor={id}>{label}</Label>} <div className="flat-control flex min-h-12 w-full items-center gap-2 rounded-input px-4 transition duration-200 ease-premium"> <input + id={id} type="number" className={cn(🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/CurrencyField.tsx` around lines 32 - 41, Update the CurrencyField label/input markup so Label receives an htmlFor value matching the input id, using the existing or generated field identifier while preserving any caller-provided input props. Ensure the association remains valid when a label is rendered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@next.config.js`:
- Line 102: Normalize the value used by the `remotePatterns.hostname`
configuration in `next.config.js`: derive a hostname from the documented
full-URL `REACT_APP_IPFS_GATEWAY` value by removing its scheme before assigning
it to `ipfsGatewayHostname`. Preserve support for the existing configuration
flow while ensuring the remote pattern receives only the host, not `https://` or
another protocol.
In `@package.json`:
- Line 57: Update tsconfig.json’s moduleResolution to an ESM-compatible resolver
such as bundler, node16, or nodenext so the GraphQLClient and RequestOptions
imports in generated/graphql.ts and config/subgraph.ts resolve with
graphql-request v7; alternatively, downgrade graphql-request to v6 while
preserving the existing resolver.
In `@src/app/globals.css`:
- Around line 348-354: Fix the declaration-empty-line-before violations in the
CSS rules .status-pill, .paper, .reward-surface, .pill-tag,
.poh-toast-container, and .poh-toast by inserting the required blank lines
before the flagged declarations, including after custom properties where
specified. Preserve all existing styles and values.
- Around line 41-199: Resolve the stylelint declaration-empty-line-before
violations by inserting a blank line between each `@apply` or custom-property
declaration and the first following plain declaration in .checkbox, .radio,
.btn-secondary, .btn-tertiary, .tooltip-surface, and .icon-btn within
src/app/globals.css lines 41-199; apply the same spacing before background in
.modal-surface at lines 211-216 and before the first plain declarations in
.status-pill, .paper, .reward-surface, .pill-tag, .poh-toast-container, and
.poh-toast at lines 348-457. No declaration values or rule behavior should
change.
- Around line 356-363: Update the CSS color keyword casing from currentColor to
currentcolor in .status-pill::before and the .poh-toast .Toastify__toast-icon
svg rule, including both background and box-shadow declarations, to satisfy
Stylelint value-keyword-case.
In `@src/components/Request/Card.tsx`:
- Around line 58-93: The ErrorFallback component is no longer used for IPFS
media-fetch errors because Content handles the useIPFS error state inline.
Remove the obsolete ErrorFallback UI, unless the intended behavior is to restore
hook-thrown errors; keep the existing inline Content error handling unchanged.
In `@src/components/SubgraphsStatus.tsx`:
- Line 148: Update the visibleStatuses computation in SubgraphsStatus to filter
statuses to only entries where status.ok is false. Preserve the empty-array
result when all chains are healthy, ensuring the banner never includes healthy
chains when any unhealthy chain exists.
- Around line 26-31: Update the subgraph fetch flow in SubgraphsStatus.tsx to
enforce an explicit timeout on each request, using AbortController or the
project’s existing timeout mechanism. Ensure a timed-out fetch rejects and is
handled by the existing polling/error path so probes cannot remain in flight or
overlap across the 60-second polling cycle.
---
Nitpick comments:
In `@src/app/`[pohid]/claim/Review.tsx:
- Around line 334-348: Update the submit-for-free control in Review to use an
accessible label: give the Switch a unique id and replace the clickable span
with a label using matching htmlFor, preserving the existing text, styling, and
toggle behavior.
In `@src/app/Header/index.tsx`:
- Around line 73-92: Extract the shared pill CTA class string used by the
Rewards Link and RegisterLink branches into a small helper, following the
existing navLink pattern. Have the helper accept the active-state condition or
class and preserve each branch’s current active styling while removing the
duplicated base classes.
In `@src/app/Header/Options.tsx`:
- Around line 31-50: Move the inline help-links array from the Options component
into a module-level HELP_LINKS constant, preserving all existing URLs, labels,
and ordering; update the component to reuse this constant during rendering.
In `@src/app/Header/WalletSection.tsx`:
- Around line 21-41: Replace the hardcoded bg-[`#2F333D`] classes with the
Tailwind dark.grey utility in src/app/Header/WalletSection.tsx lines 21-41 for
both the connected pill wrapper and Connect button, and in
src/app/Header/index.tsx lines 76-94 for the Rewards link, RegisterLink, and
hamburger button. Use the existing theme token consistently at every affected
site.
In `@src/components/Accordion.tsx`:
- Around line 26-32: Update handleToggle so propsOnToggle is invoked whenever it
is provided, regardless of isControlled; retain the internal state toggle for
uncontrolled accordions and preserve controlled behavior without changing state
management.
In `@src/components/CurrencyField.tsx`:
- Around line 32-41: Update the CurrencyField label/input markup so Label
receives an htmlFor value matching the input id, using the existing or generated
field identifier while preserving any caller-provided input props. Ensure the
association remains valid when a label is rendered.
In `@src/components/Dropdown/Menu.tsx`:
- Around line 13-30: Update the trigger button in the Dropdown component to
expose its menu state and relationship to assistive technology by adding
aria-expanded based on open and aria-haspopup="menu".
In `@src/components/Field.tsx`:
- Around line 45-63: Update the textarea and input elements in the Field
component to expose their error state through aria-invalid, setting it to true
when the field has an error and preserving the valid-state behavior otherwise.
Ensure the attribute is applied consistently in both branches.
In `@src/components/Request/Card.tsx`:
- Around line 207-217: Update the tooltip trigger in the request card header,
identified by the `group/info` wrapper around `InfoIcon`, to be keyboard
focusable and reveal the tooltip on focus as well as hover. Add the appropriate
focus-visible styling/state while preserving the existing hover behavior and
tooltip content.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7517fb96-2fcd-46be-abb2-af2e92cf2edd
⛔ Files ignored due to path filters (49)
public/logo/built-by-kleros.svgis excluded by!**/*.svgpublic/logo/gnosis-app.svgis excluded by!**/*.svgpublic/logo/human-connector.svgis excluded by!**/*.svgpublic/logo/kleros.svgis excluded by!**/*.svgpublic/logo/notifications.svgis excluded by!**/*.svgpublic/logo/poh.svgis excluded by!**/*.svgpublic/logo/pohid.svgis excluded by!**/*.svgpublic/logo/social-github.svgis excluded by!**/*.svgpublic/logo/social-help.svgis excluded by!**/*.svgpublic/logo/social-snapshot.svgis excluded by!**/*.svgpublic/logo/social-telegram.svgis excluded by!**/*.svgpublic/logo/social-x.svgis excluded by!**/*.svgsrc/app/apple-icon.pngis excluded by!**/*.pngsrc/app/favicon.icois excluded by!**/*.icosrc/app/icon.pngis excluded by!**/*.pngsrc/app/icon.svgis excluded by!**/*.svgsrc/generated/graphql.tsis excluded by!**/generated/**src/icons/CaretDown.svgis excluded by!**/*.svgsrc/icons/Challenge.svgis excluded by!**/*.svgsrc/icons/CheckCircle.svgis excluded by!**/*.svgsrc/icons/CheckCircleOutline.svgis excluded by!**/*.svgsrc/icons/ChevronDown.svgis excluded by!**/*.svgsrc/icons/CloseCircleOutline.svgis excluded by!**/*.svgsrc/icons/Copy.svgis excluded by!**/*.svgsrc/icons/Download.svgis excluded by!**/*.svgsrc/icons/EthToken.svgis excluded by!**/*.svgsrc/icons/ExternalLink.svgis excluded by!**/*.svgsrc/icons/Eye.svgis excluded by!**/*.svgsrc/icons/FlagCheckered.svgis excluded by!**/*.svgsrc/icons/Gift.svgis excluded by!**/*.svgsrc/icons/GnosisToken.svgis excluded by!**/*.svgsrc/icons/Hourglass.svgis excluded by!**/*.svgsrc/icons/Loading.svgis excluded by!**/*.svgsrc/icons/NeedsVouch.svgis excluded by!**/*.svgsrc/icons/Notifications.svgis excluded by!**/*.svgsrc/icons/Referral.svgis excluded by!**/*.svgsrc/icons/SocialGitHub.svgis excluded by!**/*.svgsrc/icons/SocialHelp.svgis excluded by!**/*.svgsrc/icons/SocialSnapshot.svgis excluded by!**/*.svgsrc/icons/SocialTelegram.svgis excluded by!**/*.svgsrc/icons/SocialX.svgis excluded by!**/*.svgsrc/icons/Stamp.svgis excluded by!**/*.svgsrc/icons/TelegramLogo.svgis excluded by!**/*.svgsrc/icons/Transfer.svgis excluded by!**/*.svgsrc/icons/Vouch.svgis excluded by!**/*.svgsrc/icons/WhatsAppLogo.svgis excluded by!**/*.svgsrc/icons/XLogo.svgis excluded by!**/*.svgsrc/icons/XdaiToken.svgis excluded by!**/*.svgyarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (74)
.eslintrc.jsnext.config.jspackage.jsonpublic/manifest.jsonsrc/app/Footer.tsxsrc/app/Header/DesktopNavigation.tsxsrc/app/Header/EmailField.tsxsrc/app/Header/MobileMenu.tsxsrc/app/Header/Options.tsxsrc/app/Header/SettingsPopover.tsxsrc/app/Header/WalletSection.tsxsrc/app/Header/index.tsxsrc/app/Toastify.tsxsrc/app/[pohid]/ProfileSummarySection.tsxsrc/app/[pohid]/ProfileTimelineHeader.tsxsrc/app/[pohid]/[chain]/[request]/ActionBar.tsxsrc/app/[pohid]/[chain]/[request]/Appeal.tsxsrc/app/[pohid]/[chain]/[request]/DegradedRequestPage.tsxsrc/app/[pohid]/[chain]/[request]/Evidence.tsxsrc/app/[pohid]/[chain]/[request]/Funding.tsxsrc/app/[pohid]/[chain]/[request]/RequestErrorState.tsxsrc/app/[pohid]/[chain]/[request]/RequestLoadingSkeleton.tsxsrc/app/[pohid]/[chain]/[request]/Timeline.tsxsrc/app/[pohid]/[chain]/[request]/error.tsxsrc/app/[pohid]/[chain]/[request]/page.tsxsrc/app/[pohid]/claim/Info.tsxsrc/app/[pohid]/claim/Review.tsxsrc/app/[pohid]/cross-chain/TransferSection.tsxsrc/app/[pohid]/cross-chain/UpdateStateSection.tsxsrc/app/attachment/page.tsxsrc/app/error.tsxsrc/app/globals.csssrc/app/layout.tsxsrc/components/Accordion.tsxsrc/components/ActionButton.tsxsrc/components/Attachment.tsxsrc/components/BulletedNumber.tsxsrc/components/Checklist.tsxsrc/components/CurrencyField.tsxsrc/components/Dropdown/Item.tsxsrc/components/Dropdown/Menu.tsxsrc/components/ExternalLink.tsxsrc/components/Field.tsxsrc/components/FileUploadZone.tsxsrc/components/Integrations/IntegrationCard.tsxsrc/components/Integrations/IntegrationHeader.tsxsrc/components/LoadableImage.tsxsrc/components/MediaFallback.tsxsrc/components/Modal.tsxsrc/components/Popover.tsxsrc/components/Previewed.tsxsrc/components/Request/Card.tsxsrc/components/Request/Grid.tsxsrc/components/Request/StatusBadge.tsxsrc/components/SearchBar.tsxsrc/components/SignInButton.tsxsrc/components/StatusIcon.tsxsrc/components/SubgraphsStatus.tsxsrc/components/Switch.tsxsrc/components/VideoThumbnail.tsxsrc/components/Webcam.tsxsrc/components/addLinkToText.tsxsrc/config/chains.tssrc/context/AppKitProvider.tsxsrc/contracts/apis/APIArbitrator.tssrc/contracts/apis/APIPoH.tssrc/contracts/config.tssrc/data/integrations.tssrc/data/user.tssrc/hooks/useDarkMode.tssrc/hooks/useIPFS.tssrc/utils/identifier.tssrc/utils/name.tstailwind.config.cjs
💤 Files with no reviewable changes (1)
- src/hooks/useDarkMode.ts
| { | ||
| protocol: "https", | ||
| hostname: process.env.REACT_APP_IPFS_GATEWAY, | ||
| hostname: ipfsGatewayHostname, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for environment variable examples or usages
rg "REACT_APP_IPFS_GATEWAY" --glob "*.env*" --glob "*.md"Repository: Proof-Of-Humanity/proof-of-humanity-v2-web
Length of output: 180
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## matches for REACT_APP_IPFS_GATEWAY\n'
rg -n "REACT_APP_IPFS_GATEWAY" . || true
printf '\n## next.config.js context\n'
nl -ba next.config.js | sed -n '85,115p'
printf '\n## remotePatterns usage\n'
rg -n "remotePatterns|ipfsGatewayHostname|new URL\\(" next.config.js . || true
printf '\n## env/docs references around IPFS gateway\n'
rg -n "ipfs gateway|IPFS_GATEWAY|ipfsGateway|gateway" . --glob "*.md" --glob "*.env*" --glob "*.js" --glob "*.ts" --glob "*.tsx" || trueRepository: Proof-Of-Humanity/proof-of-humanity-v2-web
Length of output: 782
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## next.config.js\n'
sed -n '1,130p' next.config.js
printf '\n## src/utils/ipfs.ts\n'
sed -n '1,80p' src/utils/ipfs.ts
printf '\n## CONTRIBUTING.md env example\n'
sed -n '30,45p' CONTRIBUTING.mdRepository: Proof-Of-Humanity/proof-of-humanity-v2-web
Length of output: 5005
Normalize REACT_APP_IPFS_GATEWAY before using it as remotePatterns.hostname
CONTRIBUTING.md documents this env var as a full URL (https://cdn.kleros.link), but next.config.js passes it straight into hostname. Strip the scheme first (or derive the hostname from new URL(...)) so image matching works with the documented format.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@next.config.js` at line 102, Normalize the value used by the
`remotePatterns.hostname` configuration in `next.config.js`: derive a hostname
from the documented full-URL `REACT_APP_IPFS_GATEWAY` value by removing its
scheme before assigning it to `ipfsGatewayHostname`. Preserve support for the
existing configuration flow while ensuring the remote pattern receives only the
host, not `https://` or another protocol.
| "dotenv": "^16.0.3", | ||
| "graphql": "^16.4.0", | ||
| "graphql-request": "^4.2.0", | ||
| "graphql-request": "^7.1.2", |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n "moduleResolution|\"type\"" tsconfig.json package.json
rg -n "graphql-request" -A2 -B2 src/generated/graphql.ts src/config/subgraph.tsRepository: Proof-Of-Humanity/proof-of-humanity-v2-web
Length of output: 705
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== package.json ==\n'
cat package.json | python3 - <<'PY'
import json,sys
data=json.load(sys.stdin)
print("scripts:")
for k,v in data.get("scripts",{}).items():
print(f" {k}: {v}")
print("dependencies:")
for k in ("graphql-request","next","typescript"):
if k in data.get("dependencies",{}):
print(f" {k}: {data['dependencies'][k]}")
if k in data.get("devDependencies",{}):
print(f" {k}: {data['devDependencies'][k]}")
PY
printf '\n== tsconfig.json ==\n'
cat -n tsconfig.json | sed -n '1,120p'
printf '\n== src/config/subgraph.ts ==\n'
cat -n src/config/subgraph.ts | sed -n '1,220p'
printf '\n== src/generated/graphql.ts (top) ==\n'
cat -n src/generated/graphql.ts | sed -n '1,220p'Repository: Proof-Of-Humanity/proof-of-humanity-v2-web
Length of output: 921
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== package.json =="
python3 - <<'PY'
import json
from pathlib import Path
data = json.loads(Path("package.json").read_text())
print("name:", data.get("name"))
print("type:", data.get("type"))
print("scripts:")
for k, v in data.get("scripts", {}).items():
print(f" {k}: {v}")
for section in ("dependencies", "devDependencies"):
deps = data.get(section, {})
if "graphql-request" in deps or "typescript" in deps or "next" in deps:
print(section + ":")
for k in ("graphql-request", "next", "typescript"):
if k in deps:
print(f" {k}: {deps[k]}")
PY
echo
echo "== tsconfig.json =="
cat -n tsconfig.json | sed -n '1,200p'
echo
echo "== src/config/subgraph.ts =="
cat -n src/config/subgraph.ts | sed -n '1,220p'
echo
echo "== src/generated/graphql.ts (top) =="
cat -n src/generated/graphql.ts | sed -n '1,220p'
echo
echo "== all graphql-request references =="
rg -n "graphql-request|generated/graphql|getSdk" src netlify . -g '!node_modules'Repository: Proof-Of-Humanity/proof-of-humanity-v2-web
Length of output: 20904
🌐 Web query:
graphql-request v7 package exports ESM-only RequestOptions GraphQLClient TypeScript moduleResolution node bundler node16 nodenext
💡 Result:
Starting with version 7.x, graphql-request is distributed as a pure ESM (ECMAScript Module) package [1][2]. This means it does not support CommonJS (require) imports [1]. Because the package utilizes modern package.exports, TypeScript users must configure their projects to correctly resolve these exports [3][2]. To successfully use graphql-request v7 in a TypeScript project, you must ensure the following configurations are met: 1. Package Configuration: Your project's package.json must include "type": "module" [4][1][2]. 2. TypeScript Configuration: In your tsconfig.json, the moduleResolution option must be set to "node16", "nodenext", or "bundler" [4][1][2][5]. - "node16" or "nodenext" are recommended for Node.js projects, as they align with Node's native ESM resolution algorithm [5][6]. - "bundler" is suitable if you are using a frontend bundler (e.g., Vite, Webpack), as it supports package.exports without strictly requiring file extensions on relative imports [5]. Regarding the types and exports, graphql-request v7 maintains the GraphQLClient class and various request options (such as RequestOptions and RawRequestOptions) [7][8][1]. Because the package is ESM-only, ensure you are using ES module import syntax (e.g., import { GraphQLClient } from 'graphql-request') [9][1]. If you encounter "Cannot find module" errors, it is almost certainly due to an incompatible moduleResolution setting in your tsconfig.json [4][10].
Citations:
- 1: https://tessl.io/registry/tessl/npm-graphql-request/7.2.0
- 2: https://npmx.dev/package/graphql-request/v/7.1.2
- 3: Package Exports graffle-js/graffle#863
- 4: Cannot find module 'graphql-request' or its corresponding type declarations. graffle-js/graffle#880
- 5: https://www.typescriptlang.org/tsconfig/moduleResolution.html
- 6: Require module/moduleResolution to match when either is node16/nodenext microsoft/TypeScript#54567
- 7: https://cdn.jsdelivr.net/npm/graphql-request@7.2.0/build/legacy/helpers/types.d.ts
- 8: https://cdn.jsdelivr.net/npm/graphql-request@7.3.1/src/legacy/classes/GraphQLClient.ts
- 9: https://www.npmjs.com/package/graphql-request
- 10: fix(typescript-graphql-request): Fix typescript error dotansimha/graphql-code-generator-community#549
graphql-request v7 needs a compatible TypeScript resolver here
src/generated/graphql.ts and src/config/subgraph.ts import GraphQLClient/RequestOptions from graphql-request, but tsconfig.json still uses moduleResolution: "node". v7 is ESM-only, so this can break tsc --noEmit; switch to bundler/node16/nodenext or keep graphql-request on v6.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@package.json` at line 57, Update tsconfig.json’s moduleResolution to an
ESM-compatible resolver such as bundler, node16, or nodenext so the
GraphQLClient and RequestOptions imports in generated/graphql.ts and
config/subgraph.ts resolve with graphql-request v7; alternatively, downgrade
graphql-request to v6 while preserving the existing resolver.
| .checkbox { | ||
| @apply border-orange bg-lightOrange h-6 w-6 rounded-sm border; | ||
| @apply h-6 w-6 cursor-pointer rounded-md border; | ||
| appearance: none; | ||
| background-color: #292d35; | ||
| border-color: #ffb08a; | ||
| accent-color: #ffb08a; | ||
| color: #ffb08a; | ||
| } | ||
|
|
||
| .checkbox:hover { | ||
| border-color: #ff9966; | ||
| accent-color: #ff9966; | ||
| } | ||
|
|
||
| .checkbox:focus, | ||
| .checkbox:focus-visible { | ||
| outline: 2px solid #ff9966; | ||
| outline-offset: 2px; | ||
| border-color: #ff9966; | ||
| box-shadow: none; | ||
| --tw-ring-color: #ff9966; | ||
| --tw-ring-shadow: 0 0 #0000; | ||
| } | ||
|
|
||
| .checkbox:checked { | ||
| background-color: #ffb08a; | ||
| border-color: #ffb08a; | ||
| accent-color: #ffb08a; | ||
| background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M3.5 8.5L6.5 11.5L12.5 4.5' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); | ||
| background-size: 100% 100%; | ||
| background-position: center; | ||
| background-repeat: no-repeat; | ||
| } | ||
|
|
||
| .checkbox:checked:hover { | ||
| background-color: #ff9966; | ||
| border-color: #ff9966; | ||
| accent-color: #ff9966; | ||
| } | ||
|
|
||
| .radio { | ||
| @apply border-orange bg-lightOrange h-6 w-6 rounded-full border; | ||
| @apply h-6 w-6 cursor-pointer rounded-full border; | ||
| appearance: none; | ||
| background-color: #292d35; | ||
| border-color: #3a3e48; | ||
| accent-color: #ffb08a; | ||
| color: #ffb08a; | ||
| } | ||
|
|
||
| .radio:hover { | ||
| border-color: #ffb08a; | ||
| accent-color: #ffb08a; | ||
| } | ||
|
|
||
| .radio:focus, | ||
| .radio:focus-visible { | ||
| outline: 2px solid #ff9966; | ||
| outline-offset: 2px; | ||
| box-shadow: none; | ||
| --tw-ring-color: #ff9966; | ||
| --tw-ring-shadow: 0 0 #0000; | ||
| } | ||
|
|
||
| .radio:checked { | ||
| background-color: #292d35; | ||
| border-color: #ffb08a; | ||
| color: #ffb08a; | ||
| accent-color: #ffb08a; | ||
| background-image: radial-gradient(circle, #ffb08a 0 40%, transparent 42%); | ||
| } | ||
|
|
||
| .bordered { | ||
| @apply gradient rounded p-px; | ||
| @apply rounded bg-peach p-px; | ||
| } | ||
|
|
||
| .btn-primary, | ||
| .btn-main { | ||
| @apply centered gradient w-full rounded-sm p-2 font-medium uppercase text-white disabled:opacity-40 md:w-auto; | ||
| @apply centered min-h-[48px] w-full rounded-btn px-8 py-2.5 font-normal normal-case text-white transition duration-200 ease-premium disabled:opacity-40 md:w-auto; | ||
| } | ||
|
|
||
| .btn-secondary { | ||
| @apply centered min-h-[48px] w-full rounded-btn border px-8 py-2.5 font-normal normal-case transition duration-200 ease-premium disabled:opacity-40 md:w-auto; | ||
| background: transparent; | ||
| border-color: #ffb08a; | ||
| color: #ffb08a; | ||
| } | ||
|
|
||
| .btn-secondary:hover, | ||
| .btn-secondary:active { | ||
| background: rgba(255, 153, 102, 0.32); | ||
| } | ||
|
|
||
| .btn-tertiary { | ||
| @apply centered min-h-[48px] w-full rounded-btn border px-8 py-2.5 font-normal normal-case transition duration-200 ease-premium disabled:opacity-40 md:w-auto; | ||
| background: #292d35; | ||
| border-color: #3a3e48; | ||
| color: #f7f3ef; | ||
| } | ||
|
|
||
| .btn-tertiary:hover, | ||
| .btn-tertiary:active { | ||
| background: #3a3e48; | ||
| } | ||
|
|
||
| .tooltip-surface { | ||
| position: relative; | ||
| @apply rounded-btn border p-4; | ||
| background: #2f333d; | ||
| border-color: #3a3e48; | ||
| color: #f7f3ef; | ||
| } | ||
|
|
||
| .tooltip-surface::before { | ||
| position: absolute; | ||
| top: calc(100% - 5px); | ||
| left: 50%; | ||
| width: 10px; | ||
| height: 10px; | ||
| content: ""; | ||
| background: #2f333d; | ||
| border-bottom: 1px solid #3a3e48; | ||
| border-right: 1px solid #3a3e48; | ||
| transform: translateX(-50%) rotate(45deg); | ||
| } | ||
|
|
||
| /* Circle chrome: rest = dark surface + peach glyph; hover = orange fill + white glyph. | ||
| !important so ExternalLink's default hover:text-orange does not reintroduce link blue/orange on the glyph. */ | ||
| .icon-btn { | ||
| @apply flex items-center justify-center rounded-full border transition-colors duration-200; | ||
| background: #2f333d; | ||
| border-color: #3a3e48; | ||
| color: #ffb08a !important; | ||
| } | ||
|
|
||
| .icon-btn:hover, | ||
| .icon-btn:focus-visible { | ||
| background: #ff9966; | ||
| border-color: #ff9966; | ||
| color: #ffffff !important; | ||
| } | ||
|
|
||
| .icon-btn svg { | ||
| @apply h-4 w-4 shrink-0; | ||
| } | ||
|
|
||
| .btn-primary svg, | ||
| .btn-secondary svg, | ||
| .btn-tertiary svg { | ||
| transition: transform 200ms cubic-bezier(0.22, 0.61, 0.36, 1); | ||
| } | ||
|
|
||
| .btn-sec { | ||
| @apply centered border-theme text-orange w-full rounded-sm border-2 p-2 font-semibold uppercase disabled:opacity-40 md:w-auto; | ||
| .btn-primary:hover svg, | ||
| .btn-secondary:hover svg, | ||
| .btn-tertiary:hover svg { | ||
| transform: translateX(2px); | ||
| } | ||
|
|
||
| .backdrop { | ||
| @apply fixed left-0 top-0 z-10 flex h-screen w-full items-center justify-center bg-slate-800/30 backdrop-blur; | ||
| @apply fixed left-0 top-0 z-10 flex h-screen w-full items-center justify-center bg-black/50 backdrop-blur-sm; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Repeated stylelint declaration-empty-line-before violations across globals.css. Root cause: a plain CSS declaration immediately follows an @apply (or custom-property) line with no blank line in between, throughout the new rules added in this file.
src/app/globals.css#L41-L199: add a blank line before the first plain declaration in.checkbox(L43),.radio(L83),.btn-secondary(L123),.btn-tertiary(L135),.tooltip-surface(L148), and.icon-btn(L170).src/app/globals.css#L211-L216: add a blank line beforebackground:in.modal-surface(L213).src/app/globals.css#L348-L457: add a blank line before the first plain declaration in.status-pill(L350),.paper(L391),.reward-surface(L413-417),.pill-tag(L439),.poh-toast-container(L446), and.poh-toast(L452).
🧰 Tools
🪛 Stylelint (17.14.0)
[error] 43-43: Expected empty line before declaration (declaration-empty-line-before)
(declaration-empty-line-before)
[error] 83-83: Expected empty line before declaration (declaration-empty-line-before)
(declaration-empty-line-before)
[error] 123-123: Expected empty line before declaration (declaration-empty-line-before)
(declaration-empty-line-before)
[error] 135-135: Expected empty line before declaration (declaration-empty-line-before)
(declaration-empty-line-before)
[error] 148-148: Expected empty line before declaration (declaration-empty-line-before)
(declaration-empty-line-before)
[error] 170-170: Expected empty line before declaration (declaration-empty-line-before)
(declaration-empty-line-before)
📍 Affects 1 file
src/app/globals.css#L41-L199(this comment)src/app/globals.css#L211-L216src/app/globals.css#L348-L457
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/globals.css` around lines 41 - 199, Resolve the stylelint
declaration-empty-line-before violations by inserting a blank line between each
`@apply` or custom-property declaration and the first following plain declaration
in .checkbox, .radio, .btn-secondary, .btn-tertiary, .tooltip-surface, and
.icon-btn within src/app/globals.css lines 41-199; apply the same spacing before
background in .modal-surface at lines 211-216 and before the first plain
declarations in .status-pill, .paper, .reward-surface, .pill-tag,
.poh-toast-container, and .poh-toast at lines 348-457. No declaration values or
rule behavior should change.
Source: Linters/SAST tools
| .status-pill { | ||
| @apply inline-flex items-center gap-2 rounded-full border px-3 py-1.5 text-xs font-semibold; | ||
| background: rgba(47, 51, 61, 0.82); | ||
| border-color: rgba(255, 255, 255, 0.08); | ||
| color: #b8b1c2; | ||
| box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04); | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Stylelint: more missing blank lines before declarations.
Same declaration-empty-line-before violation at Line 350 (.status-pill), Line 391 (.paper), Lines 413-417 (.reward-surface background), Line 439 (.pill-tag), Line 446 (.poh-toast-container, after custom property), Line 452 (.poh-toast, after custom property).
Also applies to: 389-400, 411-421, 437-442, 444-448, 450-457
🧰 Tools
🪛 Stylelint (17.14.0)
[error] 350-350: Expected empty line before declaration (declaration-empty-line-before)
(declaration-empty-line-before)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/globals.css` around lines 348 - 354, Fix the
declaration-empty-line-before violations in the CSS rules .status-pill, .paper,
.reward-surface, .pill-tag, .poh-toast-container, and .poh-toast by inserting
the required blank lines before the flagged declarations, including after custom
properties where specified. Preserve all existing styles and values.
Source: Linters/SAST tools
| .status-pill::before { | ||
| content: ""; | ||
| width: 7px; | ||
| height: 7px; | ||
| border-radius: 999px; | ||
| background: currentColor; | ||
| box-shadow: 0 0 12px currentColor; | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Stylelint: currentColor should be currentcolor.
value-keyword-case flags Lines 361, 362 (.status-pill::before) and Line 525 (.poh-toast .Toastify__toast-icon svg).
🎨 Fix
- background: currentColor;
- box-shadow: 0 0 12px currentColor;
+ background: currentcolor;
+ box-shadow: 0 0 12px currentcolor;- fill: currentColor;
+ fill: currentcolor;Also applies to: 512-526
🧰 Tools
🪛 Stylelint (17.14.0)
[error] 361-361: Expected "currentColor" to be "currentcolor" (value-keyword-case)
(value-keyword-case)
[error] 362-362: Expected "currentColor" to be "currentcolor" (value-keyword-case)
(value-keyword-case)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/globals.css` around lines 356 - 363, Update the CSS color keyword
casing from currentColor to currentcolor in .status-pill::before and the
.poh-toast .Toastify__toast-icon svg rule, including both background and
box-shadow declarations, to satisfy Stylelint value-keyword-case.
Source: Linters/SAST tools
| const updateCardHoverParallax = (event: PointerEvent<HTMLAnchorElement>) => { | ||
| const rect = event.currentTarget.getBoundingClientRect(); | ||
| const x = ((event.clientX - rect.left) / rect.width - 0.5) * -18; | ||
| const y = ((event.clientY - rect.top) / rect.height - 0.5) * -8; | ||
|
|
||
| event.currentTarget.style.setProperty( | ||
| "--request-card-hover-x", | ||
| `${x.toFixed(2)}px`, | ||
| ); | ||
| event.currentTarget.style.setProperty( | ||
| "--request-card-hover-y", | ||
| `${y.toFixed(2)}px`, | ||
| ); | ||
| }; | ||
|
|
||
| const resetCardHoverParallax = (event: PointerEvent<HTMLAnchorElement>) => { | ||
| event.currentTarget.style.removeProperty("--request-card-hover-x"); | ||
| event.currentTarget.style.removeProperty("--request-card-hover-y"); | ||
| }; | ||
|
|
||
| const ErrorFallback: React.FC<{ claimer?: { name?: string | null } }> = ({ | ||
| claimer, | ||
| }) => ( | ||
| <div className="h-84 flex animate-pulse flex-col items-center bg-white p-2"> | ||
| <div className="mx-auto mb-2 h-32 w-32 rounded-full bg-slate-200" /> | ||
| <span className="font-semibold">{claimer?.name}</span> | ||
| <span>Some error occurred...</span> | ||
| </div> | ||
| <> | ||
| <div className="bg-grey absolute inset-0" /> | ||
| <div className="request-card-overlay absolute inset-0" /> | ||
| <div className="absolute inset-x-0 bottom-0 z-10 px-4 pb-[21px]"> | ||
| <div className="truncate text-2xl font-semibold text-white"> | ||
| {claimer?.name || "Unavailable"} | ||
| </div> | ||
| <div className="mt-1 text-sm text-dark-secondaryText"> | ||
| Profile media could not be loaded | ||
| </div> | ||
| </div> | ||
| </> | ||
| ); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
ast-grep outline src/hooks/useIPFS.ts
rg -n "throw" src/hooks/useIPFS.ts -A3 -B3Repository: Proof-Of-Humanity/proof-of-humanity-v2-web
Length of output: 318
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== useIPFS.ts outline =="
ast-grep outline src/hooks/useIPFS.ts
echo
echo "== useIPFS.ts (numbered) =="
cat -n src/hooks/useIPFS.ts | sed -n '1,220p'
echo
echo "== Card.tsx outline =="
ast-grep outline src/components/Request/Card.tsx
echo
echo "== Card.tsx relevant lines =="
cat -n src/components/Request/Card.tsx | sed -n '1,220p'Repository: Proof-Of-Humanity/proof-of-humanity-v2-web
Length of output: 11364
ErrorFallback no longer covers IPFS load failures
useIPFS returns [data, error] and Content handles those states inline, so this boundary now only catches unexpected render crashes. If the intent is to show this UI when media fetches fail, the hook needs to throw again or the fallback should be removed.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/Request/Card.tsx` around lines 58 - 93, The ErrorFallback
component is no longer used for IPFS media-fetch errors because Content handles
the useIPFS error state inline. Remove the obsolete ErrorFallback UI, unless the
intended behavior is to restore hook-thrown errors; keep the existing inline
Content error handling unchanged.
| async function getLatestBlock(chainId: SupportedChainId) { | ||
| const client = createPublicClient({ | ||
| transport: http(getChainRpc(chainId)), | ||
| }); | ||
| return Number(await client.getBlockNumber()); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
set -euo pipefail
printf '\n## Files\n'
git ls-files | rg '^(src/components/SubgraphsStatus\.tsx|src/app/layout\.tsx|package.json|pnpm-lock\.yaml|yarn.lock|package-lock\.json)$'
printf '\n## Outline: SubgraphsStatus\n'
ast-grep outline src/components/SubgraphsStatus.tsx --view expanded || true
printf '\n## Read relevant slices\n'
sed -n '1,220p' src/components/SubgraphsStatus.tsx
printf '\n## Layout usage\n'
sed -n '1,220p' src/app/layout.tsx
printf '\n## Package versions mentioning viem / fetch wrappers\n'
rg -n '"viem"|getBlockNumber|createPublicClient|http\(' package.json pnpm-lock.yaml yarn.lock package-lock.json src -g '!**/node_modules/**' || trueRepository: Proof-Of-Humanity/proof-of-humanity-v2-web
Length of output: 15990
🏁 Script executed:
set -euo pipefail
printf '\n## File sizes\n'
wc -l src/components/SubgraphsStatus.tsx src/app/layout.tsx || true
printf '\n## Exact lines around probe implementation\n'
nl -ba src/components/SubgraphsStatus.tsx | sed -n '1,220p'
printf '\n## Exact lines around layout mount\n'
nl -ba src/app/layout.tsx | sed -n '1,220p'
printf '\n## Search for timeout / abort usage in subgraph status path\n'
rg -n 'AbortController|timeout|setInterval|REFRESH_INTERVAL_MS|probeSubgraph|getLatestBlock|fetch\(' src/components/SubgraphsStatus.tsx src/app/layout.tsx src -g '!**/node_modules/**' || true
printf '\n## Search viem http transport docs-like usage in repo\n'
rg -n 'createPublicClient\(|http\(' src -g '!**/node_modules/**' || trueRepository: Proof-Of-Humanity/proof-of-humanity-v2-web
Length of output: 354
🏁 Script executed:
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path('src/components/SubgraphsStatus.tsx')
print(p.read_text())
PYRepository: Proof-Of-Humanity/proof-of-humanity-v2-web
Length of output: 6252
🏁 Script executed:
set -euo pipefail
rg -n 'probeSubgraph|getLatestBlock|AbortController|signal:|timeout' src/components/SubgraphsStatus.tsx src -g '!**/node_modules/**'Repository: Proof-Of-Humanity/proof-of-humanity-v2-web
Length of output: 2169
🌐 Web query:
viem http transport default timeout getBlockNumber timeout fetchOptions
💡 Result:
In Viem, the http transport has a default timeout of 10,000 milliseconds (10 seconds) [1][2]. You can configure this timeout when initializing the http transport by providing a timeout option in the transport configuration object [1]: import { http } from 'viem' const transport = http('https://rpc.example.com', { timeout: 60_000, // Timeout in ms }) Regarding fetchOptions, these are separate from the transport's internal timeout mechanism. You can pass fetchOptions to the http transport to customize the underlying Fetch API request (e.g., headers, mode, credentials), but these do not override or directly control the Viem-level timeout [3][4][5]: const transport = http('https://rpc.example.com', { fetchOptions: { headers: { 'x-custom-header': 'value' }, }, }) For actions like getBlockNumber, the request is executed using the transport defined on your client [6]. There is no specific timeout parameter in getBlockNumber itself [7][6]. Instead, the request inherits the timeout configured on the transport [1]. If the request exceeds the transport's timeout, Viem will throw an error [2]. Summary of Key Points: - Default Timeout: 10,000 ms [1][2]. - Configuration: Set via the timeout property in the http transport options [1]. - FetchOptions: Used for customizing the fetch request, not for setting timeouts [3][4][5]. - Action Behavior: Actions like getBlockNumber use the client's transport configuration [6].
Citations:
- 1: https://viem.sh/docs/clients/transports/http
- 2: wevm/viem@db9caa9
- 3: https://cdn.jsdelivr.net/npm/viem@2.53.1/_types/clients/transports/http.d.ts
- 4: https://github.com/wevm/viem/blob/a59b5630/src/clients/transports/http.ts
- 5: https://github.com/wevm/viem/blob/a59b5630/src/utils/rpc/http.ts
- 6: https://github.com/wevm/viem/blob/a59b5630/src/actions/public/getBlockNumber.ts
- 7: https://viem.sh/docs/actions/public/getBlockNumber
🏁 Script executed:
set -euo pipefail
sed -n '1,220p' src/components/SubgraphsStatus.tsxRepository: Proof-Of-Humanity/proof-of-humanity-v2-web
Length of output: 6251
🌐 Web query:
viem http transport timeout default getBlockNumber docs
💡 Result:
In Viem, the default timeout for the HTTP transport is 10,000 milliseconds (10 seconds) [1][2][3]. When you use getBlockNumber, it utilizes the transport configured for your client to perform the underlying eth_blockNumber JSON-RPC request [4]. Consequently, any requests made via getBlockNumber are subject to this 10-second timeout by default [1][3]. You can override this timeout when configuring the HTTP transport by providing the timeout option in milliseconds [1]: import { http } from 'viem' const transport = http('https://your-rpc-url.com', { timeout: 60_000, // Sets timeout to 60 seconds }) This default timeout behavior is consistent across other transports in Viem, such as webSocket and ipc, which also default to 10,000 milliseconds [5][6].
Citations:
- 1: https://viem.sh/docs/clients/transports/http
- 2: https://github.com/wevm/viem/blob/a59b5630/src/clients/transports/http.ts
- 3: wevm/viem@db9caa9
- 4: https://github.com/wevm/viem/blob/a59b5630/src/actions/public/getBlockNumber.ts
- 5: https://viem.sh/docs/clients/transports/websocket
- 6: https://viem.sh/docs/clients/transports/ipc
Add a timeout around the subgraph fetch getBlockNumber() already inherits viem’s HTTP transport timeout, but the subgraph request itself can still hang forever. With the 60s polling loop, a stuck fetch can leave overlapping in-flight probes.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/SubgraphsStatus.tsx` around lines 26 - 31, Update the subgraph
fetch flow in SubgraphsStatus.tsx to enforce an explicit timeout on each
request, using AbortController or the project’s existing timeout mechanism.
Ensure a timed-out fetch rejects and is handled by the existing polling/error
path so probes cannot remain in flight or overlap across the 60-second polling
cycle.
| }, []); | ||
|
|
||
| if (unhealthy.length === 0) return null; | ||
| const visibleStatuses = statuses.some((status) => !status.ok) ? statuses : []; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
visibleStatuses shows all chains, not just unhealthy ones.
statuses.some(...) ? statuses : [] only checks whether any chain is unhealthy — it then returns the full statuses array (including healthy chains) instead of filtering to the unhealthy subset. The banner will rotate through healthy chains too whenever one chain is degraded.
Proposed fix
- const visibleStatuses = statuses.some((status) => !status.ok) ? statuses : [];
+ const visibleStatuses = statuses.filter((status) => !status.ok);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const visibleStatuses = statuses.some((status) => !status.ok) ? statuses : []; | |
| const visibleStatuses = statuses.filter((status) => !status.ok); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/SubgraphsStatus.tsx` at line 148, Update the visibleStatuses
computation in SubgraphsStatus to filter statuses to only entries where
status.ok is false. Preserve the empty-array result when all chains are healthy,
ensuring the banner never includes healthy chains when any unhealthy chain
exists.
- Introduced a new SVG logo for the POH ID badge, replacing the previous logo in various components. - Created the ActionWalletGate component to manage wallet connection and chain switching for profile action buttons. - Updated ProfileActionsSection to utilize ActionWalletGate for improved user experience. - Refactored error handling and loading states in multiple components for consistency and better UX. - Enhanced styling and accessibility across components, including updates to button classes and layout adjustments.
…ansaction handling - Introduced useEnoughFunds hook to check wallet balance against transaction amounts, enhancing user feedback on insufficient funds. - Implemented resolveTxState utility to streamline button state management across various components, providing consistent tooltip messages and disabled states. - Updated multiple components (RevokeModal, ActionBar, Appeal, Challenge, Funding, etc.) to utilize the new hooks for better user experience and error handling. - Refactored tooltip and disabled logic in buttons to improve clarity and responsiveness during transactions.
- Updated ActionWalletGate to improve wallet connection logic and chain switching. - Refactored RevokeModal to ensure loading states are properly managed during revoke actions. - Enhanced ActionBar, Appeal, and Challenge components to provide clearer feedback on connection status and balance checks. - Improved Funding component to validate input amounts and provide appropriate error messages. - Streamlined Timeline and Review components for better accessibility and user experience. - Added tooltip support for better guidance during cross-chain actions.
Add POH ID badge SVG and implement ActionWalletGate component
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (5)
src/utils/txState.ts (1)
1-18: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd unit tests for
resolveTxState.This pure function now centralizes disable/tooltip logic for transaction gating across
RemoveVouch,Challenge,ActionBar,Funding, andAppeal. It's trivial to unit test (priority ordering, message-less checks, empty array) and a regression here would silently break disabled/tooltip behavior everywhere it's used.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/utils/txState.ts` around lines 1 - 18, Add focused unit tests for resolveTxState covering an empty checks array, active and inactive checks, priority ordering among multiple active checks, and active message-less checks that disable without producing a tooltip. Verify the returned disabled and tooltip values for each case without changing the implementation.src/hooks/useEnoughFunds.ts (1)
19-57: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winConsider reserving a small buffer for gas.
The hook only checks whether
balance >= amount, explicitly excluding gas as documented. Since this gates payable actions inChallenge.tsx,Funding.tsx, andAppeal.tsx, a wallet with exactly the required balance will be reported as "sufficient," then fail on-chain (wasting gas) because nothing is left to pay the transaction fee.Consider adding an optional gas-reserve margin (even a rough static buffer per chain) so
insufficientreflects the realistic minimum needed to complete the transaction.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hooks/useEnoughFunds.ts` around lines 19 - 57, Update useEnoughFunds to include a small gas-reserve margin when determining insufficient, rather than comparing balanceValue only with amount. Add an optional or chain-aware reserve using the existing chainId context, and ensure both the insufficient result and displayed balance message reflect the total required amount while preserving current behavior when balance data or amount is unavailable.src/app/[pohid]/[chain]/[request]/ActionBar.tsx (1)
240-272: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract a shared "wrong chain" check helper.
Five files independently rebuild the identical chain-mismatch check object (
{ active: userChainId !== chainId, message:Switch your chain above to ${idToChain(chainId)?.name || "the correct chain"}}).ActionBar.tsxalready dedups this locally into a singlewrongChainCheckreused across its three triggers, showing the pattern is easy to share — it just isn't shared across files.
src/app/[pohid]/[chain]/[request]/ActionBar.tsx#L240-L272: extract the localwrongChainCheckobject into a shared helper (e.g.wrongChainCheck(userChainId, chain.id)) exported fromutils/txState.ts, and use it here.src/app/[pohid]/[chain]/[request]/RemoveVouch.tsx#L99-L108: replace the inline chain-mismatch check in thetriggerarray with the shared helper.src/app/[pohid]/[chain]/[request]/Challenge.tsx#L249-L273: replace both inline chain-mismatch checks (insubmitDisabledandtrigger) with the shared helper.src/app/[pohid]/[chain]/[request]/Funding.tsx#L122-L157: replace both inline chain-mismatch checks (inisDisabledandtrigger) with the shared helper.src/app/[pohid]/[chain]/[request]/Appeal.tsx#L89-L115: replace the inline chain-mismatch check inSideFunding'sresolveTxStatecall with the shared helper.♻️ Proposed shared helper
export type TxCheck = { active: boolean; message?: string }; +export function wrongChainCheck(userChainId: number, targetChainId: number): TxCheck { + return { + active: userChainId !== targetChainId, + message: `Switch your chain above to ${idToChain(targetChainId)?.name || "the correct chain"}`, + }; +} + export function resolveTxState(checks: TxCheck[]): {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/`[pohid]/[chain]/[request]/ActionBar.tsx around lines 240 - 272, Extract the duplicated chain-mismatch object into an exported helper in utils/txState.ts, such as wrongChainCheck(userChainId, chainId), preserving the existing active comparison and switch-chain message. Update src/app/[pohid]/[chain]/[request]/ActionBar.tsx lines 240-272 to use the helper for all three triggers; update RemoveVouch.tsx lines 99-108, both checks in Challenge.tsx lines 249-273, both checks in Funding.tsx lines 122-157, and the SideFunding resolveTxState check in Appeal.tsx lines 89-115 to use it instead of inline objects.src/app/[pohid]/ProfileSummarySection.tsx (1)
226-241: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueExternal-link icon on an internal navigation link without
target="_blank".
View source requestusesExternalLinkIconandgroup/external-linkstyling (mirroring the truly-external "Claimed by" link), but thisLinknavigates to another internal profile route in the same tab, with notarget="_blank". Consider either dropping the external-link affordance or opening it in a new tab to match the icon's implied behavior.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/`[pohid]/ProfileSummarySection.tsx around lines 226 - 241, The “View source request” link in ProfileSummarySection uses external-link affordance while navigating internally in the same tab. Update the Link for punishedVouchSourceHref to open the destination in a new tab, or remove the ExternalLinkIcon and external-link styling so its behavior matches the indicated navigation.src/app/[pohid]/ActionWalletGate.tsx (1)
40-48: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winConsider surfacing switch-chain pending/error feedback.
switchChain?.({ chainId: homeChain.id })is fire-and-forget: if the user rejects the request or the connector can't switch programmatically, the button just sits there with no feedback. Since this PR already introduces action-feedback patterns elsewhere, consider surfacingisPending/errorfromuseSwitchChain()here too.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/`[pohid]/ActionWalletGate.tsx around lines 40 - 48, Update the chain-switching flow in ActionWalletGate to consume isPending and error from useSwitchChain, and surface the pending or failure state through the existing action-feedback UI patterns. Keep the current home-chain mismatch condition and switch request behavior, while making rejection or connector failures visible to the user.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/`[pohid]/claim/Review.tsx:
- Around line 73-81: The self-funded amount parsing in Review should not convert
parse failures into the empty-input state. Track the parse error separately from
selfFundedWei, pass undefined only when no amount was entered, and update the
submit validation/UI near the existing submission logic to block submission and
show an error when parsing fails.
In `@src/app/`[pohid]/ProfileSummarySection.tsx:
- Line 232: Update the profile removal message in the punishedVouchReason
rendering to include the article “a” before “Sybil Attack,” while leaving the
“Identity Theft” wording unchanged.
In `@src/app/`[pohid]/RevokeConsequences.tsx:
- Around line 18-42: Update the STEPS-driven rendering in RevokeConsequences so
“If unchallenged” and “If challenged” are displayed as sibling alternative
branches after the challenge window, rather than consecutive items in one
connected <ol>. Preserve their existing content, icons, and styling while
adjusting the relevant rendering around the outcome cards.
---
Nitpick comments:
In `@src/app/`[pohid]/[chain]/[request]/ActionBar.tsx:
- Around line 240-272: Extract the duplicated chain-mismatch object into an
exported helper in utils/txState.ts, such as wrongChainCheck(userChainId,
chainId), preserving the existing active comparison and switch-chain message.
Update src/app/[pohid]/[chain]/[request]/ActionBar.tsx lines 240-272 to use the
helper for all three triggers; update RemoveVouch.tsx lines 99-108, both checks
in Challenge.tsx lines 249-273, both checks in Funding.tsx lines 122-157, and
the SideFunding resolveTxState check in Appeal.tsx lines 89-115 to use it
instead of inline objects.
In `@src/app/`[pohid]/ActionWalletGate.tsx:
- Around line 40-48: Update the chain-switching flow in ActionWalletGate to
consume isPending and error from useSwitchChain, and surface the pending or
failure state through the existing action-feedback UI patterns. Keep the current
home-chain mismatch condition and switch request behavior, while making
rejection or connector failures visible to the user.
In `@src/app/`[pohid]/ProfileSummarySection.tsx:
- Around line 226-241: The “View source request” link in ProfileSummarySection
uses external-link affordance while navigating internally in the same tab.
Update the Link for punishedVouchSourceHref to open the destination in a new
tab, or remove the ExternalLinkIcon and external-link styling so its behavior
matches the indicated navigation.
In `@src/hooks/useEnoughFunds.ts`:
- Around line 19-57: Update useEnoughFunds to include a small gas-reserve margin
when determining insufficient, rather than comparing balanceValue only with
amount. Add an optional or chain-aware reserve using the existing chainId
context, and ensure both the insufficient result and displayed balance message
reflect the total required amount while preserving current behavior when balance
data or amount is unavailable.
In `@src/utils/txState.ts`:
- Around line 1-18: Add focused unit tests for resolveTxState covering an empty
checks array, active and inactive checks, priority ordering among multiple
active checks, and active message-less checks that disable without producing a
tooltip. Verify the returned disabled and tooltip values for each case without
changing the implementation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 680f0ff6-df30-4a49-893e-f03063193e75
⛔ Files ignored due to path filters (1)
public/logo/pohid-badge.svgis excluded by!**/*.svg
📒 Files selected for processing (41)
src/app/[pohid]/(profile)/loading.tsxsrc/app/[pohid]/(profile)/page.tsxsrc/app/[pohid]/ActionWalletGate.tsxsrc/app/[pohid]/ProfileActionsSection.tsxsrc/app/[pohid]/ProfileErrorCard.tsxsrc/app/[pohid]/ProfileSectionErrorCard.tsxsrc/app/[pohid]/ProfileSummarySection.tsxsrc/app/[pohid]/ProfileTimelineError.tsxsrc/app/[pohid]/ProfileTimelineSection.tsxsrc/app/[pohid]/Renew.tsxsrc/app/[pohid]/RevokeClient.tsxsrc/app/[pohid]/RevokeConsequences.tsxsrc/app/[pohid]/RevokeModal.tsxsrc/app/[pohid]/[chain]/[request]/ActionBar.tsxsrc/app/[pohid]/[chain]/[request]/Appeal.tsxsrc/app/[pohid]/[chain]/[request]/Challenge.tsxsrc/app/[pohid]/[chain]/[request]/Evidence.tsxsrc/app/[pohid]/[chain]/[request]/Funding.tsxsrc/app/[pohid]/[chain]/[request]/RemoveVouch.tsxsrc/app/[pohid]/[chain]/[request]/Timeline.tsxsrc/app/[pohid]/[chain]/[request]/Vouch.tsxsrc/app/[pohid]/claim/Review.tsxsrc/app/[pohid]/cross-chain/CrossChain.tsxsrc/app/[pohid]/cross-chain/CrossChainError.tsxsrc/app/[pohid]/cross-chain/CrossChainLoading.tsxsrc/app/[pohid]/cross-chain/PendingRelaySection.tsxsrc/app/[pohid]/cross-chain/TransferSection.tsxsrc/app/[pohid]/cross-chain/UpdateStateSection.tsxsrc/app/[pohid]/cross-chain/crossChainUi.tsxsrc/app/[pohid]/error.tsxsrc/components/ChainLogo.tsxsrc/components/CopyButton.tsxsrc/components/ExternalLinkIcon.tsxsrc/components/Field.tsxsrc/components/Modal.tsxsrc/components/NewTabIcon.tsxsrc/components/RequestPunishedVouchNotice.tsxsrc/data/uploadEvidence.tssrc/hooks/useActionFeedback.tssrc/hooks/useEnoughFunds.tssrc/utils/txState.ts
💤 Files with no reviewable changes (1)
- src/components/NewTabIcon.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
- src/components/Modal.tsx
- src/app/[pohid]/[chain]/[request]/Evidence.tsx
- src/components/Field.tsx
| let selfFundedWei: bigint | undefined; | ||
| if (!submitForFree && selfFunded) { | ||
| try { | ||
| selfFundedWei = parseEther(selfFunded.toString()); | ||
| } catch { | ||
| selfFundedWei = undefined; | ||
| } | ||
| } | ||
| const funds = useEnoughFunds({ chainId, amount: selfFundedWei }); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== Review.tsx context ==\n'
sed -n '1,220p' src/app/'[pohid]'/claim/Review.tsx
printf '\n== Search for useEnoughFunds ==\n'
rg -n "function useEnoughFunds|const useEnoughFunds|useEnoughFunds\\(" src -g '!**/node_modules/**'
printf '\n== Candidate hook/file outline ==\n'
fd -a "useEnoughFunds.*|EnoughFunds.*" src || trueRepository: Proof-Of-Humanity/proof-of-humanity-v2-web
Length of output: 9490
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map likely files containing the hook implementation, then read the relevant slice.
files=$(rg -l "useEnoughFunds" src -g '!**/node_modules/**' || true)
printf '%s\n' "$files"
for f in $files; do
printf '\n== %s ==\n' "$f"
wc -l "$f"
sed -n '1,240p' "$f"
doneRepository: Proof-Of-Humanity/proof-of-humanity-v2-web
Length of output: 39141
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Read only the relevant parts of Review.tsx with line numbers.
nl -ba src/app/'[pohid]'/claim/Review.tsx | sed -n '1,180p'
printf '\n== useEnoughFunds references ==\n'
rg -n "useEnoughFunds" src -g '!**/node_modules/**' -A 12 -B 12Repository: Proof-Of-Humanity/proof-of-humanity-v2-web
Length of output: 220
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== Review.tsx around funding + submit ==\n'
python3 - <<'PY'
from pathlib import Path
p = Path("src/app/[pohid]/claim/Review.tsx")
for i, line in enumerate(p.read_text().splitlines(), 1):
if 70 <= i <= 220 or 220 <= i <= 420:
# print all if within ranges, but avoid too much? We'll filter below.
pass
PYRepository: Proof-Of-Humanity/proof-of-humanity-v2-web
Length of output: 222
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path("src/app/[pohid]/claim/Review.tsx")
lines = p.read_text().splitlines()
for start, end, title in [(70, 120, "funding"), (300, 430, "submit area")]:
print(f"\n== {title} ({start}-{end}) ==")
for i in range(start, min(end, len(lines)) + 1):
print(f"{i:4d}: {lines[i-1]}")
PY
printf '\n== Disabled/submit occurrences ==\n'
rg -n "disabled=|onClick=|funds\\.|selfFunded|submitForFree" src/app/'[pohid]'/claim/Review.tsxRepository: Proof-Of-Humanity/proof-of-humanity-v2-web
Length of output: 10571
Do not treat a parse failure as empty input. parseEther(selfFunded.toString()) falls back to undefined, and useEnoughFunds skips the balance check for undefined, so the Submit button can stay enabled on invalid deposit values. Keep parse errors separate and block submission/show an error. src/app/[pohid]/claim/Review.tsx:73-81, 424-435
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/`[pohid]/claim/Review.tsx around lines 73 - 81, The self-funded
amount parsing in Review should not convert parse failures into the empty-input
state. Track the parse error separately from selfFundedWei, pass undefined only
when no amount was entered, and update the submit validation/UI near the
existing submission logic to block submission and show an error when parsing
fails.
| Punished vouch | ||
| </span> | ||
| <p className="text-secondaryText text-sm font-normal leading-6"> | ||
| This profile was removed for aiding {punishedVouchReason}.{" "} |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Grammar: missing article before "Sybil Attack".
"aiding Sybil Attack" should read "aiding a Sybil Attack" for grammatical correctness (the "Identity Theft" branch reads fine without an article).
✏️ Proposed fix
- This profile was removed for aiding {punishedVouchReason}.{" "}
+ This profile was removed for aiding{" "}
+ {punishedVouchReason === "Sybil Attack" ? "a " : ""}
+ {punishedVouchReason}.{" "}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| This profile was removed for aiding {punishedVouchReason}.{" "} | |
| This profile was removed for aiding{" "} | |
| {punishedVouchReason === "Sybil Attack" ? "a " : ""} | |
| {punishedVouchReason}.{" "} |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/`[pohid]/ProfileSummarySection.tsx at line 232, Update the profile
removal message in the punishedVouchReason rendering to include the article “a”
before “Sybil Attack,” while leaving the “Identity Theft” wording unchanged.
| const STEPS: OutcomeStep[] = [ | ||
| { | ||
| icon: FlagCheckeredIcon, | ||
| title: "Submit request", | ||
| description: "Your deposit is locked while the request is open.", | ||
| accent: true, | ||
| }, | ||
| { | ||
| icon: HourglassIcon, | ||
| title: "Challenge window", | ||
| description: "Anyone can contest the removal during this period.", | ||
| }, | ||
| { | ||
| icon: CheckCircleOutlineIcon, | ||
| title: "If unchallenged", | ||
| description: "The profile is removed and your deposit is refunded.", | ||
| iconClassName: "text-status-registered", | ||
| }, | ||
| { | ||
| icon: ChallengeIcon, | ||
| title: "If challenged", | ||
| description: "The dispute is resolved in Kleros Court.", | ||
| iconClassName: "text-peach", | ||
| }, | ||
| ]; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Render the two possible outcomes as branches, not sequential steps.
“If unchallenged” and “If challenged” are mutually exclusive, but the connected <ol> visually presents both as consecutive events. Split them into sibling outcome cards or branches after the challenge window.
Also applies to: 61-89
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/`[pohid]/RevokeConsequences.tsx around lines 18 - 42, Update the
STEPS-driven rendering in RevokeConsequences so “If unchallenged” and “If
challenged” are displayed as sibling alternative branches after the challenge
window, rather than consecutive items in one connected <ol>. Preserve their
existing content, icons, and styling while adjusting the relevant rendering
around the outcome cards.
Summary by CodeRabbit