[TASK-15725] feat: support waiting for merchant amount#1350
[TASK-15725] feat: support waiting for merchant amount#1350jjramirezn merged 3 commits intopeanut-wallet-devfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAdds retry and merchant-amount waiting flow to MANTECA and SIMPLEFI QR payment paths, replaces the "Order Not Ready" modal with a waiting UI and support surface, removes Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
src/components/Global/PeanutLoading/index.tsx (1)
14-14: Prevent potential CLS: set intrinsic size (or use next/image).Fixed height without width can cause layout shift; also “logo” alt is generic. Prefer explicit intrinsic size or next/image for consistency.
Apply one of:
- <img src={PEANUTMAN_LOGO.src} alt="logo" className="h-10" /> + <img src={PEANUTMAN_LOGO.src} alt="Peanut logo" width={40} height={40} className="h-10 w-10" />or
+ import Image from 'next/image' ... - <img src={PEANUTMAN_LOGO.src} alt="logo" className="h-10" /> + <Image src={PEANUTMAN_LOGO} alt="Peanut logo" width={40} height={40} className="h-10 w-10" priority />Please confirm which pattern you prefer across Global components for icon/logo images.
src/components/Global/DirectSendQR/index.tsx (1)
408-413: Icon sizing: rely on standard Tailwind; avoid app‑specific class.Use w-full with h-full for predictable scaling inside the 5rem button. Also confirm custom-size exists.
- className={twMerge( - 'mx-auto h-20 w-20 cursor-pointer justify-center rounded-full p-3 hover:bg-primary-1/100', - className - )} + className={twMerge( + 'mx-auto h-20 w-20 cursor-pointer justify-center rounded-full p-3 hover:bg-primary-1/100', + className + )} ... - <Icon name={icon} className="custom-size h-full" /> + <Icon name={icon} className="h-full w-full" />If other consumers depended on the removed iconClassName prop, confirm all call sites were updated.
src/app/(mobile-ui)/qr-pay/page.tsx (2)
731-744: Auto-retry timer lacks cleanup; can update unmounted state.Store the timeout id and clear it in a cleanup to avoid leaks and stale retries.
- useEffect(() => { - if (waitingForMerchantAmount && !shouldRetry) { - if (retryCount.current < 3) { - retryCount.current++ - setTimeout(() => { - setShouldRetry(true) - }, 3000) - } else { - setWaitingForMerchantAmount(false) - setShowOrderNotReadyModal(true) - } - } - }, [waitingForMerchantAmount, shouldRetry]) + useEffect(() => { + let t: ReturnType<typeof setTimeout> | undefined + if (waitingForMerchantAmount && !shouldRetry) { + if (retryCount.current < 3) { + retryCount.current++ + t = setTimeout(() => setShouldRetry(true), 3000) + } else { + setWaitingForMerchantAmount(false) + setShowOrderNotReadyModal(true) + } + } + return () => { + if (t) clearTimeout(t) + } + }, [waitingForMerchantAmount, shouldRetry])
836-848: Next/Image intrinsic size mismatch; likely blur/CLS.width/height are 20 but class sets 128px; set intrinsic size to match rendered size (or use sizes/fill).
- <Image - src={chillPeanutAnim.src} - alt="Peanut Mascot" - width={20} - height={20} - className="absolute z-0 h-32 w-32 -translate-y-20 translate-x-26" - /> + <Image + src={chillPeanutAnim.src} + alt="Peanut Mascot" + width={128} + height={128} + className="absolute z-0 h-32 w-32 -translate-y-20 translate-x-26" + priority + />Confirm translate-x-26 exists in your Tailwind scale; otherwise use arbitrary value e.g. translate-x-[6.5rem].
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
src/app/(mobile-ui)/qr-pay/page.tsx(6 hunks)src/components/Global/DirectSendQR/index.tsx(1 hunks)src/components/Global/Icons/qr-code.tsx(1 hunks)src/components/Global/PeanutLoading/index.tsx(1 hunks)src/components/Home/RewardsCardModal.tsx(0 hunks)
💤 Files with no reviewable changes (1)
- src/components/Home/RewardsCardModal.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
src/components/Global/DirectSendQR/index.tsx (1)
src/components/Global/Icons/Icon.tsx (1)
Icon(206-215)
src/app/(mobile-ui)/qr-pay/page.tsx (3)
src/context/SupportModalContext.tsx (1)
useSupportModalContext(22-28)src/services/manteca.ts (1)
mantecaApi(102-290)src/components/Global/Icons/Icon.tsx (1)
Icon(206-215)
| <svg className={props.className} viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||
| <path | ||
| d="M0.337891 10.6832H10.6854V0.335693H0.337891V10.6832ZM2.92477 2.92257H8.09852V8.09632H2.92477V2.92257Z" | ||
| d="M12 18H10V16H12V18ZM10 11H8V16H10V11ZM18 9H16V13H18V9ZM16 7H14V9H16V7ZM4 9H2V11H4V9ZM2 7H0V9H2V7ZM9 2H11V0H9V2ZM1.5 1.5V4.5H4.5V1.5H1.5ZM5 6H1C0.45 6 0 5.55 0 5V1C0 0.45 0.45 0 1 0H5C5.55 0 6 0.45 6 1V5C6 5.55 5.55 6 5 6ZM1.5 13.5V16.5H4.5V13.5H1.5ZM5 18H1C0.45 18 0 17.55 0 17V13C0 12.45 0.45 12 1 12H5C5.55 12 6 12.45 6 13V17C6 17.55 5.55 18 5 18ZM13.5 1.5V4.5H16.5V1.5H13.5ZM17 6H13C12.45 6 12 5.55 12 5V1C12 0.45 12.45 0 13 0H17C17.55 0 18 0.45 18 1V5C18 5.55 17.55 6 17 6ZM16 16V13H12V15H14V18H18V16H16ZM14 9H10V11H14V9ZM10 7H4V9H6V11H8V9H10V7ZM11 6V4H9V2H7V6H11ZM3.75 2.25H2.25V3.75H3.75V2.25ZM3.75 14.25H2.25V15.75H3.75V14.25ZM15.75 2.25H14.25V3.75H15.75V2.25Z" |
There was a problem hiding this comment.
A11Y and theming regression: props dropped; hardcoded black.
Removing props spread breaks aria/role propagation; fill="black" blocks theming via currentColor. Restore prop forwarding and use currentColor.
-export const QrCodeIcon: FC<SVGProps<SVGSVGElement>> = (props) => (
- <svg className={props.className} viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+export const QrCodeIcon: FC<SVGProps<SVGSVGElement>> = ({ className, ...rest }) => (
+ <svg
+ className={className}
+ viewBox="0 0 18 18"
+ xmlns="http://www.w3.org/2000/svg"
+ aria-hidden={rest['aria-label'] ? undefined : true}
+ focusable="false"
+ {...rest}
+ >
<path
- d="M12 18H10V16H12V18ZM10 11H8V16H10V11ZM18 9H16V13H18V9ZM16 7H14V9H16V7ZM4 9H2V11H4V9ZM2 7H0V9H2V7ZM9 2H11V0H9V2ZM1.5 1.5V4.5H4.5V1.5H1.5ZM5 6H1C0.45 6 0 5.55 0 5V1C0 0.45 0.45 0 1 0H5C5.55 0 6 0.45 6 1V5C6 5.55 5.55 6 5 6ZM1.5 13.5V16.5H4.5V13.5H1.5ZM5 18H1C0.45 18 0 17.55 0 17V13C0 12.45 0.45 12 1 12H5C5.55 12 6 12.45 6 13V17C6 17.55 5.55 18 5 18ZM13.5 1.5V4.5H16.5V1.5H13.5ZM17 6H13C12.45 6 12 5.55 12 5V1C12 0.45 12.45 0 13 0H17C17.55 0 18 0.45 18 1V5C18 5.55 17.55 6 17 6ZM16 16V13H12V15H14V18H18V16H16ZM14 9H10V11H14V9ZM10 7H4V9H6V11H8V9H10V7ZM11 6V4H9V2H7V6H11ZM3.75 2.25H2.25V3.75H3.75V2.25ZM3.75 14.25H2.25V15.75H3.75V14.25ZM15.75 2.25H14.25V3.75H15.75V2.25Z"
- fill="black"
+ d="M12 18H10V16H12V18ZM10 11H8V16H10V11ZM18 9H16V13H18V9ZM16 7H14V9H16V7ZM4 9H2V11H4V9ZM2 7H0V9H2V7ZM9 2H11V0H9V2ZM1.5 1.5V4.5H4.5V1.5H1.5ZM5 6H1C0.45 6 0 5.55 0 5V1C0 0.45 0.45 0 1 0H5C5.55 0 6 0.45 6 1V5C6 5.55 5.55 6 5 6ZM1.5 13.5V16.5H4.5V13.5H1.5ZM5 18H1C0.45 18 0 17.55 0 17V13C0 12.45 0.45 12 1 12H5C5.55 12 6 12.45 6 13V17C6 17.55 5.55 18 5 18ZM13.5 1.5V4.5H16.5V1.5H13.5ZM17 6H13C12.45 6 12 5.55 12 5V1C12 0.45 12.45 0 13 0H17C17.55 0 18 0.45 18 1V5C18 5.55 17.55 6 17 6ZM16 16V13H12V15H14V18H18V16H16ZM14 9H10V11H14V9ZM10 7H4V9H6V11H8V9H10V7ZM11 6V4H9V2H7V6H11ZM3.75 2.25H2.25V3.75H3.75V2.25ZM3.75 14.25H2.25V15.75H3.75V14.25ZM15.75 2.25H14.25V3.75H15.75V2.25Z"
+ fill="currentColor"
/>
</svg>
)📝 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.
| <svg className={props.className} viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| <path | |
| d="M0.337891 10.6832H10.6854V0.335693H0.337891V10.6832ZM2.92477 2.92257H8.09852V8.09632H2.92477V2.92257Z" | |
| d="M12 18H10V16H12V18ZM10 11H8V16H10V11ZM18 9H16V13H18V9ZM16 7H14V9H16V7ZM4 9H2V11H4V9ZM2 7H0V9H2V7ZM9 2H11V0H9V2ZM1.5 1.5V4.5H4.5V1.5H1.5ZM5 6H1C0.45 6 0 5.55 0 5V1C0 0.45 0.45 0 1 0H5C5.55 0 6 0.45 6 1V5C6 5.55 5.55 6 5 6ZM1.5 13.5V16.5H4.5V13.5H1.5ZM5 18H1C0.45 18 0 17.55 0 17V13C0 12.45 0.45 12 1 12H5C5.55 12 6 12.45 6 13V17C6 17.55 5.55 18 5 18ZM13.5 1.5V4.5H16.5V1.5H13.5ZM17 6H13C12.45 6 12 5.55 12 5V1C12 0.45 12.45 0 13 0H17C17.55 0 18 0.45 18 1V5C18 5.55 17.55 6 17 6ZM16 16V13H12V15H14V18H18V16H16ZM14 9H10V11H14V9ZM10 7H4V9H6V11H8V9H10V7ZM11 6V4H9V2H7V6H11ZM3.75 2.25H2.25V3.75H3.75V2.25ZM3.75 14.25H2.25V15.75H3.75V14.25ZM15.75 2.25H14.25V3.75H15.75V2.25Z" | |
| export const QrCodeIcon: FC<SVGProps<SVGSVGElement>> = ({ className, ...rest }) => ( | |
| <svg | |
| className={className} | |
| viewBox="0 0 18 18" | |
| xmlns="http://www.w3.org/2000/svg" | |
| aria-hidden={rest['aria-label'] ? undefined : true} | |
| focusable="false" | |
| {...rest} | |
| > | |
| <path | |
| d="M12 18H10V16H12V18ZM10 11H8V16H10V11ZM18 9H16V13H18V9ZM16 7H14V9H16V7ZM4 9H2V11H4V9ZM2 7H0V9H2V7ZM9 2H11V0H9V2ZM1.5 1.5V4.5H4.5V1.5H1.5ZM5 6H1C0.45 6 0 5.55 0 5V1C0 0.45 0.45 0 1 0H5C5.55 0 6 0.45 6 1V5C6 5.55 5.55 6 5 6ZM1.5 13.5V16.5H4.5V13.5H1.5ZM5 18H1C0.45 18 0 17.55 0 17V13C0 12.45 0.45 12 1 12H5C5.55 12 6 12.45 6 13V17C6 17.55 5.55 18 5 18ZM13.5 1.5V4.5H16.5V1.5H13.5ZM17 6H13C12.45 6 12 5.55 12 5V1C12 0.45 12.45 0 13 0H17C17.55 0 18 0.45 18 1V5C18 5.55 17.55 6 17 6ZM16 16V13H12V15H14V18H18V16H16ZM14 9H10V11H14V9ZM10 7H4V9H6V11H8V9H10V7ZM11 6V4H9V2H7V6H11ZM3.75 2.25H2.25V3.75H3.75V2.25ZM3.75 14.25H2.25V15.75H3.75V14.25ZM15.75 2.25H14.25V3.75H15.75V2.25Z" | |
| fill="currentColor" | |
| /> | |
| </svg> | |
| ) |
🤖 Prompt for AI Agents
In src/components/Global/Icons/qr-code.tsx around lines 4 to 6, the SVG was
changed to drop prop forwarding and uses a hardcoded black fill which breaks
accessibility (aria/role) and theming; restore spreading incoming props onto the
root <svg> (e.g. keep className but add {...props}) so aria, role, title, etc.
propagate, and remove the hardcoded fill="black" on the path (or replace it with
fill="currentColor") so the icon respects CSS color/theming.
| }) | ||
| .catch((error) => { | ||
| if (error.message.includes("provider can't decode it")) { | ||
| setWaitingForMerchantAmount(true) |
There was a problem hiding this comment.
sad :(
But good on removing dead code
Signed-off-by: Hugo Montenegro <hugo@peanut.to>
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
src/app/(mobile-ui)/qr-pay/page.tsx (2)
390-409: CRITICAL: Reset retryCount on success and avoid brittle error matching.Two critical issues remain from previous reviews:
retryCount not reset on success: Line 398 should reset
retryCount.current = 0when payment lock succeeds. Without this, subsequent "Scan again" attempts will immediately hit the retry cap.Brittle error string matching (line 401): Matching
error.message.includes("provider can't decode it")is fragile. If the API message changes, this logic breaks.Apply this diff to fix both issues:
.then((pl) => { setWaitingForMerchantAmount(false) + retryCount.current = 0 setPaymentLock(pl) }) .catch((error) => { - if (error.message.includes("provider can't decode it")) { + // TODO: Use typed error codes from API instead of string matching + if (error.message && typeof error.message === 'string' && error.message.includes("provider can't decode it")) { setWaitingForMerchantAmount(true) } else { setErrorInitiatingPayment(error.message) setWaitingForMerchantAmount(false) } })
910-913: CRITICAL: "Scan again" must reset retryCount.Line 912 enables retry but doesn't reset
retryCount.current. If the user previously exhausted 3 retries, clicking "Scan again" will immediately show this modal again.Apply this diff:
onClick={() => { setShowOrderNotReadyModal(false) + retryCount.current = 0 setShouldRetry(true) }}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/app/(mobile-ui)/qr-pay/page.tsx(6 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-05-15T14:47:26.891Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#857
File: src/hooks/useWebSocket.ts:77-82
Timestamp: 2025-05-15T14:47:26.891Z
Learning: The useWebSocket hook in src/hooks/useWebSocket.ts is designed to provide raw history entries, while the components using it (such as HomeHistory.tsx) are responsible for implementing deduplication logic based on UUID to prevent duplicate entries when combining WebSocket data with other data sources.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
🧬 Code graph analysis (1)
src/app/(mobile-ui)/qr-pay/page.tsx (3)
src/context/SupportModalContext.tsx (1)
useSupportModalContext(39-45)src/services/manteca.ts (1)
mantecaApi(102-290)src/components/Global/Icons/Icon.tsx (1)
Icon(206-215)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Cursor Bugbot
- GitHub Check: Deploy-Preview
🔇 Additional comments (4)
src/app/(mobile-ui)/qr-pay/page.tsx (4)
46-47: LGTM! Clean context and state additions.The new imports and state variables are well-structured for the merchant-amount waiting and retry features.
Also applies to: 89-92
767-772: LGTM! SimpleFi retry effect is correctly structured.The retry trigger logic is clean and follows the same pattern as the MANTECA flow.
774-786: LGTM! Auto-retry logic with exponential backoff is well-implemented.The 3-retry limit with 3-second delays provides a good balance between user experience and server load. The fallback to showing the support modal after retries is user-friendly.
919-925: LGTM! Support button integration is user-friendly.The "Having trouble?" option provides a clear escape hatch when automatic retries fail. Good UX addition.
This PR is being reviewed by Cursor BugbotDetailsYou are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle. To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial. |
Bug: SimpleFi Retry Logic Fails on Payment Processor ChangeThe |
Bug: Unintended Retries Due to Callback DependencyThe useEffect depends on |
Bug: Retry Logic Fails to Clean Up TimeoutThe retry logic has a race condition. When |
Do not merge before prod release
WhatsApp.Video.2025-10-22.at.20.27.08.mp4