Skip to content
Merged
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
7 changes: 4 additions & 3 deletions src/app/receipt/[entryId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { type Metadata } from 'next'
import { BASE_URL } from '@/constants/general.consts'
import { formatAmount, formatCurrency, isStableCoin } from '@/utils/general.utils'
import getOrigin from '@/lib/hosting/get-origin'
import PageContainer from '@/components/0_Bruddle/PageContainer'

// Helper function to map transaction card type to OG image type
function mapTransactionTypeToOGType(transactionType: string): 'send' | 'request' {
Expand Down Expand Up @@ -186,13 +187,13 @@ export default async function ReceiptPage({
}
const { transactionDetails } = mapTransactionDataForDrawer(entry)
return (
<div className="p-6">
<PageContainer className="flex min-h-[100dvh] flex-col items-center justify-center p-6">
<div className="md:hidden">
<NavHeader title="Receipt" />
</div>
<div className="flex min-h-[100dvh] flex-col items-center justify-center">
<div className="flex flex-1 flex-col items-center justify-center">
<TransactionDetailsReceipt className="w-full" transaction={transactionDetails!} />
</div>
</div>
</PageContainer>
)
}
47 changes: 42 additions & 5 deletions src/components/Claim/Claim.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@ import { useUserInteractions } from '@/hooks/useUserInteractions'
import { useWallet } from '@/hooks/wallet/useWallet'
import * as interfaces from '@/interfaces'
import { ESendLinkStatus, getParamsFromLink, sendLinksApi, type ClaimLinkData } from '@/services/sendLinks'
import { getInitialsFromName, getTokenDetails, isStableCoin } from '@/utils/general.utils'
import {
getInitialsFromName,
getTokenDetails,
isStableCoin,
getChainName,
getTokenLogo,
getChainLogo,
} from '@/utils/general.utils'
import * as Sentry from '@sentry/nextjs'
import { useQuery } from '@tanstack/react-query'
import type { Hash } from 'viem'
Expand Down Expand Up @@ -129,26 +136,56 @@ export const Claim = ({}) => {

const rewardData = REWARD_TOKENS[claimLinkData.tokenAddress.toLowerCase()]

// determine direction based on user role and status
let direction: TransactionDetails['direction'] = 'send'
if (status === 'completed') {
// if link is claimed, show as send from sender's perspective
direction = 'send'
}

// determine recipient name
const recipientName =
claimLinkData.claim?.recipient?.username ?? claimLinkData.claim?.recipientAddress ?? 'Send via Link'

// find the claimed event for timestamp
const claimedEvent = claimLinkData.events?.find((e) => e.status === 'CLAIMED')

let details: Partial<TransactionDetails> = {
id: claimLinkData.pubKey,
direction,
status,
amount: Number(formatUnits(claimLinkData.amount, tokenDetails?.decimals ?? 6)),
date: new Date(claimLinkData.createdAt),
createdAt: new Date(claimLinkData.createdAt),
claimedAt: claimedEvent ? new Date(claimedEvent.timestamp) : undefined,
tokenSymbol: tokenDetails?.symbol,
initials: getInitialsFromName(claimLinkData.claim?.recipient?.username ?? ''),
tokenAddress: claimLinkData.tokenAddress,
initials: getInitialsFromName(recipientName),
memo: claimLinkData.textContent,
attachmentUrl: claimLinkData.fileUrl,
cancelledDate: status === 'cancelled' ? new Date(claimLinkData.events[0].timestamp) : undefined,
cancelledDate: status === 'cancelled' ? new Date(claimLinkData.events[0]?.timestamp) : undefined,
txHash: claimLinkData.claim?.txHash,
extraDataForDrawer: {
isLinkTransaction: true,
originalType: EHistoryEntryType.SEND_LINK,
originalUserRole: EHistoryUserRole.SENDER,
link: claimLinkData.link,
rewardData,
transactionCardType: 'send',
},
userName:
claimLinkData.claim?.recipient?.username ?? claimLinkData.claim?.recipientAddress ?? 'Send via Link',
userName: recipientName,
fullName: claimLinkData.claim?.recipient?.username ?? recipientName,
sourceView: 'history',
tokenDisplayDetails: tokenDetails
? {
tokenSymbol: tokenDetails.symbol,
chainName: getChainName(claimLinkData.chainId),
tokenIconUrl: getTokenLogo(tokenDetails.symbol),
chainIconUrl: getChainName(claimLinkData.chainId)
? getChainLogo(getChainName(claimLinkData.chainId)!)
: undefined,
}
: undefined,
peanutFeeDetails: {
amountDisplay: '$ 0.00',
},
Expand Down
1 change: 0 additions & 1 deletion src/components/Claim/Link/FlowManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ const FlowManager = ({
}) => {
const viewComponents: _consts.IFlowManagerClaimComponents = {
INITIAL: InitialClaimLinkView,
// todo: @dev note, handle bank claims in links-v2 project
CONFIRM: onchainViews.ConfirmClaimLinkView,
SUCCESS: onchainViews.SuccessClaimLinkView,
}
Expand Down
6 changes: 5 additions & 1 deletion src/components/Claim/Link/SendLinkActionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,11 @@ export default function SendLinkActionList({
const dispatch = useAppDispatch()

const requiresVerification = useMemo(() => {
return claimType === BankClaimType.GuestKycNeeded || claimType === BankClaimType.ReceiverKycNeeded
return (
claimType === BankClaimType.GuestKycNeeded ||
claimType === BankClaimType.ReceiverKycNeeded ||
claimType === BankClaimType.GuestBankClaim
)
}, [claimType])

// filter and sort payment methods based on geolocation
Expand Down
2 changes: 1 addition & 1 deletion src/components/Common/SavedAccountsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export function SavedAccountsMapping({
/>
)}
<div className="absolute -bottom-1 -right-1 flex h-5 w-5 items-center justify-center rounded-full bg-yellow-400 p-1">
<Icon name="bank" className="h-full w-full text-black" />
<Icon size={12} name="bank" className="text-black" />
</div>
</div>
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Global/ErrorAlert/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface ErrorAlertProps {
const ErrorAlert = ({ className, iconSize = 16, description, iconClassName }: ErrorAlertProps) => {
return (
<div className={twMerge('flex items-start justify-center gap-3 text-[12px] font-medium text-error', className)}>
<Icon name="error" size={iconSize} className={twMerge('mt-0.5 min-w-fit', iconClassName)} />
<Icon name="error" size={iconSize} className={twMerge('mt-0.5 min-w-fit text-error', iconClassName)} />
<div>{description}</div>
</div>
)
Expand Down
2 changes: 1 addition & 1 deletion src/components/Global/PeanutActionDetailsCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export default function PeanutActionDetailsCard({
)}
{!isRegionalMethodClaim && (
<div className="absolute -bottom-1 -right-1 flex h-6 w-6 items-center justify-center rounded-full bg-yellow-400 p-1.5">
<Icon name="bank" className="h-full w-full text-black" />
<Icon size={14} name="bank" className="text-black" />
</div>
)}
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/components/Global/WalletNavigation/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ const MobileNav: React.FC<MobileNavProps> = ({ pathName }) => {
{ 'text-primary-1': pathName === '/home' }
)}
>
<NavIcon name="home" size={20} />
<span className="mx-auto mt-1 block text-center text-xs font-medium">Home</span>
<NavIcon name="home" size={25} fill="currentColor" />
<span className="mx-auto block text-center text-xs font-medium">Home</span>
</Link>

{/* QR Button - Main Action */}
Expand Down
3 changes: 2 additions & 1 deletion src/components/Payment/PaymentInfoRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ export const PaymentInfoRow = ({
>
<Icon
name="info"
className="size-3 cursor-pointer"
size={12}
className="cursor-pointer"
onClick={() => setShowMoreInfo(!showMoreInfo)}
/>
{showMoreInfo && (
Expand Down
2 changes: 1 addition & 1 deletion src/components/Send/views/SendRouter.view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export const SendRouterView = () => {
...method,
identifierIcon: (
<div className="flex size-8 min-w-8 items-center justify-center rounded-full bg-black">
<Icon name="bank" size={14} color="white" />
<Icon name="bank" size={14} fill="white" />
</div>
),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,7 @@ export const TransactionDetailsReceipt = ({
) : (
<div className="flex items-center gap-2">
<span>{shortenStringLong(transaction.txHash)}</span>
<CopyToClipboard textToCopy={transaction.txHash} iconSize="4" />
<CopyToClipboard fill="black" textToCopy={transaction.txHash} iconSize="4" />
</div>
)
}
Expand Down
15 changes: 7 additions & 8 deletions src/constants/actionlist.consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,13 @@ export const ACTION_METHODS: PaymentMethod[] = [
icons: [PIX],
soon: false,
},
// todo: @dev @Zishan-7 to enable during deposit v2 integration is done
// {
// id: 'exchange-or-wallet',
// title: 'Exchange or Wallet',
// description: 'Binance, Metamask and more',
// icons: [binanceIcon, TRUST_WALLET_SMALL_LOGO, METAMASK_LOGO],
// soon: false,
// },
{
id: 'exchange-or-wallet',
title: 'Exchange or Wallet',
description: 'Binance, Metamask and more',
icons: [binanceIcon, TRUST_WALLET_SMALL_LOGO, METAMASK_LOGO],
soon: false,
},
]

export const DEVCONNECT_CLAIM_METHODS: PaymentMethod[] = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function RequestPotActionList({
}: RequestPotActionListProps) {
const router = useRouter()
const { user } = useAuth()
const { hasSufficientBalance } = useWallet()
const { hasSufficientBalance, isFetchingBalance } = useWallet()
const { isUserMantecaKycApproved } = useKycStatus()
const { requestType } = useDetermineBankRequestType(recipientUserId ?? '')

Expand All @@ -65,10 +65,12 @@ export function RequestPotActionList({
}, [requestType])

// check if user has enough peanut balance for the entered amount
// only show insufficient balance after balance has loaded to avoid flash
const userHasSufficientPeanutBalance = useMemo(() => {
if (!user || !usdAmount) return false
if (isFetchingBalance) return true // assume sufficient while loading to avoid flash
return hasSufficientBalance(usdAmount)
}, [user, usdAmount, hasSufficientBalance])
}, [user, usdAmount, hasSufficientBalance, isFetchingBalance])

// filter and sort payment methods
const { filteredMethods: sortedMethods, isLoading: isGeoLoading } = useGeoFilteredPaymentOptions({
Expand All @@ -77,7 +79,7 @@ export function RequestPotActionList({
method.soon ||
(method.id === 'bank' && requiresVerification) ||
(['mercadopago', 'pix'].includes(method.id) && !isUserMantecaKycApproved),
methods: ACTION_METHODS,
methods: ACTION_METHODS.filter((method) => method.id !== 'exchange-or-wallet'), // todo: @dev note, remove exchange-or-wallet filter from here in deposit project
})

// handle clicking on a payment method
Expand Down
22 changes: 19 additions & 3 deletions src/features/payments/flows/contribute-pot/useContributePotFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,14 @@ export function useContributePotFlow() {
const { user } = useAuth()
const { createCharge, isCreating: isCreatingCharge } = useChargeManager()
const { recordPayment, isRecording } = usePaymentRecorder()
const { isConnected, address: walletAddress, sendMoney, formattedBalance, hasSufficientBalance } = useWallet()
const {
isConnected,
address: walletAddress,
sendMoney,
formattedBalance,
hasSufficientBalance,
isFetchingBalance,
} = useWallet()

const isLoggedIn = !!user?.user?.userId

Expand Down Expand Up @@ -89,9 +96,18 @@ export function useContributePotFlow() {
}, [amount, hasSufficientBalance])

// check if should show insufficient balance error
// only show after balance has loaded to avoid flash of error on initial render
const isInsufficientBalance = useMemo(() => {
return isLoggedIn && !!amount && !hasEnoughBalance && !isLoading && !isCreatingCharge && !isRecording
}, [isLoggedIn, amount, hasEnoughBalance, isLoading, isCreatingCharge, isRecording])
return (
isLoggedIn &&
!!amount &&
!hasEnoughBalance &&
!isLoading &&
!isCreatingCharge &&
!isRecording &&
!isFetchingBalance
)
}, [isLoggedIn, amount, hasEnoughBalance, isLoading, isCreatingCharge, isRecording, isFetchingBalance])

// calculate default slider value and suggested amount
const sliderDefaults = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { type TRequestChargeResponse, type PaymentCreationResponse } from '@/ser
import { type ParsedURL, type RecipientType } from '@/lib/url-parser/types/payment'

// view states for semantic request flow
export type SemanticRequestFlowView = 'INITIAL' | 'CONFIRM' | 'STATUS'
export type SemanticRequestFlowView = 'INITIAL' | 'CONFIRM' | 'STATUS' | 'RECEIPT'

// recipient info from parsed url
export interface SemanticRequestRecipient {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* - INITIAL: amount/token input
* - CONFIRM: review cross-chain payment details
* - STATUS: success view after payment
* - RECEIPT: shows receipt for already-paid charges
*
* receives pre-parsed url data from wrapper
*/
Expand All @@ -15,6 +16,7 @@ import { SemanticRequestFlowProvider, useSemanticRequestFlowContext } from './Se
import { SemanticRequestInputView } from './views/SemanticRequestInputView'
import { SemanticRequestConfirmView } from './views/SemanticRequestConfirmView'
import { SemanticRequestSuccessView } from './views/SemanticRequestSuccessView'
import { SemanticRequestReceiptView } from './views/SemanticRequestReceiptView'
import { type ParsedURL } from '@/lib/url-parser/types/payment'

// internal component that switches views
Expand All @@ -26,6 +28,8 @@ function SemanticRequestFlowContent() {
return <SemanticRequestConfirmView />
case 'STATUS':
return <SemanticRequestSuccessView />
case 'RECEIPT':
return <SemanticRequestReceiptView />
case 'INITIAL':
default:
return <SemanticRequestInputView />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,10 +323,23 @@ export function useSemanticRequestFlow() {

// fetch charge from url if chargeIdFromUrl is present but charge is not loaded
useEffect(() => {
if (chargeIdFromUrl && !charge && currentView === 'CONFIRM' && !isFetchingCharge) {
if (
chargeIdFromUrl &&
!charge &&
(currentView === 'CONFIRM' || currentView === 'RECEIPT') &&
!isFetchingCharge
) {
fetchCharge(chargeIdFromUrl)
.then((fetchedCharge) => {
setCharge(fetchedCharge)

// check if charge is already paid - if so, switch to receipt view
const isPaid = fetchedCharge.fulfillmentPayment?.status === 'SUCCESSFUL'
if (isPaid && currentView === 'CONFIRM') {
setCurrentView('RECEIPT')
return
}

// set amount from charge if not already set
if (!amount && fetchedCharge.tokenAmount) {
setAmount(fetchedCharge.tokenAmount)
Expand Down Expand Up @@ -358,6 +371,7 @@ export function useSemanticRequestFlow() {
setError,
setSelectedChainID,
setSelectedTokenAddress,
setCurrentView,
])

// call prepareRoute when entering confirm view and charge is ready
Expand Down
Loading
Loading