diff --git a/src/components/AddWithdraw/DynamicBankAccountForm.tsx b/src/components/AddWithdraw/DynamicBankAccountForm.tsx index c0c496946..d7fd6d4fd 100644 --- a/src/components/AddWithdraw/DynamicBankAccountForm.tsx +++ b/src/components/AddWithdraw/DynamicBankAccountForm.tsx @@ -1,5 +1,5 @@ 'use client' -import { forwardRef, useImperativeHandle, useMemo, useState, useEffect } from 'react' +import { forwardRef, useImperativeHandle, useMemo, useState } from 'react' import { useForm, Controller } from 'react-hook-form' import { useAuth } from '@/context/authContext' import { Button } from '@/components/0_Bruddle/Button' @@ -133,7 +133,6 @@ export const DynamicBankAccountForm = forwardRef<{ handleSubmit: () => void }, D if (isIban) { // if BIC field is shown but empty, don't proceed if (showBicField && !bic) { - setIsSubmitting(false) setSubmissionError('BIC is required') return } @@ -148,13 +147,11 @@ export const DynamicBankAccountForm = forwardRef<{ handleSubmit: () => void }, D setValue('bic', autoBic, { shouldValidate: false }) } else { setShowBicField(true) - setIsSubmitting(false) setSubmissionError('BIC is required') return } } catch (error) { setShowBicField(true) - setIsSubmitting(false) setSubmissionError('BIC is required') return } @@ -197,10 +194,10 @@ export const DynamicBankAccountForm = forwardRef<{ handleSubmit: () => void }, D }) if (result.error) { setSubmissionError(result.error) - setIsSubmitting(false) } } catch (error: any) { setSubmissionError(error.message) + } finally { setIsSubmitting(false) } } diff --git a/src/components/Claim/Claim.tsx b/src/components/Claim/Claim.tsx index 90ff30d8c..99bf282ca 100644 --- a/src/components/Claim/Claim.tsx +++ b/src/components/Claim/Claim.tsx @@ -213,8 +213,9 @@ export const Claim = ({}) => { } if (0 < price) setTokenPrice(price) - // perform user related checks only after user is fetched - if (!isFetchingUser) { + // if there is no logged-in user, allow claiming immediately. + // otherwise, perform user-related checks after user fetch completes + if (!user || !isFetchingUser) { if (user && user.user.userId === sendLink.sender?.userId) { setLinkState(_consts.claimLinkStateType.CLAIM_SENDER) } else { diff --git a/src/components/Claim/Link/Initial.view.tsx b/src/components/Claim/Link/Initial.view.tsx index 20fc7cbf2..782352be4 100644 --- a/src/components/Claim/Link/Initial.view.tsx +++ b/src/components/Claim/Link/Initial.view.tsx @@ -110,6 +110,14 @@ export const InitialClaimLinkView = (props: IClaimScreenProps) => { const queryClient = useQueryClient() const searchParams = useSearchParams() const prevRecipientType = useRef(null) + const prevUser = useRef(user) + + useEffect(() => { + if (!prevUser.current && user) { + resetClaimBankFlow() + } + prevUser.current = user + }, [user, resetClaimBankFlow]) const resetSelectedToken = useCallback(() => { if (isPeanutWallet) { @@ -773,7 +781,7 @@ export const InitialClaimLinkView = (props: IClaimScreenProps) => { /> setShowVerificationModal(false)} description="The sender isn't verified, so please create an account and verify your identity to have the funds deposited to your bank." /> diff --git a/src/components/Claim/Link/views/Confirm.bank-claim.view.tsx b/src/components/Claim/Link/views/Confirm.bank-claim.view.tsx index b5112210d..9c7fddeac 100644 --- a/src/components/Claim/Link/views/Confirm.bank-claim.view.tsx +++ b/src/components/Claim/Link/views/Confirm.bank-claim.view.tsx @@ -13,6 +13,8 @@ import { ClaimLinkData } from '@/services/sendLinks' import { formatUnits } from 'viem' import ExchangeRate from '@/components/ExchangeRate' import { AccountType } from '@/interfaces' +import { useCurrency } from '@/hooks/useCurrency' +import { getCurrencySymbol } from '@/utils/bridge.utils' interface ConfirmBankClaimViewProps { onConfirm: () => void @@ -52,6 +54,43 @@ export function ConfirmBankClaimView({ return countryCodeMap[bankDetails?.country?.toUpperCase()] ?? bankDetails.country.toUpperCase() }, [bankDetails.country]) + // amount in USD from claim link data + const usdAmount = useMemo( + () => formatUnits(claimLinkData?.amount ?? 0, claimLinkData?.tokenDecimals) || '0.00', + [claimLinkData] + ) + + // determine display currency based on account type + const currencyCode = useMemo(() => { + if (accountType === AccountType.CLABE) return 'MXN' + if (accountType === AccountType.US) return 'USD' + return 'EUR' + }, [accountType]) + + // fetch exchange rate and symbol (USD -> local currency) + const { symbol: resolvedSymbol, price, isLoading: isLoadingCurrency } = useCurrency(currencyCode) + + // fallback if conversion fails + const failedConversion = useMemo(() => { + return currencyCode !== 'USD' && !isLoadingCurrency && (!price || isNaN(price)) + }, [currencyCode, isLoadingCurrency, price]) + + // display amount in local currency + const displayAmount = useMemo(() => { + if (currencyCode === 'USD') return usdAmount + if (isLoadingCurrency) return '-' + if (!price || isNaN(price)) return usdAmount + const converted = (Number(usdAmount) * price).toFixed(2) + return converted + }, [price, usdAmount, currencyCode, isLoadingCurrency]) + + const displaySymbol = useMemo(() => { + if (currencyCode === 'USD') return '$' + // fallback to $ if conversion fails + if (failedConversion) return '$' + return resolvedSymbol ?? getCurrencySymbol(currencyCode) + }, [currencyCode, resolvedSymbol, failedConversion]) + return (
@@ -64,8 +103,10 @@ export function ConfirmBankClaimView({ transactionType="CLAIM_LINK_BANK_ACCOUNT" recipientType="BANK_ACCOUNT" recipientName={bankDetails.country} - amount={formatUnits(claimLinkData?.amount ?? 0, claimLinkData?.tokenDecimals) || '0.00'} + amount={displayAmount} tokenSymbol={claimLinkData.tokenSymbol} + currencySymbol={displaySymbol} + isLoading={isLoadingCurrency} /> diff --git a/src/components/Global/PeanutActionDetailsCard/index.tsx b/src/components/Global/PeanutActionDetailsCard/index.tsx index bc2741b00..51a5b9240 100644 --- a/src/components/Global/PeanutActionDetailsCard/index.tsx +++ b/src/components/Global/PeanutActionDetailsCard/index.tsx @@ -10,6 +10,7 @@ import Card from '../Card' import { Icon, IconName } from '../Icons/Icon' import RouteExpiryTimer from '../RouteExpiryTimer' import Image from 'next/image' +import Loading from '../Loading' export type PeanutActionDetailsCardTransactionType = | 'REQUEST' @@ -45,6 +46,7 @@ export interface PeanutActionDetailsCardProps { onTimerExpired?: () => void disableTimerRefetch?: boolean timerError?: string | null + isLoading?: boolean } export default function PeanutActionDetailsCard({ @@ -67,6 +69,7 @@ export default function PeanutActionDetailsCard({ timerError = null, countryCodeForFlag, currencySymbol, + isLoading = false, }: PeanutActionDetailsCardProps) { const renderRecipient = () => { if (recipientType === 'ADDRESS') return printableAddress(recipientName) @@ -196,26 +199,32 @@ export default function PeanutActionDetailsCard({
{getTitle()} -

- {(transactionType === 'ADD_MONEY' || isAddBankAccount) && currencySymbol - ? `${currencySymbol}` - : tokenSymbol.toLowerCase() === PEANUT_WALLET_TOKEN_SYMBOL.toLowerCase() || - (transactionType === 'CLAIM_LINK_BANK_ACCOUNT' && viewType === 'SUCCESS') - ? '$' - : ''} - {amount} - - {tokenSymbol.toLowerCase() !== PEANUT_WALLET_TOKEN_SYMBOL.toLowerCase() && - transactionType !== 'ADD_MONEY' && - !(transactionType === 'CLAIM_LINK_BANK_ACCOUNT' && viewType === 'SUCCESS') && - ` ${ - tokenSymbol.toLowerCase() === 'pnt' - ? Number(amount) === 1 - ? 'Beer' - : 'Beers' - : tokenSymbol - }`} -

+ {isLoading ? ( + + ) : ( +

+ {(transactionType === 'ADD_MONEY' || isAddBankAccount || isClaimLinkBankAccount) && + currencySymbol + ? `${currencySymbol}` + : tokenSymbol.toLowerCase() === PEANUT_WALLET_TOKEN_SYMBOL.toLowerCase() || + (transactionType === 'CLAIM_LINK_BANK_ACCOUNT' && viewType === 'SUCCESS') + ? '$' + : ''} + {amount} + + {tokenSymbol.toLowerCase() !== PEANUT_WALLET_TOKEN_SYMBOL.toLowerCase() && + transactionType !== 'ADD_MONEY' && + !isClaimLinkBankAccount && + !(transactionType === 'CLAIM_LINK_BANK_ACCOUNT' && viewType === 'SUCCESS') && + ` ${ + tokenSymbol.toLowerCase() === 'pnt' + ? Number(amount) === 1 + ? 'Beer' + : 'Beers' + : tokenSymbol + }`} +

+ )} {showTimer && ( diff --git a/src/components/Setup/Views/Welcome.tsx b/src/components/Setup/Views/Welcome.tsx index d0a413be5..fdd64f953 100644 --- a/src/components/Setup/Views/Welcome.tsx +++ b/src/components/Setup/Views/Welcome.tsx @@ -18,7 +18,15 @@ const WelcomeStep = () => { const toast = useToast() useEffect(() => { - if (!!user) push('/home') + if (!!user) { + const localStorageRedirect = getFromLocalStorage('redirect') + if (localStorageRedirect) { + localStorage.removeItem('redirect') + push(localStorageRedirect) + } else { + push('/home') + } + } }, [user, push]) const handleError = (error: any) => { @@ -45,17 +53,9 @@ const WelcomeStep = () => { className="h-11" variant="primary-soft" onClick={() => { - handleLogin() - .then(() => { - const localStorageRedirect = getFromLocalStorage('redirect') - if (localStorageRedirect) { - localStorage.removeItem('redirect') // Clear the redirect URL - push(localStorageRedirect) - } - }) - .catch((e) => { - handleError(e) - }) + handleLogin().catch((e) => { + handleError(e) + }) }} > Log In diff --git a/src/components/TransactionDetails/TransactionDetailsReceipt.tsx b/src/components/TransactionDetails/TransactionDetailsReceipt.tsx index ca1a36f3c..eb5802d20 100644 --- a/src/components/TransactionDetails/TransactionDetailsReceipt.tsx +++ b/src/components/TransactionDetails/TransactionDetailsReceipt.tsx @@ -97,7 +97,15 @@ export const TransactionDetailsReceipt = ({ return { createdAt: !!transaction.createdAt, to: transaction.direction === 'claim_external', - tokenAndNetwork: !!(transaction.tokenDisplayDetails && transaction.sourceView === 'history'), + tokenAndNetwork: !!( + transaction.tokenDisplayDetails && + transaction.sourceView === 'history' && + // hide token and network for send links in acitvity drawer for sender + !( + transaction.extraDataForDrawer?.originalType === EHistoryEntryType.SEND_LINK && + transaction.extraDataForDrawer?.originalUserRole === EHistoryUserRole.SENDER + ) + ), txId: !!transaction.txHash, cancelled: !!(transaction.status === 'cancelled' && transaction.cancelledDate), claimed: !!(transaction.status === 'completed' && transaction.claimedAt), diff --git a/src/hooks/useCurrency.ts b/src/hooks/useCurrency.ts index 813fc69c7..4a50aad03 100644 --- a/src/hooks/useCurrency.ts +++ b/src/hooks/useCurrency.ts @@ -12,32 +12,44 @@ export const useCurrency = (currencyCode: string | null) => { const [code, setCode] = useState(currencyCode?.toUpperCase() ?? null) const [symbol, setSymbol] = useState(null) const [price, setPrice] = useState(null) + const [isLoading, setIsLoading] = useState(false) useEffect(() => { - if (!code) return + if (!code) { + setIsLoading(false) + return + } if (code === 'USD') { setSymbol('$') setPrice(1) + setIsLoading(false) return } if (!Object.keys(SIMBOLS_BY_CURRENCY_CODE).includes(code)) { setCode(null) + setIsLoading(false) return } + setIsLoading(true) getCurrencyPrice(code) .then((price) => { setSymbol(SIMBOLS_BY_CURRENCY_CODE[code]) setPrice(price) + setIsLoading(false) + }) + .catch((err) => { + console.error(err) + setIsLoading(false) }) - .catch(console.error) }, [code]) return { code, symbol, price, + isLoading, } }