Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/app/(mobile-ui)/history/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from 'react'
import { generateMetadata } from '@/app/metadata'

export const metadata = generateMetadata({
title: 'Transaction History | Peanut',
title: 'History | Peanut',
description:
'View your transaction history with Peanut. Track your P2P digital dollar payments, transfers, and claims.',
})
Expand Down
21 changes: 10 additions & 11 deletions src/app/[...recipient]/client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import InitialPaymentView from '@/components/Payment/Views/Initial.payment.view'
import DirectSuccessView from '@/components/Payment/Views/Status.payment.view'
import PintaReqPaySuccessView from '@/components/PintaReqPay/Views/Success.pinta.view'
import PublicProfile from '@/components/Profile/components/PublicProfile'
import { TransactionDetailsDrawer } from '@/components/TransactionDetails/TransactionDetailsDrawer'
import { TransactionDetailsReceipt } from '@/components/TransactionDetails/TransactionDetailsDrawer'
import { TransactionDetails } from '@/components/TransactionDetails/transactionTransformer'
import { useAuth } from '@/context/authContext'
import { useCurrency } from '@/hooks/useCurrency'
Expand Down Expand Up @@ -255,7 +255,11 @@ export default function PaymentPage({ recipient, flow = 'request_pay' }: Props)
return null
}

const username = recipientAccount?.user.username
const username =
recipientAccount?.user?.username ||
recipientAccount?.identifier ||
chargeDetails.requestLink.recipientAddress
const originalUserRole = isCurrentUser ? EHistoryUserRole.RECIPIENT : EHistoryUserRole.SENDER
let details: Partial<TransactionDetails> = {
id: chargeDetails.uuid,
status,
Expand All @@ -267,9 +271,9 @@ export default function PaymentPage({ recipient, flow = 'request_pay' }: Props)
attachmentUrl: chargeDetails.requestLink.attachmentUrl ?? undefined,
cancelledDate: status === 'cancelled' ? new Date(chargeDetails.timeline[0].time) : undefined,
extraDataForDrawer: {
isLinkTransaction: true,
isLinkTransaction: originalUserRole === EHistoryUserRole.SENDER && isCurrentUser,
originalType: EHistoryEntryType.REQUEST,
originalUserRole: isCurrentUser ? EHistoryUserRole.RECIPIENT : EHistoryUserRole.SENDER,
originalUserRole: originalUserRole,
link: window.location.href,
},
userName: username ?? chargeDetails.requestLink.recipientAddress,
Expand Down Expand Up @@ -395,6 +399,8 @@ export default function PaymentPage({ recipient, flow = 'request_pay' }: Props)
<>
{parsedPaymentData?.token?.symbol === 'PNT' ? (
<PintaReqPaySuccessView />
) : isDrawerOpen && selectedTransaction?.id === transactionForDrawer?.id ? (
<TransactionDetailsReceipt transaction={selectedTransaction} />
) : (
<DirectSuccessView
headerTitle={isAddMoneyFlow ? 'Add Money' : 'Send'}
Expand All @@ -409,13 +415,6 @@ export default function PaymentPage({ recipient, flow = 'request_pay' }: Props)
)}
</>
)}
<TransactionDetailsDrawer
isOpen={isDrawerOpen && selectedTransaction?.id === transactionForDrawer?.id}
onClose={() => {
router.push('/home')
}}
transaction={selectedTransaction}
/>
</div>
)
}
Expand Down
20 changes: 20 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { FooterVisibilityProvider } from '@/context/footerVisibility'
import { Viewport } from 'next'
import { Londrina_Solid, Roboto_Flex, Sniglet } from 'next/font/google'
import localFont from 'next/font/local'
import Script from 'next/script'
import '../styles/globals.css'
import { generateMetadata } from './metadata'

Expand Down Expand Up @@ -58,9 +59,28 @@ export const viewport: Viewport = {
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<Script id="google-tag-manager" strategy="afterInteractive">
{`
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-5MGHBCQ9');
`}
</Script>
</head>
<body
className={`${roboto.variable} ${londrina.variable} ${knerdOutline.variable} ${knerdFilled.variable} ${sniglet.variable} chakra-ui-light font-sans`}
>
<noscript>
<iframe
src="https://www.googletagmanager.com/ns.html?id=GTM-5MGHBCQ9"
height="0"
width="0"
style={{ display: 'none', visibility: 'hidden' }}
></iframe>
</noscript>
<PeanutProvider>
<ContextProvider>
<FooterVisibilityProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ const AddWithdrawCountriesList = ({ flow }: AddWithdrawCountriesListProps) => {
backgroundColor:
method.icon === ('bank' as IconName)
? '#FFC900'
: getColorForUsername(method.title).backgroundColor,
: getColorForUsername(method.title).lightShade,
color: method.icon === ('bank' as IconName) ? 'black' : 'black',
Comment on lines +64 to +65
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix redundant conditional and align with color scheme pattern.

The conditional logic on line 65 is redundant since both branches return 'black'. Based on the new color utility pattern used in other components, non-bank icons should use darkShade for better contrast and consistency.

Apply this diff to fix the redundant conditional:

                                            backgroundColor:
                                                method.icon === ('bank' as IconName)
                                                    ? '#FFC900'
                                                    : getColorForUsername(method.title).lightShade,
-                                            color: method.icon === ('bank' as IconName) ? 'black' : 'black',
+                                            color: method.icon === ('bank' as IconName) ? 'black' : getColorForUsername(method.title).darkShade,
📝 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.

Suggested change
: getColorForUsername(method.title).lightShade,
color: method.icon === ('bank' as IconName) ? 'black' : 'black',
backgroundColor:
method.icon === ('bank' as IconName)
? '#FFC900'
: getColorForUsername(method.title).lightShade,
color: method.icon === ('bank' as IconName)
? 'black'
: getColorForUsername(method.title).darkShade,
🤖 Prompt for AI Agents
In src/components/AddWithdraw/components/AddWithdrawCountriesList.tsx around
lines 64 to 65, the conditional setting the color to 'black' regardless of the
condition is redundant. Remove the ternary operator and set the color to 'black'
directly for bank icons, and for non-bank icons, use
getColorForUsername(method.title).darkShade to align with the color scheme
pattern used in other components.

}}
/>
) : (
Expand Down
29 changes: 11 additions & 18 deletions src/components/Claim/Claim.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
'use client'
import peanut from '@squirrel-labs/peanut-sdk'
import { useCallback, useContext, useEffect, useState, useMemo } from 'react'
import { useCallback, useContext, useEffect, useMemo, useState } from 'react'

import { fetchTokenDetails, fetchTokenPrice } from '@/app/actions/tokens'
import { StatusType } from '@/components/Global/Badges/StatusBadge'
import { TransactionDetailsReceipt } from '@/components/TransactionDetails/TransactionDetailsDrawer'
import { TransactionDetails } from '@/components/TransactionDetails/transactionTransformer'
import * as consts from '@/constants'
import { tokenSelectorContext } from '@/context'
import { useAuth } from '@/context/authContext'
import { useTransactionDetailsDrawer } from '@/hooks/useTransactionDetailsDrawer'
import { EHistoryEntryType, EHistoryUserRole } from '@/hooks/useTransactionHistory'
import { useWallet } from '@/hooks/wallet/useWallet'
import * as interfaces from '@/interfaces'
import { ESendLinkStatus, sendLinksApi, type ClaimLinkData } from '@/services/sendLinks'
import { isStableCoin } from '@/utils'
import { getInitialsFromName, getTokenDetails, isStableCoin } from '@/utils'
import * as Sentry from '@sentry/nextjs'
import { useRouter } from 'next/navigation'
import type { Hash } from 'viem'
import { formatUnits } from 'viem'
import PageContainer from '../0_Bruddle/PageContainer'
import PeanutLoading from '../Global/PeanutLoading'
import * as _consts from './Claim.consts'
import * as genericViews from './Generic'
import FlowManager from './Link/FlowManager'
import { TransactionDetailsDrawer } from '@/components/TransactionDetails/TransactionDetailsDrawer'
import { useTransactionDetailsDrawer } from '@/hooks/useTransactionDetailsDrawer'
import { TransactionDetails } from '@/components/TransactionDetails/transactionTransformer'
import { StatusType } from '@/components/Global/Badges/StatusBadge'
import { getInitialsFromName, getTokenDetails } from '@/utils'
import { EHistoryEntryType, EHistoryUserRole } from '@/hooks/useTransactionHistory'
import type { Hash } from 'viem'
import { formatUnits } from 'viem'
import { useRouter } from 'next/navigation'

export const Claim = ({}) => {
const [step, setStep] = useState<_consts.IClaimScreenState>(_consts.INIT_VIEW_STATE)
Expand Down Expand Up @@ -276,13 +275,7 @@ export const Claim = ({}) => {
)}
{linkState === _consts.claimLinkStateType.WRONG_PASSWORD && <genericViews.WrongPasswordClaimLink />}
{linkState === _consts.claimLinkStateType.NOT_FOUND && <genericViews.NotFoundClaimLink />}
<TransactionDetailsDrawer
isOpen={isDrawerOpen && selectedTransaction?.id === transactionForDrawer?.id}
onClose={() => {
router.push('/home')
}}
transaction={selectedTransaction}
/>
<TransactionDetailsReceipt transaction={selectedTransaction} />
</PageContainer>
)
}
16 changes: 13 additions & 3 deletions src/components/Global/PeanutActionDetailsCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import AvatarWithBadge, { AvatarSize } from '@/components/Profile/AvatarWithBadg
import { PEANUT_WALLET_TOKEN_SYMBOL } from '@/constants'
import { RecipientType } from '@/lib/url-parser/types/payment'
import { printableAddress } from '@/utils'
import { getColorForUsername } from '@/utils/color.utils'
import { AVATAR_TEXT_DARK, getColorForUsername } from '@/utils/color.utils'
import { useCallback } from 'react'
import { twMerge } from 'tailwind-merge'
import Attachment from '../Attachment'
Expand Down Expand Up @@ -85,9 +85,19 @@ export default function PeanutActionDetailsCard({
backgroundColor:
viewType === 'SUCCESS'
? '#29CC6A'
: transactionType === 'ADD_MONEY' || recipientType === 'ADDRESS'
: transactionType === 'ADD_MONEY' ||
recipientType === 'ADDRESS' ||
recipientType === 'ENS'
? '#FFC900'
: getColorForUsername(recipientName).backgroundColor,
: getColorForUsername(recipientName).darkShade,
Comment on lines +88 to +92
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix potential contrast issue with background color.

The logic uses darkShade for backgroundColor, but this could create contrast issues since darkShade is also used for text color in line 100. Based on the pattern in other components (like UserCard.tsx), the background should use lightShade instead.

Apply this fix:

-                                  : getColorForUsername(recipientName).darkShade,
+                                  : getColorForUsername(recipientName).lightShade,
🤖 Prompt for AI Agents
In src/components/Global/PeanutActionDetailsCard/index.tsx around lines 88 to
92, the background color uses getColorForUsername(recipientName).darkShade,
which may cause contrast issues since darkShade is also used for text color.
Change the background color to use getColorForUsername(recipientName).lightShade
instead to ensure better contrast and readability, following the pattern used in
similar components like UserCard.tsx.

color:
viewType === 'SUCCESS'
? AVATAR_TEXT_DARK
: transactionType === 'ADD_MONEY' ||
recipientType === 'ADDRESS' ||
recipientType === 'ENS'
? AVATAR_TEXT_DARK
: getColorForUsername(recipientName).darkShade,
}}
/>
</div>
Expand Down
1 change: 0 additions & 1 deletion src/components/Global/TokenSelector/TokenSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,6 @@ const TokenSelector: React.FC<NewTokenSelectorProps> = ({ classNameButton, viewT
buttonSymbol = generalTokenDetails.symbol
buttonLogoURI = generalTokenDetails.logoURI
buttonChainName = chainInfo.axelarChainName || `Chain ${selectedChainID}`
buttonChainLogoURI = chainInfo.chainIconURI
}
if (userBalanceDetails) {
buttonFormattedBalance = formatTokenAmount(userBalanceDetails.amount) ?? null
Expand Down
5 changes: 3 additions & 2 deletions src/components/Profile/AvatarWithBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,10 @@ const AvatarWithBadge: React.FC<AvatarWithBadgeProps> = ({
// apply dynamic styles (e.g., background color)

style={{
background: name ? getColorForUsername(name).backgroundColor : undefined,
background: name ? getColorForUsername(name).lightShade : undefined,
border: name && !icon ? `1px solid ${getColorForUsername(name).darkShade}` : undefined,
color: name ? getColorForUsername(name).darkShade : !icon ? textColor : undefined,
...inlineStyle,
color: !icon ? textColor : undefined,
}}
>
{/* display icon if provided, otherwise display initials */}
Expand Down
27 changes: 15 additions & 12 deletions src/components/Profile/components/PublicProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import ProfileHeader from './ProfileHeader'
import { useState, useEffect } from 'react'
import { usersApi } from '@/services/users'
import { useRouter } from 'next/navigation'
import { formatExtendedNumber } from '@/utils'
import Card from '@/components/Global/Card'
import { useAuth } from '@/context/authContext'

interface PublicProfileProps {
username: string
Expand All @@ -29,6 +32,9 @@ const PublicProfile: React.FC<PublicProfileProps> = ({
}) => {
const dispatch = useAppDispatch()
const [fullName, setFullName] = useState<string>(username)
const [totalSent, setTotalSent] = useState<string>('0.00')
const [totalReceived, setTotalReceived] = useState<string>('0.00')
const { user } = useAuth()
const router = useRouter()

// Handle send button click
Expand All @@ -43,6 +49,8 @@ const PublicProfile: React.FC<PublicProfileProps> = ({
useEffect(() => {
usersApi.getByUsername(username).then((user) => {
if (user?.fullName) setFullName(user.fullName)
setTotalSent(user.totalUsdSent)
setTotalReceived(user.totalUsdReceived)
Comment on lines +52 to +53
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add null/undefined checks for API response fields.

The code directly assigns user.totalUsdSent and user.totalUsdReceived without checking if these fields exist. This could set undefined values to state if the API doesn't return these fields.

Apply this diff to add proper null checking:

-setTotalSent(user.totalUsdSent)
-setTotalReceived(user.totalUsdReceived)
+setTotalSent(user.totalUsdSent ?? '0.00')
+setTotalReceived(user.totalUsdReceived ?? '0.00')
📝 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.

Suggested change
setTotalSent(user.totalUsdSent)
setTotalReceived(user.totalUsdReceived)
setTotalSent(user.totalUsdSent ?? '0.00')
setTotalReceived(user.totalUsdReceived ?? '0.00')
🤖 Prompt for AI Agents
In src/components/Profile/components/PublicProfile.tsx around lines 52 to 53,
the code sets state values directly from user.totalUsdSent and
user.totalUsdReceived without checking if these fields are null or undefined. To
fix this, add null or undefined checks before setting the state, for example by
using conditional (ternary) operators or logical OR to provide default values
like 0 when these fields are missing from the API response.

})
}, [username])

Expand Down Expand Up @@ -92,31 +100,26 @@ const PublicProfile: React.FC<PublicProfileProps> = ({
</Link>
</div>

{/*
<div className="space-y-6">
{!!hasTransactions && (
{totalSent !== '0.00' && totalReceived !== '0.00' && (
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider showing stats when either value is non-zero.

The current condition requires both totalSent AND totalReceived to be non-zero. Users who have only sent or only received money won't see any statistics.

Consider changing the logic to show stats when at least one value is non-zero:

-{totalSent !== '0.00' && totalReceived !== '0.00' && (
+{(totalSent !== '0.00' || totalReceived !== '0.00') && (
📝 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.

Suggested change
{totalSent !== '0.00' && totalReceived !== '0.00' && (
{(totalSent !== '0.00' || totalReceived !== '0.00') && (
🤖 Prompt for AI Agents
In src/components/Profile/components/PublicProfile.tsx at line 103, the
condition currently requires both totalSent and totalReceived to be non-zero to
display stats. Change the condition to use a logical OR instead of AND so that
stats are shown when either totalSent or totalReceived is non-zero, ensuring
users who have only sent or only received money see their statistics.

<div className="space-y-6">
<div>
<Card position="first">
<div className="flex items-center justify-between py-2">
<span className="font-medium">Total sent to</span>
<span className="font-medium">
${formatExtendedNumber(transactions?.sent || 0)}
</span>
<span className="font-medium">${formatExtendedNumber(totalSent)}</span>
</div>
</Card>
<Card position="last">
<div className="flex items-center justify-between py-2">
<span className="font-medium">Total received from</span>
<span className="font-medium">
${formatExtendedNumber(transactions?.received || 0)}
</span>
<span className="font-medium">${formatExtendedNumber(totalReceived)}</span>
</div>
</Card>
</div>
)}

</div>
</div>
)}

{/*
{!hasTransactions && (
<div className="relative flex flex-col items-center">
<Card position="single" className="z-10 mt-28 space-y-2 p-4 text-center">
Expand Down
4 changes: 2 additions & 2 deletions src/components/Setup/Views/Signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ const SignupStep = () => {
}

// check character requirement
if (!username.match(/^[a-z][a-z0-9_]{3,11}$/)) {
setError('Username must contain only lowercase letters, numbers and underscores and start with a letter')
if (!username.match(/^[a-z][a-z0-9]{3,11}$/)) {
setError('Username must contain only lowercase letters and numbers and start with a letter')
return false
}

Expand Down
4 changes: 2 additions & 2 deletions src/components/TransactionDetails/TransactionAvatarBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ const TransactionAvatarBadge: React.FC<TransactionAvatarBadgeProps> = ({
iconFillColor = AVATAR_TEXT_DARK
} else if (displayInitials) {
const colors = getColorForUsername(userName)
calculatedBgColor = colors.backgroundColor
textColor = AVATAR_TEXT_DARK
calculatedBgColor = colors.lightShade
textColor = colors.darkShade
displayIconName = undefined
} else {
// fallback for send/request if no initials and not link/address
Expand Down
Loading
Loading