Skip to content

Conversation

Hugo0
Copy link
Contributor

@Hugo0 Hugo0 commented Jul 28, 2025

No description provided.

jjramirezn and others added 30 commits June 13, 2025 12:38
Stop using the skd and use the squid API directly, this give us more
control and access to all the data that returns squid (for example,
we now have access to the fees and don't have to recalculate them
ourselves)
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Use coral through squid to get the cross-chain route for the different
flows. This enables xchain withdraw for peanut wallet
[TASK-11579] feat: add cross-chain action card
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

🔭 Outside diff range comments (2)
src/components/Profile/components/PublicProfile.tsx (1)

128-172: Fix logical issue in nested conditional rendering.

There's a logical inconsistency: lines 132-138 check isLoggedIn inside a block that's already conditioned on !isLoggedIn, so the "You're all set" branch will never execute.

{!isLoggedIn && (
    <div className="relative flex flex-col items-center">
        <Card position="single" className="z-10 mt-28 space-y-2 p-4 text-center">
-           {isLoggedIn ? (
-               <>
-                   <h2 className="text-lg font-extrabold">You're all set</h2>
-                   <p className="mx-auto max-w-[55%] text-sm">
-                       Now send or request money to get started.
-                   </p>
-               </>
-           ) : (
-               <div className="space-y-4">
-                   <div className="space-y-2">
-                       <h2 className="text-lg font-extrabold">Join Peanut!</h2>
-                       <p>Send and receive payments in seconds with your own Peanut account.</p>
-                   </div>
-                   <Button
-                       variant="purple"
-                       shadowSize="4"
-                       className="mt-1 flex w-full items-center justify-center gap-2 rounded-sm"
-                       onClick={() => router.push('/setup')}
-                   >
-                       <Icon name="user-plus" size={16} fill="black" />
-                       <span className="font-bold">Create Account</span>
-                   </Button>
-               </div>
-           )}
+           <div className="space-y-4">
+               <div className="space-y-2">
+                   <h2 className="text-lg font-extrabold">Join Peanut!</h2>
+                   <p>Send and receive payments in seconds with your own Peanut account.</p>
+               </div>
+               <Button
+                   variant="purple"
+                   shadowSize="4"
+                   className="mt-1 flex w-full items-center justify-center gap-2 rounded-sm"
+                   onClick={() => router.push('/setup')}
+               >
+                   <Icon name="user-plus" size={16} fill="black" />
+                   <span className="font-bold">Create Account</span>
+               </Button>
+           </div>
        </Card>
src/components/Payment/Views/Confirm.payment.view.tsx (1)

366-418: Fix conditional hook call violation

The useMemo hook for minReceived is called after the early return for isPintaReq, which violates React's rules of hooks. All hooks must be called in the same order on every render.

Move the minReceived calculation before the isPintaReq check:

+    const minReceived = useMemo<string | null>(() => {
+        if (!chargeDetails?.tokenDecimals || !requestedResolvedTokenSymbol) return null
+        if (!xChainRoute) {
+            return `$ ${chargeDetails?.tokenAmount}`
+        }
+        const amount = formatAmount(
+            formatUnits(BigInt(xChainRoute.rawResponse.route.estimate.toAmountMin), chargeDetails.tokenDecimals)
+        )
+        return isStableCoin(requestedResolvedTokenSymbol) ? `$ ${amount}` : `${amount} ${requestedResolvedTokenSymbol}`
+    }, [xChainRoute, chargeDetails?.tokenDecimals, requestedResolvedTokenSymbol])

     if (isPintaReq) {
         return (
             // ... PintaReq UI
         )
     }

-    const minReceived = useMemo<string | null>(() => {
-        if (!chargeDetails?.tokenDecimals || !requestedResolvedTokenSymbol) return null
-        if (!xChainRoute) {
-            return `$ ${chargeDetails?.tokenAmount}`
-        }
-        const amount = formatAmount(
-            formatUnits(BigInt(xChainRoute.rawResponse.route.estimate.toAmountMin), chargeDetails.tokenDecimals)
-        )
-        return isStableCoin(requestedResolvedTokenSymbol) ? `$ ${amount}` : `${amount} ${requestedResolvedTokenSymbol}`
-    }, [xChainRoute, chargeDetails?.tokenDecimals, requestedResolvedTokenSymbol])
🧹 Nitpick comments (26)
src/components/Global/ScreenOrientationLocker.tsx (1)

9-11: Consider more specific type assertions.

The as any type assertions could be more specific for better type safety:

-            if (screen.orientation && (screen.orientation as any).lock) {
+            if (screen.orientation && 'lock' in screen.orientation) {
                try {
-                    await (screen.orientation as any).lock('portrait-primary')
+                    await (screen.orientation as any as ScreenOrientationLock).lock('portrait-primary')

Alternatively, you could define a proper interface for the extended ScreenOrientation API.

src/components/Claim/Claim.tsx (1)

27-27: Remove unused import.

The useGuestFlow import is added but not used in this component. Consider removing it to keep the imports clean.

-import { useGuestFlow } from '@/context/GuestFlowContext'

If this import is preparation for upcoming changes, consider adding a comment to clarify the intent.

src/app/(mobile-ui)/withdraw/page.tsx (1)

97-101: Consider consolidating state cleanup logic.

The explicit state clearing is good defensive programming, but you now have two useEffect hooks that run on mount for state cleanup (lines 42-44 and 97-101). Consider whether these could be consolidated if resetWithdrawFlow() doesn't already handle withdrawal data clearing.

If consolidation is desired, you could combine the cleanup:

 useEffect(() => {
     resetWithdrawFlow()
+    setAmountToWithdraw('')
+    setWithdrawData(null)
 }, [])

-// Clean state
-useEffect(() => {
-    setAmountToWithdraw('')
-    setWithdrawData(null)
-}, [])
src/components/Home/FloatingReferralButton/index.tsx (1)

9-16: LGTM! Well-implemented floating button with good animations.

The component has nice animation effects and proper accessibility. The responsive design hiding it on larger screens is appropriate for a mobile-focused feature.

Consider these potential improvements:

  1. The hard-coded positioning (left-[43%] top-[15%]) might be fragile across different screen sizes
  2. Z-index of 50 is quite high - ensure it doesn't conflict with modals or other overlays
  3. Consider adding a fallback for the emoji in case it doesn't render properly
- className="absolute left-[43%] top-[15%] z-50 animate-pulse cursor-pointer text-4xl transition-all duration-300 hover:scale-110 hover:animate-none md:hidden"
+ className="absolute left-[43%] top-[15%] z-30 animate-pulse cursor-pointer text-4xl transition-all duration-300 hover:scale-110 hover:animate-none md:hidden"
src/components/LandingPage/yourMoney.tsx (3)

12-12: Consider using more specific types for SVG imports.

Using any type for titleSvg reduces type safety. Consider using a more specific type like React.FC<React.SVGProps<SVGSVGElement>> or string if these are static imports.

interface Feature {
    id: number
    title: string
-   titleSvg: any
+   titleSvg: React.FC<React.SVGProps<SVGSVGElement>> | string
    description: string
-   imageSrc: any
+   imageSrc: StaticImageData
    imageAlt: string
}

70-70: Clarify the conditional margin logic.

The condition index === 1 ? 'mb-3' : 'mb-4' applies different bottom margins to the second feature. This seems arbitrary without context.

Consider adding a comment explaining why the second feature needs different spacing, or refactor to use consistent spacing if this is unintentional.


75-75: Extract inline styles to CSS classes.

The inline letterSpacing: '-0.5px' style could be extracted to a Tailwind CSS class for better maintainability.

<p
-   className="w-full max-w-[360px] text-left font-roboto text-lg font-normal leading-relaxed md:text-lg"
-   style={{ letterSpacing: '-0.5px' }}
+   className="w-full max-w-[360px] text-left font-roboto text-lg font-normal leading-relaxed tracking-tight md:text-lg"
>

Or define a custom class in your Tailwind config if -0.5px isn't available as tracking-tight.

src/components/LandingPage/securityBuiltIn.tsx (2)

12-14: Consider using more specific types for asset imports.

Similar to other landing page components, using any type reduces type safety. Consider using more specific types for better TypeScript support.

interface Feature {
    id: number
    title: string
-   titleSvg: any
+   titleSvg: React.FC<React.SVGProps<SVGSVGElement>> | string
    description: string
-   iconSrc: any
+   iconSrc: StaticImageData
    iconAlt: string
}

78-78: Extract inline styles to CSS classes.

The inline letterSpacing: '-0.5px' style should be extracted to a Tailwind CSS class for consistency with the design system.

<p
-   className="w-full max-w-[360px] text-left font-roboto text-lg font-normal leading-relaxed md:text-lg"
-   style={{ letterSpacing: '-0.5px' }}
+   className="w-full max-w-[360px] text-left font-roboto text-lg font-normal leading-relaxed tracking-tight md:text-lg"
>
src/components/Global/IconStack.tsx (2)

22-28: Improve accessibility with descriptive alt text.

The current alt text icon-${index} is not descriptive. Consider making it more accessible by accepting alt text descriptions or using empty alt for decorative icons.

interface IconStackProps {
    icons: string[]
    iconSize?: number
    iconClassName?: string
+   iconAlts?: string[]
}

-const IconStack: React.FC<IconStackProps> = ({ icons, iconSize = 24, iconClassName = '' }) => {
+const IconStack: React.FC<IconStackProps> = ({ icons, iconSize = 24, iconClassName = '', iconAlts }) => {
    return (
        <div className="flex items-center -space-x-2">
            {icons.map((icon, index) => (
                <div
                    key={index}
                    className={twMerge(
                        `flex max-h-6 min-h-6 min-w-6 max-w-6 items-center justify-center rounded-full bg-white`,
                        iconClassName
                    )}
                    style={{ zIndex: index }}
                >
                    <Image
                        src={icon}
-                       alt={`icon-${index}`}
+                       alt={iconAlts?.[index] || ''}
                        width={iconSize}
                        height={iconSize}
                        className="min-h-6 min-w-6 rounded-full"
                    />
                </div>
            ))}
        </div>
    )
}

16-20: Consider making container dimensions responsive to iconSize.

The container has fixed dimensions (min-h-6, min-w-6, etc.) regardless of the configurable iconSize prop. This could cause layout issues with larger icons.

<div
    key={index}
    className={twMerge(
-       `flex max-h-6 min-h-6 min-w-6 max-w-6 items-center justify-center rounded-full bg-white`,
+       `flex items-center justify-center rounded-full bg-white`,
        iconClassName
    )}
-   style={{ zIndex: index }}
+   style={{ 
+     zIndex: index,
+     minHeight: `${iconSize + 4}px`,
+     minWidth: `${iconSize + 4}px`,
+     maxHeight: `${iconSize + 4}px`,
+     maxWidth: `${iconSize + 4}px`
+   }}
>
src/components/LandingPage/businessIntegrate.tsx (1)

32-39: Consider empty alt text for decorative scribble overlay.

Based on the retrieved learning about decorative elements, the scribble image is positioned absolutely as a decorative overlay and should use empty alt text to prevent layout issues if the image fails to load.

<Image
    src={scribble}
-   alt="Scribble around BUSINESS"
+   alt=""
    width={1200}
    height={120}
    unoptimized
    className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 scale-x-[-1.1] scale-y-[-1.0] md:scale-x-[-1.15] md:scale-y-[-0.9]"
/>
src/components/AddWithdraw/AddWithdrawCountriesList.tsx (1)

254-256: Payment method highlighting works but could be more maintainable.

The logic correctly highlights both bank and crypto payment methods with the consistent #FFC900 color.

Consider extracting the highlighting condition into a helper function for better maintainability:

+const shouldHighlightMethod = (method: SpecificPaymentMethod) => 
+    method.icon === 'bank' || method.id === 'crypto-add' || method.id === 'crypto-withdraw'

 backgroundColor:
-    method.icon === ('bank' as IconName)
-        ? '#FFC900'
-        : method.id === 'crypto-add' || method.id === 'crypto-withdraw'
-          ? '#FFC900'
-          : getColorForUsername(method.title).lightShade,
+    shouldHighlightMethod(method) ? '#FFC900' : getColorForUsername(method.title).lightShade,
src/components/Claim/Link/Onchain/Confirm.view.tsx (1)

62-71: Review the slippage calculation approach.

The manual 1% slippage calculation is applied to the deposited token amount, but this may not accurately reflect the actual slippage for cross-chain transactions. Cross-chain bridges typically have variable slippage based on liquidity and market conditions.

Consider using the actual slippage data from the selectedRoute if available:

const minReceived = useMemo<string>(() => {
    let amountNumber: number

+   if (selectedRoute?.route.estimate.slippage) {
+       // Use actual slippage from route
+       const slippageDecimal = selectedRoute.route.estimate.slippage / 100
+       amountNumber = Number(formatUnits(BigInt(claimLinkData.amount), claimLinkData.tokenDecimals)) * (1 - slippageDecimal)
+   } else {
        // manual 1% slippage calculation based on the deposited token amount
        amountNumber = Number(formatUnits(BigInt(claimLinkData.amount), claimLinkData.tokenDecimals)) * 0.99 // subtract 1%
+   }

    const formattedAmount = formatTokenAmount(amountNumber)
    return `$ ${formattedAmount}`
}, [selectedRoute, resolvedTokenSymbol, claimLinkData])
.env.example (1)

18-20: Consider addressing static analysis warnings for consistency.

The dotenv-linter identified several formatting and ordering inconsistencies in the environment variable declarations. While these don't affect functionality, maintaining consistent formatting improves readability.

Consider addressing the formatting warnings:

# Reorder and remove quotes for consistency
export NEXT_PUBLIC_ALCHEMY_API_KEY=
export NEXT_PUBLIC_DEFAULT_SQUID_INTEGRATOR_ID=
export MOBULA_API_URL=

# SQUID
export DEFAULT_SQUID_INTEGRATOR_ID=
export SQUID_API_URL=
export SQUID_INTEGRATOR_ID=

# Balance warning (reorder)
export NEXT_PUBLIC_BALANCE_WARNING_EXPIRY=15
export NEXT_PUBLIC_BALANCE_WARNING_THRESHOLD=1

Also applies to: 30-35, 44-45

src/components/Claim/Link/views/Confirm.bank-claim.view.tsx (1)

72-72: Address the TODO comment for full name handling.

The TODO comment indicates that the current full name is from the sender, not the recipient. This could lead to confusion in the UI.

This seems like an important UX issue where the wrong name might be displayed. Would you like me to help implement proper recipient name handling or create an issue to track this?

src/components/LandingPage/noFees.tsx (1)

63-112: Consider performance optimization for multiple animated stars.

While the star animations create a nice visual effect, having 5 simultaneously animated elements might impact performance on slower devices.

Consider adding a prefers-reduced-motion media query check to disable animations for users who prefer reduced motion:

+const prefersReducedMotion = typeof window !== 'undefined' 
+    ? window.matchMedia('(prefers-reduced-motion: reduce)').matches 
+    : false

// In the star animations:
-initial={{ opacity: 0, translateY: 20, translateX: 5, rotate: 22 }}
-whileInView={{ opacity: 1, translateY: 0, translateX: 0, rotate: 22 }}
-transition={{ type: 'spring', damping: 5, delay: 0.2 }}
+initial={{ opacity: 0, translateY: prefersReducedMotion ? 0 : 20, translateX: prefersReducedMotion ? 0 : 5, rotate: 22 }}
+whileInView={{ opacity: 1, translateY: 0, translateX: 0, rotate: 22 }}
+transition={prefersReducedMotion ? { duration: 0 } : { type: 'spring', damping: 5, delay: 0.2 }}
src/components/LandingPage/sendInSeconds.tsx (1)

113-140: Consider performance impact of multiple infinite animations

While the animations use GPU-accelerated transforms, having 4 continuous infinite animations might impact performance on low-end devices. Consider adding a prefers-reduced-motion check or limiting animations on mobile.

+    const prefersReducedMotion = typeof window !== 'undefined' && 
+        window.matchMedia('(prefers-reduced-motion: reduce)').matches
+
     return (
         <section className="relative overflow-hidden bg-secondary-1 px-4 py-16 text-n-1 md:py-32">
             {/* Decorative clouds, stars, and exclamations */}
             <div className="absolute left-0 top-0 h-full w-full overflow-hidden">
                 {/* Animated clouds */}
+                {!prefersReducedMotion && (
+                <>
                 <motion.img
                     src={borderCloud.src}
                     // ... rest of cloud animations
                 />
+                </>
+                )}
src/app/page.tsx (2)

176-176: Consider extracting repeated marquee rendering

The Marquee component is rendered 6 times with identical props. Consider extracting this into a reusable component or helper.

+const SectionDivider = () => <Marquee {...marqueeProps} />
+
 return (
     <Layout className="enable-select !m-0 w-full !p-0">
         <Hero
             heading={hero.heading}
             primaryCta={hero.primaryCta}
             buttonVisible={buttonVisible}
             buttonScale={buttonScale}
         />
-        <Marquee {...marqueeProps} />
+        <SectionDivider />
         <YourMoney />
-        <Marquee {...marqueeProps} />
+        <SectionDivider />
         <NoFees />
-        <Marquee {...marqueeProps} />
+        <SectionDivider />
         <SecurityBuiltIn />
-        <Marquee {...marqueeProps} />
+        <SectionDivider />
         <FAQs heading={faqs.heading} questions={faqs.questions} marquee={faqs.marquee} />
-        <Marquee {...marqueeProps} />
+        <SectionDivider />
         <div ref={sendInSecondsRef}>
             <SendInSeconds />
         </div>
-        <Marquee {...marqueeProps} />
+        <SectionDivider />
         <BusinessIntegrate />
-        <Marquee {...marqueeProps} />
+        <SectionDivider />
     </Layout>
 )

Also applies to: 186-201


85-175: Complex scroll interaction needs documentation

The scroll freeze and button scaling animation is intricate. Consider adding comments to explain the behavior for future maintainability.

Add comments explaining the key behaviors:

 useEffect(() => {
     const handleScroll = () => {
         if (sendInSecondsRef.current) {
             const targetElement = document.getElementById('sticky-button-target')
             if (!targetElement) return

             const targetRect = targetElement.getBoundingClientRect()
             const currentScrollY = window.scrollY

+            // Sticky button position calculation (16px from bottom + ~52px button height)
             const stickyButtonTop = window.innerHeight - 16 - 52
             const stickyButtonBottom = window.innerHeight - 16

+            // Freeze scroll when target overlaps with sticky button area
             const shouldFreeze =
                 targetRect.top <= stickyButtonBottom - 60 &&
                 targetRect.bottom >= stickyButtonTop - 60 &&
                 !animationComplete &&
                 !shrinkingPhase &&
                 !hasGrown
src/components/Claim/Link/views/BankFlowManager.view.tsx (1)

34-150: Well-structured bank claim flow with room for UX improvement

The implementation properly handles the multi-step process with good error handling. Consider making error messages more user-friendly for better UX.

Consider mapping technical errors to user-friendly messages:

 if (userResponse.kycStatus !== 'approved') {
-    setError('User not KYC approved')
-    return { error: 'User not KYC approved' }
+    setError('The sender needs to complete identity verification before you can receive funds to your bank account')
+    return { error: 'The sender needs to complete identity verification before you can receive funds to your bank account' }
 }
 if (!paymentRail || !currency) {
-    const err = 'Chain or token not supported for bank withdrawal'
+    const err = 'This token cannot be withdrawn to your bank account. Please contact support for assistance.'
     setError(err)
     return { error: err }
 }
src/components/GuestActions/MethodList.tsx (1)

95-98: Document intentional case fallthrough.

The 'crypto' case falls through to 'exchange', which appears intentional but should be documented for clarity.

 case 'crypto':
+    // Intentionally fall through - both crypto and exchange use external wallet
 case 'exchange':
     setClaimToExternalWallet(true)
     break
src/components/Request/link/views/Create.request.link.view.tsx (1)

109-115: Consider using parseFloat for consistent validation

The condition parseFloat(tokenValue) <= 0 might behave unexpectedly if tokenValue contains invalid input like "abc" (parseFloat returns NaN). Consider adding isNaN check.

-if (!tokenValue || parseFloat(tokenValue) <= 0) {
+const parsedValue = parseFloat(tokenValue)
+if (!tokenValue || isNaN(parsedValue) || parsedValue <= 0) {
src/app/(mobile-ui)/withdraw/crypto/page.tsx (1)

101-102: Remove debug console logs before production

Debug console.log statements should be removed or replaced with proper logging.

-console.log('Preparing withdraw transaction details...')
-console.dir(activeChargeDetailsFromStore)
 prepareTransactionDetails({

...

-console.log('Refreshing withdraw route due to expiry...')
-console.log('About to call prepareTransactionDetails with:', activeChargeDetailsFromStore)
 await prepareTransactionDetails({

Also applies to: 243-244

src/components/0_Bruddle/Button.tsx (2)

197-206: Remove timer values from effect dependencies to prevent unnecessary re-runs.

The cleanup function will always have access to the current timer values through closure, so including them in the dependency array causes unnecessary effect re-runs whenever timers are set or cleared.

-}, [pressTimer, progressInterval])
+}, [])

250-257: Consider making the progress bar color variant-aware.

The hardcoded purple gradient may not visually align with non-purple button variants (e.g., green, yellow). Consider adapting the progress bar color based on the button variant.

Example approach to make the progress bar color dynamic:

-className="absolute inset-0 bg-gradient-to-r from-purple-400 to-purple-600 opacity-30 transition-all duration-75 ease-out"
+className={twMerge(
+  "absolute inset-0 opacity-30 transition-all duration-75 ease-out",
+  variant === 'green' && "bg-gradient-to-r from-green-400 to-green-600",
+  variant === 'yellow' && "bg-gradient-to-r from-yellow-400 to-yellow-600",
+  (!variant || variant === 'purple') && "bg-gradient-to-r from-purple-400 to-purple-600",
+  // Add more variant mappings as needed
+)}

@kushagrasarathe kushagrasarathe changed the title Prod release 1 - Links v2 send flow guest claims Prod release SP 102 - Links v2 send flow guest claims Jul 29, 2025
Zishan-7 added 2 commits July 30, 2025 11:24
* fix: TOS acceptance taking too much time

* remove log
@Hugo0 Hugo0 merged commit a60a7b4 into peanut-wallet Jul 30, 2025
12 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Aug 13, 2025
@coderabbitai coderabbitai bot mentioned this pull request Aug 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants