Conversation
iframe no longer unmounts on close, eliminating 4-5s reload delay. added touch drag-to-dismiss on handle bar.
dual-layer clippath for black-to-white text transition on hold. reduced icon size to max 15% container width for narrow devices.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
WalkthroughRemoved unused device hook; updated UI copy for withdrawal/send flows; adjusted refunded status styling and amount presentation; refined QR Pay visuals and hold-to-claim clipping; added swap state/button and URL-sync guards in ExchangeRateWidget; replaced SupportDrawer with a custom bottom sheet and drag/escape handling; expanded popular currencies; added invite flow wiring for payment CTAs. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 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 docstrings
🧪 Generate unit tests (beta)
📝 Coding Plan
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
src/components/Global/SupportDrawer/index.tsx (3)
84-90: Redundant translate classes - inline style overrides them.The
translate-y-0andtranslate-y-fullclasses in the className (line 85) are overridden by the inlinetransformstyle (line 88). Consider removing the redundant Tailwind classes to avoid confusion.♻️ Suggested cleanup
className={`fixed inset-x-0 bottom-0 z-[999999] flex max-h-[85vh] flex-col rounded-t-[10px] border bg-background pt-4 ${ - isSupportModalOpen ? 'translate-y-0' : 'pointer-events-none translate-y-full' + isSupportModalOpen ? '' : 'pointer-events-none' }`}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/Global/SupportDrawer/index.tsx` around lines 84 - 90, Remove the redundant Tailwind translate classes from the SupportDrawer container: the className currently toggles 'translate-y-0' and 'translate-y-full' but those are always overridden by the inline style using transform (which reads isSupportModalOpen and dragOffset) and transition (isDragging). Update the JSX for the element that builds className (the container using isSupportModalOpen, isDragging, and dragOffset) to drop 'translate-y-0' and 'translate-y-full' so the visual behavior is controlled solely by the inline transform/transition logic.
18-22: Consider using state instead of ref for drag tracking.Reading
dragStartY.currentduring render (line 22) to computeisDraggingis fragile because ref mutations don't trigger re-renders. While this mostly works due tosetDragOffsetcausing re-renders during drag, there's a subtle issue: whenhandleTouchEndsetsdragOffsetto0but it was already0(e.g., user tapped without dragging), no re-render occurs, leavingisDraggingpotentially stale for the next interaction.♻️ Suggested refactor using state
- const dragStartY = useRef<number | null>(null) - const [dragOffset, setDragOffset] = useState(0) - const isDragging = dragStartY.current !== null + const dragStartY = useRef<number | null>(null) + const [dragOffset, setDragOffset] = useState(0) + const [isDragging, setIsDragging] = useState(false) const handleTouchStart = useCallback((e: React.TouchEvent) => { dragStartY.current = e.touches[0].clientY + setIsDragging(true) }, []) // ... handleTouchMove unchanged ... const handleTouchEnd = useCallback(() => { if (dragOffset > DISMISS_THRESHOLD) { setIsSupportModalOpen(false) } dragStartY.current = null setDragOffset(0) + setIsDragging(false) - }, [dragOffset, setIsSupportModalOpen]) + }, [dragOffset, setIsSupportModalOpen])🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/Global/SupportDrawer/index.tsx` around lines 18 - 22, The current useRef dragStartY and derived isDragging (dragStartY.current !== null) can become stale because ref changes don't trigger renders; replace dragStartY ref with a stateful value (e.g., const [dragStartY, setDragStartY] = useState<number | null>(null) or add an explicit [isDragging, setIsDragging]) and update the touch handlers (handleTouchStart, handleTouchMove, handleTouchEnd) to call setDragStartY / setIsDragging and setDragOffset via setDragOffset so component re-renders reliably; update any references to panelRef, dragStartY, isDragging, setDragOffset, and handleTouchEnd to use the new state and ensure you clear the state on touch end so isDragging accurately reflects the UI.
79-91: Add focus management to the dialog for improved accessibility.The component has proper ARIA attributes (
role="dialog",aria-modal) and Escape key handling, but lacks focus management. For better accessibility, implement:
- Move focus into the dialog (or to the iframe) when it opens
- Trap focus within the dialog while open
- Restore focus to the trigger element when closed
This can be done with a library like
focus-trap-reactorreact-focus-lock, or by implementing focus management directly in auseEffecthook.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/Global/SupportDrawer/index.tsx` around lines 79 - 91, Add focus management around the Support drawer: when isSupportModalOpen becomes true, save document.activeElement, programmatically focus the panel (panelRef) or its iframe (iframeRef) and enable a focus trap while open (use focus-trap-react or implement Tab/Shift+Tab key handling on panelRef). When closing, remove the trap and restore focus to the previously focused trigger element. Implement this in a useEffect that watches isSupportModalOpen and cleans up on unmount; reference panelRef, iframeRef (or create one), and isSupportModalOpen to locate where to wire the logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/Global/SupportDrawer/index.tsx`:
- Around line 43-55: The CRISP_READY listener (inside the useEffect with
handleMessage) can leave the loader showing forever if the message never
arrives; add a timeout fallback: create a timeout (e.g., 8–15s) inside the same
useEffect that sets a new state (e.g., crispReadyTimedOut or sets isCrispReady
to false/error) to dismiss the loader or show an error, clear that timeout when
handleMessage receives 'CRISP_READY' (and setIsCrispReady(true)), and also clear
the timeout in the effect cleanup; update the UI to check crispReadyTimedOut (or
the error state) so the loading overlay doesn’t remain indefinitely.
In `@src/components/TransactionDetails/TransactionCard.tsx`:
- Around line 209-216: The component uses inconsistent color token names: inside
the span that renders displayAmount (the className on the span that combines
'font-semibold' and a refunded condition using status === 'refunded'), replace
the token 'text-grey-1' with 'text-gray-1' so it matches the other usages (e.g.,
other lines using 'text-gray-1') and keeps naming consistent across
TransactionCard.
---
Nitpick comments:
In `@src/components/Global/SupportDrawer/index.tsx`:
- Around line 84-90: Remove the redundant Tailwind translate classes from the
SupportDrawer container: the className currently toggles 'translate-y-0' and
'translate-y-full' but those are always overridden by the inline style using
transform (which reads isSupportModalOpen and dragOffset) and transition
(isDragging). Update the JSX for the element that builds className (the
container using isSupportModalOpen, isDragging, and dragOffset) to drop
'translate-y-0' and 'translate-y-full' so the visual behavior is controlled
solely by the inline transform/transition logic.
- Around line 18-22: The current useRef dragStartY and derived isDragging
(dragStartY.current !== null) can become stale because ref changes don't trigger
renders; replace dragStartY ref with a stateful value (e.g., const [dragStartY,
setDragStartY] = useState<number | null>(null) or add an explicit [isDragging,
setIsDragging]) and update the touch handlers (handleTouchStart,
handleTouchMove, handleTouchEnd) to call setDragStartY / setIsDragging and
setDragOffset via setDragOffset so component re-renders reliably; update any
references to panelRef, dragStartY, isDragging, setDragOffset, and
handleTouchEnd to use the new state and ensure you clear the state on touch end
so isDragging accurately reflects the UI.
- Around line 79-91: Add focus management around the Support drawer: when
isSupportModalOpen becomes true, save document.activeElement, programmatically
focus the panel (panelRef) or its iframe (iframeRef) and enable a focus trap
while open (use focus-trap-react or implement Tab/Shift+Tab key handling on
panelRef). When closing, remove the trap and restore focus to the previously
focused trigger element. Implement this in a useEffect that watches
isSupportModalOpen and cleans up on unmount; reference panelRef, iframeRef (or
create one), and isSupportModalOpen to locate where to wire the logic.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ab00c3c0-fe86-405c-967c-0b890040860a
📒 Files selected for processing (13)
src/app/(mobile-ui)/home/page.tsxsrc/app/(mobile-ui)/points/page.tsxsrc/app/(mobile-ui)/qr-pay/page.tsxsrc/app/(mobile-ui)/withdraw/manteca/page.tsxsrc/components/Global/Badges/StatusBadge.tsxsrc/components/Global/ExchangeRateWidget/index.tsxsrc/components/Global/Icons/Icon.tsxsrc/components/Global/PeanutActionDetailsCard/index.tsxsrc/components/Global/StatusPill/index.tsxsrc/components/Global/SupportDrawer/index.tsxsrc/components/LandingPage/CurrencySelect.tsxsrc/components/Slider/index.tsxsrc/components/TransactionDetails/TransactionCard.tsx
💤 Files with no reviewable changes (2)
- src/app/(mobile-ui)/home/page.tsx
- src/components/Global/Icons/Icon.tsx
|
@coderabbitai review in depth. Evaluate:
Also, write a very concise testing guide for QA |
🧩 Analysis chainLet me gather the key file contents to perform a thorough review. 🏁 Script executed: Length of output: 116084
🔴 Critical Bug
|
| Change | Assessment |
|---|---|
Slider overlay fix (inset-0 z-10 pointer-events-none) |
Correct — label was being covered by the range fill; z-10 and inset-0 fix both stacking and sizing |
TransactionCard refunded strikethrough (twMerge + line-through) |
Clean, correct, no regressions |
StatusBadge + StatusPill refunded → success colors |
Consistent; refunded is semantically positive (money returned) |
SupportDrawer rewrite (drag-to-dismiss, Escape, backdrop) |
Well-structured; Escape listener is correctly scoped to isSupportModalOpen; same-origin check on message events is correct |
Hold-to-claim clipPath overlay |
clipPath: inset(0 ${100 - holdProgress}% 0 0) is the correct approach for a reveal effect; z-20 stacking over the black fill is correct |
PeanutActionDetailsCard + manteca/page.tsx copy change |
Consistent "You're sending" across both |
LandingPage/CurrencySelect — ARS + BRL added to popular |
Low risk, purely UI ordering |
home/page.tsx — useDeviceType removal |
Safe if the deviceType variable had no downstream consumers |
🧪 QA Testing Guide
1. Exchange Rate Widget — Currency Swap
- Enter
100 USD → EUR, confirm rate shows. - Click the swap arrow (↔) — verify shimmer shows during recalculation, then new rate appears (
100 EUR → USD). - Verify the amount field now shows the former destination amount (e.g., ~92).
- Swap with
destinationAmount = 0or empty — should not crash.
2. QR Pay — Hold-to-Claim Perk
- Trigger a Manteca QR payment that returns a perk.
- On success screen, hold the "Claim Peanut Perk Now!" button — confirm black fill animates left-to-right and white clipped text tracks the fill boundary.
- Release early — confirm state resets cleanly.
- Hold to 100% — confirm confetti fires, success banner appears, and perk card switches to "Peanut got you!".
3. QR Pay — Provider Under Maintenance
- Set
MANTECAindisabledPaymentProvidersconfig and scan a Mercado Pago QR — verify maintenance card shows without a React hook crash.
4. Support Drawer
- Open → drag down past ~100px → confirm drawer dismisses.
- Open → press Escape → confirm closes.
- Open → click backdrop → confirm closes.
- Open for the first time → confirm loading spinner shows until
CRISP_READYfires → confirm spinner hides. - Close and reopen — confirm chat is still visible (no reload/flash).
5. Refunded Transaction Styling
- Find a transaction with
status = 'refunded'. - Confirm USD amount has
line-through+ grey text. - Confirm the secondary currency amount (non-USD) also has
line-through. - Confirm
StatusBadgeandStatusPillboth show green (success) styling for refunded.
🧠 Learnings used
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 852
File: src/hooks/useTransactionHistory.ts:177-239
Timestamp: 2025-05-14T21:16:06.369Z
Learning: When reviewing code, pay attention to and resolve CodeRabbit comments as they can contain useful refactoring suggestions and insights.
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 594
File: src/components/Request/Pay/Views/Initial.view.tsx:188-213
Timestamp: 2024-12-31T15:08:15.640Z
Learning: Always resolve coderabbit comments when reviewing code, since they can contain useful suggestions.
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1094
File: src/components/AddWithdraw/DynamicBankAccountForm.tsx:279-279
Timestamp: 2025-08-14T08:02:26.705Z
Learning: For hotfixes in the peanut-ui codebase, prefer generic error messages over specific validation error details until the copy can be reviewed with the team, even when the validation functions return detailed error messages.
Learnt from: kushagrasarathe
Repo: peanutprotocol/peanut-ui PR: 0
File: :0-0
Timestamp: 2026-03-04T18:32:51.654Z
Learning: In peanutprotocol/peanut-ui, Bridge-specific add-money and withdraw flows (`src/app/(mobile-ui)/add-money/[country]/bank/page.tsx`, `src/components/AddWithdraw/AddWithdrawCountriesList.tsx`, `src/components/Claim/Link/views/BankFlowManager.view.tsx`) have been migrated (PR `#1713`) to use the unified `isUserKycApproved` from `useKycStatus()` hook instead of per-component `liveKycStatus` + WebSocket state. The migration is complete; no more direct `bridgeKycStatus === 'approved'` checks in those files.
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 1014
File: src/components/Claim/Link/Initial.view.tsx:413-413
Timestamp: 2025-07-24T13:26:10.290Z
Learning: In the peanut-ui repository, the change from `${SQUID_API_URL}/route` to `${SQUID_API_URL}/v2/route` in src/components/Claim/Link/Initial.view.tsx was a typo fix, not an API migration, as the codebase was already using Squid API v2.
✅ Actions performed
Full review triggered.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/features/payments/shared/components/SendWithPeanutCta.tsx (1)
63-74: Consider extracting invite code generation to a shared utility.The invite code generation logic (
${inviterUsername.toUpperCase()}INVITESYOU) is duplicated inRequestPotActionList.tsx(line 134). Consider extracting this to a shared utility function to ensure consistency and simplify maintenance.♻️ Suggested refactor
Create a utility function:
// In a shared utils file export const generateInviteCode = (username: string) => `${username.toUpperCase()}INVITESYOU`Then use it in both components:
-const inviteCode = `${inviterUsername.toUpperCase()}INVITESYOU` +const inviteCode = generateInviteCode(inviterUsername)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/payments/shared/components/SendWithPeanutCta.tsx` around lines 63 - 74, Extract the duplicated invite-code logic into a shared utility (e.g., export const generateInviteCode(username: string) => `${username.toUpperCase()}INVITESYOU`) and replace the inline construction in SendWithPeanutCta (inside the block that sets inviteCode and dispatches setupActions.setInviteType) and the duplicate in RequestPotActionList (around line 134) to call generateInviteCode(inviterUsername) so both components use the same helper and stay consistent.src/features/payments/flows/contribute-pot/components/RequestPotActionList.tsx (1)
133-140: Duplicate invite code generation logic.This invite code generation (
${recipientUsername.toUpperCase()}INVITESYOU) duplicates the logic inSendWithPeanutCta.tsx(lines 67-70). As noted in the other file, consider extracting to a shared utility.The different
redirectUrivalues are appropriate: bank methods redirect to/add-moneywhile the peanut CTA preserves the current location.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/payments/flows/contribute-pot/components/RequestPotActionList.tsx` around lines 133 - 140, The invite code string ("${recipientUsername.toUpperCase()}INVITESYOU") is duplicated between RequestPotActionList.tsx and SendWithPeanutCta.tsx; extract this logic into a single utility (e.g., makeInviteCode(recipientUsername) or formatInviteCode) and replace the inline template in both components to call that exported function, then import the util into RequestPotActionList and SendWithPeanutCta and ensure dispatch(setupActions.setInviteCode(...)) uses the new helper; keep existing redirectUri behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In
`@src/features/payments/flows/contribute-pot/components/RequestPotActionList.tsx`:
- Around line 133-140: The invite code string
("${recipientUsername.toUpperCase()}INVITESYOU") is duplicated between
RequestPotActionList.tsx and SendWithPeanutCta.tsx; extract this logic into a
single utility (e.g., makeInviteCode(recipientUsername) or formatInviteCode) and
replace the inline template in both components to call that exported function,
then import the util into RequestPotActionList and SendWithPeanutCta and ensure
dispatch(setupActions.setInviteCode(...)) uses the new helper; keep existing
redirectUri behavior unchanged.
In `@src/features/payments/shared/components/SendWithPeanutCta.tsx`:
- Around line 63-74: Extract the duplicated invite-code logic into a shared
utility (e.g., export const generateInviteCode(username: string) =>
`${username.toUpperCase()}INVITESYOU`) and replace the inline construction in
SendWithPeanutCta (inside the block that sets inviteCode and dispatches
setupActions.setInviteType) and the duplicate in RequestPotActionList (around
line 134) to call generateInviteCode(inviterUsername) so both components use the
same helper and stay consistent.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 5415dad8-42cd-4d3e-83da-5a9e1e2c4bd6
📒 Files selected for processing (3)
src/features/payments/flows/contribute-pot/components/RequestPotActionList.tsxsrc/features/payments/flows/contribute-pot/views/ContributePotInputView.tsxsrc/features/payments/shared/components/SendWithPeanutCta.tsx
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/app/(mobile-ui)/qr-pay/page.tsx (1)
1061-1077:⚠️ Potential issue | 🔴 CriticalMove
useMemoout of the conditional branch—this violates React's Rules of Hooks.The
useMemoat line 1063 is called only whenisProviderDisabledis true. IfisProviderDisabledchanges between renders (it depends onpaymentProcessor), the hook call order changes, causing React's hook state to become corrupted.Additionally, the empty dependency array
[]is incorrect; the memoized value depends onpaymentProcessorandqrType.🔧 Proposed fix
+ const paymentMethodName = useMemo(() => { + if (paymentProcessor === 'MANTECA') { + switch (qrType) { + case EQrType.PIX: + return 'PIX' + case EQrType.MERCADO_PAGO: + return 'Mercado Pago' + case EQrType.ARGENTINA_QR3: + return 'QR' + default: + return 'QR' + } + } + return 'SimpleFi' + }, [paymentProcessor, qrType]) + if (isProviderDisabled) { - const paymentMethodName = useMemo(() => { - if (paymentProcessor === 'MANTECA') { - switch (qrType) { - case EQrType.PIX: - return 'PIX' - case EQrType.MERCADO_PAGO: - return 'Mercado Pago' - case EQrType.ARGENTINA_QR3: - return 'QR' - default: - return 'QR' - } - } - return 'SimpleFi' - }, [])🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`(mobile-ui)/qr-pay/page.tsx around lines 1061 - 1077, The hook call to useMemo for computing paymentMethodName is currently inside the isProviderDisabled conditional which breaks React's Rules of Hooks; move the useMemo call to the component's top-level (always-call) so it runs on every render, and update its dependency array to [paymentProcessor, qrType] so the memo recomputes when those values change; keep using the same logic inside the memo (checking paymentProcessor === 'MANTECA' and the EQrType switch) and then reference the paymentMethodName variable inside the isProviderDisabled branch as before.
♻️ Duplicate comments (1)
src/components/Global/SupportDrawer/index.tsx (1)
43-55:⚠️ Potential issue | 🟡 MinorDon't gate the chat on a single ready message.
isCrispReadyonly flips fromCRISP_READY, so any proxy failure leaves the loading overlay up forever and blocks support. Please add a timeout oriframeload/error fallback so the user can recover.Also applies to: 104-107
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/Global/SupportDrawer/index.tsx` around lines 43 - 55, The current useEffect installs handleMessage which only sets isCrispReady when receiving 'CRISP_READY', leaving the loading overlay stuck if that message never arrives; update the SupportDrawer logic (the useEffect, handleMessage, and any code that reads isCrispReady) to add a fail-safe: start a timeout (e.g. 5–10s) when the iframe is created that flips isCrispReady (or a separate isCrispFallback flag) so the UI unblocks if 'CRISP_READY' never arrives, and also attach iframe onLoad and onError handlers to call setIsCrispReady/setIsCrispFallback accordingly; ensure you still remove the message listener and clear the timeout in the cleanup to avoid leaks and keep the existing CRISP_READY handling intact.
🧹 Nitpick comments (4)
src/components/Global/StatusPill/index.tsx (1)
16-16: LGTM! Refunded status color update is consistent.The refunded status now uses the success palette (green), matching the identical change in
StatusBadge.tsxline 35. This creates a consistent visual treatment across the UI.As noted in the
StatusBadge.tsxreview, both components now duplicate therefunded → successcolor mapping. Consider extracting this to a shared constant to prevent future drift between the two components.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/Global/StatusPill/index.tsx` at line 16, Both components duplicate the refunded→success color mapping; extract the shared mapping into a single exported constant (e.g., STATUS_CLASS_MAP or REFUND_STATUS_CLASS) and import it into StatusPill (index.tsx) and StatusBadge so both use the same source of truth; update the mapping references (the refunded key currently in StatusPill and the mapping in StatusBadge) to reference the new constant and remove the duplicate literal mappings.src/components/Global/Badges/StatusBadge.tsx (1)
34-35: Refunded status color change looks good.The change from yellow/pending palette to green/success palette for refunded transactions is visually consistent and intuitive. However, note that
StatusPill/index.tsxline 16 duplicates this exactrefunded → successcolor mapping.♻️ Consider extracting shared constant to avoid future drift
To keep the refunded status styling consistent between
StatusBadgeandStatusPill, consider extracting the color mapping to a shared constant:// src/components/Global/statusColors.ts export const STATUS_COLOR_MAPS = { refunded: 'success' as const, completed: 'success' as const, closed: 'success' as const, // ... other mappings }Then both components can reference the same source of truth.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/Global/Badges/StatusBadge.tsx` around lines 34 - 35, StatusBadge's refunded→success color mapping is duplicated in StatusPill; extract a single source-of-truth constant (e.g., export const STATUS_COLOR_MAPS = { refunded: 'success', completed: 'success', closed: 'success', ... } from a new module like statusColors.ts) and update both StatusBadge and StatusPill to import STATUS_COLOR_MAPS and derive the CSS class string from the mapped token instead of hardcoding 'refunded' → 'bg-success-2 ...'; ensure the mapping contains all existing status keys and that both components use the mapping symbol when building their className logic.src/features/payments/shared/components/SendWithPeanutCta.tsx (1)
66-70: Consider extracting invite code generation to a shared utility.The invite code generation pattern
${username.toUpperCase()}INVITESYOUand the Redux dispatch sequence is duplicated inRequestPotActionList.tsx(lines 133-137). Extracting this to a shared utility would reduce duplication and prevent drift.♻️ Suggested utility extraction
Create a shared utility in
src/utils/invite.utils.ts:import { setupActions } from '@/redux/slices/setup-slice' import { EInviteType } from '@/services/services.types' import type { AppDispatch } from '@/redux/store' export const generateInviteCode = (username: string): string => { return `${username.toUpperCase()}INVITESYOU` } export const dispatchInviteCode = ( dispatch: AppDispatch, username: string, inviteType: EInviteType = EInviteType.PAYMENT_LINK ) => { const inviteCode = generateInviteCode(username) dispatch(setupActions.setInviteCode(inviteCode)) dispatch(setupActions.setInviteType(inviteType)) return inviteCode }Then use in both components:
-const inviteCode = `${inviterUsername.toUpperCase()}INVITESYOU` -dispatch(setupActions.setInviteCode(inviteCode)) -dispatch(setupActions.setInviteType(EInviteType.PAYMENT_LINK)) +const inviteCode = dispatchInviteCode(dispatch, inviterUsername)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/payments/shared/components/SendWithPeanutCta.tsx` around lines 66 - 70, Duplicate invite-code generation and Redux dispatch logic between SendWithPeanutCta and RequestPotActionList should be extracted into a shared utility; create functions generateInviteCode(username: string) and dispatchInviteCode(dispatch, username, inviteType = EInviteType.PAYMENT_LINK) (referencing setupActions, setInviteCode, setInviteType, and EInviteType) in a new utils file (e.g., invite.utils.ts), replace the inline code in SendWithPeanutCta (the block using inviterUsername, setupActions and router.push) and the corresponding block in RequestPotActionList to call dispatchInviteCode to get the inviteCode and then call router.push(`/invite?code=${inviteCode}&redirect_uri=${redirectUri}`).src/components/Global/ExchangeRateWidget/index.tsx (1)
99-103: Clarify behavior whendestinationAmountis invalid.When
destinationAmountis0orundefined,newAmountbecomesundefinedand the existing URLamountis silently preserved. After swap, the source input will show the old amount rather than a swapped value.If this is intentional, consider adding a brief comment. Otherwise, consider explicit handling (e.g., default to a fallback or clear the amount param).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/Global/ExchangeRateWidget/index.tsx` around lines 99 - 103, The swap branch currently sets newAmount to undefined when destinationAmount is 0 or missing, which silently preserves the existing URL amount; update the ExchangeRateWidget swap logic around destinationAmount/newAmount to be explicit: either add a short comment above this block stating that undefined intentionally preserves the existing amount, or change the behavior to explicitly clear or set a fallback amount when destinationAmount is invalid (e.g., call updateUrlParams({ from: destinationCurrency, to: sourceCurrency, amount: null }) or amount: '' or amount: 0 depending on updateUrlParams semantics) so the source input shows the expected value after swap; update the code at the newAmount calculation and the subsequent updateUrlParams call accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/app/`(mobile-ui)/qr-pay/page.tsx:
- Around line 1343-1353: The overlay span that duplicates the button label (the
second <span> that has className "absolute inset-0 z-20 flex items-center
justify-center text-white transition-all duration-75" and uses style.clipPath
with holdProgress) should be hidden from assistive tech so screen readers don't
announce the label twice; update that overlay element (not the visible label
span or the label variable) to be decorative by adding an appropriate
accessibility attribute such as aria-hidden="true" (or role="presentation") to
the overlay span.
In `@src/components/Global/ExchangeRateWidget/index.tsx`:
- Around line 106-111: The current useEffect that clears isSwapping when
!isLoading can race because isLoading may still be false from a prior state;
modify the logic in the ExchangeRateWidget by adding a small local flag (e.g.,
hasObservedLoading) tracked inside the component and referenced in the existing
useEffect for isSwapping: set hasObservedLoading to true when isLoading becomes
true, and only call setIsSwapping(false) when hasObservedLoading is true and
isLoading transitions to false; update the dependencies of the useEffect to
include hasObservedLoading (and keep isSwapping/isLoading) so the effect only
clears swapping after an actual loading cycle has been observed.
In `@src/components/Global/SupportDrawer/index.tsx`:
- Around line 57-65: The sheet currently only wires an Escape key listener on
window via the useEffect referenced (isSupportModalOpen, setIsSupportModalOpen)
but does not move initial focus into the sheet, trap focus there, restore focus
on close, provide a parent close control, nor handle Escape events that occur
inside the iframe (see lines ~79-116). Fix by turning the sheet into an
accessible dialog: when isSupportModalOpen goes true, save
document.activeElement, call focus() on a designated focusable container (or
close button) inside the component, implement a focus trap (or use a library
like focus-trap or ReachDialog) to intercept Tab/Shift+Tab and keep focus inside
the sheet, restore focus to the saved element on close via
setIsSupportModalOpen, and ensure a visible close control in the parent shell.
Also handle Escape events originating inside the iframe by either mounting the
iframe inside the same dialog boundary (so host dialog handles focus) or by
adding a postMessage API from the iframe to the parent to invoke
setIsSupportModalOpen(false) (and still keep the parent Escape listener) so
presses inside the framed document close the sheet.
---
Outside diff comments:
In `@src/app/`(mobile-ui)/qr-pay/page.tsx:
- Around line 1061-1077: The hook call to useMemo for computing
paymentMethodName is currently inside the isProviderDisabled conditional which
breaks React's Rules of Hooks; move the useMemo call to the component's
top-level (always-call) so it runs on every render, and update its dependency
array to [paymentProcessor, qrType] so the memo recomputes when those values
change; keep using the same logic inside the memo (checking paymentProcessor ===
'MANTECA' and the EQrType switch) and then reference the paymentMethodName
variable inside the isProviderDisabled branch as before.
---
Duplicate comments:
In `@src/components/Global/SupportDrawer/index.tsx`:
- Around line 43-55: The current useEffect installs handleMessage which only
sets isCrispReady when receiving 'CRISP_READY', leaving the loading overlay
stuck if that message never arrives; update the SupportDrawer logic (the
useEffect, handleMessage, and any code that reads isCrispReady) to add a
fail-safe: start a timeout (e.g. 5–10s) when the iframe is created that flips
isCrispReady (or a separate isCrispFallback flag) so the UI unblocks if
'CRISP_READY' never arrives, and also attach iframe onLoad and onError handlers
to call setIsCrispReady/setIsCrispFallback accordingly; ensure you still remove
the message listener and clear the timeout in the cleanup to avoid leaks and
keep the existing CRISP_READY handling intact.
---
Nitpick comments:
In `@src/components/Global/Badges/StatusBadge.tsx`:
- Around line 34-35: StatusBadge's refunded→success color mapping is duplicated
in StatusPill; extract a single source-of-truth constant (e.g., export const
STATUS_COLOR_MAPS = { refunded: 'success', completed: 'success', closed:
'success', ... } from a new module like statusColors.ts) and update both
StatusBadge and StatusPill to import STATUS_COLOR_MAPS and derive the CSS class
string from the mapped token instead of hardcoding 'refunded' → 'bg-success-2
...'; ensure the mapping contains all existing status keys and that both
components use the mapping symbol when building their className logic.
In `@src/components/Global/ExchangeRateWidget/index.tsx`:
- Around line 99-103: The swap branch currently sets newAmount to undefined when
destinationAmount is 0 or missing, which silently preserves the existing URL
amount; update the ExchangeRateWidget swap logic around
destinationAmount/newAmount to be explicit: either add a short comment above
this block stating that undefined intentionally preserves the existing amount,
or change the behavior to explicitly clear or set a fallback amount when
destinationAmount is invalid (e.g., call updateUrlParams({ from:
destinationCurrency, to: sourceCurrency, amount: null }) or amount: '' or
amount: 0 depending on updateUrlParams semantics) so the source input shows the
expected value after swap; update the code at the newAmount calculation and the
subsequent updateUrlParams call accordingly.
In `@src/components/Global/StatusPill/index.tsx`:
- Line 16: Both components duplicate the refunded→success color mapping; extract
the shared mapping into a single exported constant (e.g., STATUS_CLASS_MAP or
REFUND_STATUS_CLASS) and import it into StatusPill (index.tsx) and StatusBadge
so both use the same source of truth; update the mapping references (the
refunded key currently in StatusPill and the mapping in StatusBadge) to
reference the new constant and remove the duplicate literal mappings.
In `@src/features/payments/shared/components/SendWithPeanutCta.tsx`:
- Around line 66-70: Duplicate invite-code generation and Redux dispatch logic
between SendWithPeanutCta and RequestPotActionList should be extracted into a
shared utility; create functions generateInviteCode(username: string) and
dispatchInviteCode(dispatch, username, inviteType = EInviteType.PAYMENT_LINK)
(referencing setupActions, setInviteCode, setInviteType, and EInviteType) in a
new utils file (e.g., invite.utils.ts), replace the inline code in
SendWithPeanutCta (the block using inviterUsername, setupActions and
router.push) and the corresponding block in RequestPotActionList to call
dispatchInviteCode to get the inviteCode and then call
router.push(`/invite?code=${inviteCode}&redirect_uri=${redirectUri}`).
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b8a22bfd-6095-4543-b031-160d0a22e82e
📒 Files selected for processing (16)
src/app/(mobile-ui)/home/page.tsxsrc/app/(mobile-ui)/points/page.tsxsrc/app/(mobile-ui)/qr-pay/page.tsxsrc/app/(mobile-ui)/withdraw/manteca/page.tsxsrc/components/Global/Badges/StatusBadge.tsxsrc/components/Global/ExchangeRateWidget/index.tsxsrc/components/Global/Icons/Icon.tsxsrc/components/Global/PeanutActionDetailsCard/index.tsxsrc/components/Global/StatusPill/index.tsxsrc/components/Global/SupportDrawer/index.tsxsrc/components/LandingPage/CurrencySelect.tsxsrc/components/Slider/index.tsxsrc/components/TransactionDetails/TransactionCard.tsxsrc/features/payments/flows/contribute-pot/components/RequestPotActionList.tsxsrc/features/payments/flows/contribute-pot/views/ContributePotInputView.tsxsrc/features/payments/shared/components/SendWithPeanutCta.tsx
💤 Files with no reviewable changes (2)
- src/components/Global/Icons/Icon.tsx
- src/app/(mobile-ui)/home/page.tsx
- move paymentMethodName useMemo above the isProviderDisabled conditional to fix Rules of Hooks violation (hooks can't be called conditionally) - add proper deps (paymentProcessor, qrType) to the useMemo - fix text-grey-1 → text-gray-1 in TransactionCard for consistency
Summary
Bug squash PR — multiple independent fixes across the app.
Fixes
peanut.me/prado/5USDC) now get the sender's invite code and redirect to/inviteinstead of getting stuck on signup with no code. Same pattern as claim links.Enhancements
Chores