From 8aaff71574d72a6a0537496d8409f6bddbd539c0 Mon Sep 17 00:00:00 2001 From: Zishan Mohd Date: Thu, 11 Sep 2025 22:29:29 +0530 Subject: [PATCH 1/5] Extract exchange rate widget to a separate component --- .../Global/ExchangeRateWidget/index.tsx | 228 ++++++++++++++++++ src/components/LandingPage/noFees.tsx | 224 +---------------- 2 files changed, 231 insertions(+), 221 deletions(-) create mode 100644 src/components/Global/ExchangeRateWidget/index.tsx diff --git a/src/components/Global/ExchangeRateWidget/index.tsx b/src/components/Global/ExchangeRateWidget/index.tsx new file mode 100644 index 000000000..8efb56a60 --- /dev/null +++ b/src/components/Global/ExchangeRateWidget/index.tsx @@ -0,0 +1,228 @@ +import CurrencySelect from '@/components/LandingPage/CurrencySelect' +import countryCurrencyMappings from '@/constants/countryCurrencyMapping' +import { useDebounce } from '@/hooks/useDebounce' +import { useExchangeRate } from '@/hooks/useExchangeRate' +import Image from 'next/image' +import { useRouter, useSearchParams } from 'next/navigation' +import { useCallback, useEffect, useMemo } from 'react' +import { Icon } from '../Icons/Icon' +import { Button } from '@/components/0_Bruddle' + +const ExchangeRateWiget = () => { + const searchParams = useSearchParams() + const router = useRouter() + + // Get values from URL or use defaults + const sourceCurrency = searchParams.get('from') || 'USD' + const destinationCurrency = searchParams.get('to') || 'EUR' + const urlSourceAmount = searchParams.get('amount') ? parseFloat(searchParams.get('amount')!) : 10 + + // Exchange rate hook handles all the conversion logic + const { + sourceAmount, + destinationAmount, + exchangeRate, + isLoading, + isError, + handleSourceAmountChange, + handleDestinationAmountChange, + getDestinationDisplayValue, + } = useExchangeRate({ + sourceCurrency, + destinationCurrency, + initialSourceAmount: urlSourceAmount, + }) + + const debouncedSourceAmount = useDebounce(sourceAmount, 500) + + // Function to update URL parameters + const updateUrlParams = useCallback( + (params: { from?: string; to?: string; amount?: number }) => { + const newSearchParams = new URLSearchParams(searchParams.toString()) + + if (params.from) newSearchParams.set('from', params.from) + if (params.to) newSearchParams.set('to', params.to) + if (params.amount !== undefined) newSearchParams.set('amount', params.amount.toString()) + + router.replace(`?${newSearchParams.toString()}`, { scroll: false }) + }, + [searchParams, router] + ) + + // Setter functions that update URL + const setSourceCurrency = useCallback( + (currency: string) => { + updateUrlParams({ from: currency }) + }, + [updateUrlParams] + ) + + const setDestinationCurrency = useCallback( + (currency: string) => { + updateUrlParams({ to: currency }) + }, + [updateUrlParams] + ) + + // Update URL when source amount changes (only for valid numbers) + useEffect(() => { + if (typeof debouncedSourceAmount === 'number' && debouncedSourceAmount !== urlSourceAmount) { + updateUrlParams({ amount: debouncedSourceAmount }) + } + }, [debouncedSourceAmount, urlSourceAmount, updateUrlParams]) + + const sourceCurrencyFlag = useMemo( + () => countryCurrencyMappings.find((currency) => currency.currencyCode === sourceCurrency)?.flagCode, + [sourceCurrency] + ) + + const destinationCurrencyFlag = useMemo( + () => countryCurrencyMappings.find((currency) => currency.currencyCode === destinationCurrency)?.flagCode, + [destinationCurrency] + ) + + // Determine delivery time text based on destination currency + const deliveryTimeText = useMemo(() => { + return destinationCurrency === 'USD' + ? 'Should arrive in hours. Estimate.' + : 'Should arrive in minutes. Estimate.' + }, [destinationCurrency]) + + return ( +
+
+

You Send

+
+ {isLoading ? ( +
+
+
+ ) : ( + { + const inputValue = e.target.value + if (inputValue === '') { + handleSourceAmountChange('') + } else { + const value = parseFloat(inputValue) + handleSourceAmountChange(isNaN(value) ? '' : value) + } + }} + type="number" + className="w-full bg-transparent outline-none" + /> + )} + + {`${sourceCurrencyFlag} + {sourceCurrency} + + } + /> +
+
+ +
+

Recipient Gets

+
+ {isLoading ? ( +
+
+
+ ) : ( + { + const inputValue = e.target.value + if (inputValue === '') { + handleDestinationAmountChange('', '') + } else { + const value = parseFloat(inputValue) + handleDestinationAmountChange(inputValue, isNaN(value) ? '' : value) + } + }} + type="number" + className="w-full bg-transparent outline-none" + /> + )} + + {`${destinationCurrencyFlag} + {destinationCurrency} + + } + /> +
+
+ +
+ {isLoading ? ( +
+ ) : isError ? ( + Rate currently unavailable + ) : ( + <> + 1 {sourceCurrency} = {exchangeRate.toFixed(4)} {destinationCurrency} + + )} +
+ + {typeof destinationAmount === 'number' && destinationAmount > 0 && ( +
+
+

Bank fee

+

Free!

+
+ +
+

Peanut fee

+

Free!

+
+
+ )} + + + + {typeof destinationAmount === 'number' && destinationAmount > 0 && ( +
+ +

{deliveryTimeText}

+
+ )} +
+ ) +} + +export default ExchangeRateWiget diff --git a/src/components/LandingPage/noFees.tsx b/src/components/LandingPage/noFees.tsx index 2744e27ae..8d3a5e9dc 100644 --- a/src/components/LandingPage/noFees.tsx +++ b/src/components/LandingPage/noFees.tsx @@ -1,84 +1,16 @@ 'use client' -import React, { useEffect, useState, useCallback, useMemo } from 'react' +import React, { useEffect, useState } from 'react' import { motion } from 'framer-motion' import borderCloud from '@/assets/illustrations/border-cloud.svg' import noHiddenFees from '@/assets/illustrations/no-hidden-fees.svg' import { Star } from '@/assets' import Image from 'next/image' -import { Button } from '../0_Bruddle' -import CurrencySelect from './CurrencySelect' -import { Icon } from '../Global/Icons/Icon' -import { useDebounce } from '@/hooks/useDebounce' -import { useExchangeRate } from '@/hooks/useExchangeRate' -import { useSearchParams, useRouter } from 'next/navigation' -import countryCurrencyMappings from '@/constants/countryCurrencyMapping' +import ExchangeRateWiget from '../Global/ExchangeRateWidget' export function NoFees() { - const searchParams = useSearchParams() - const router = useRouter() - const [screenWidth, setScreenWidth] = useState(typeof window !== 'undefined' ? window.innerWidth : 1200) - // Get values from URL or use defaults - const sourceCurrency = searchParams.get('from') || 'USD' - const destinationCurrency = searchParams.get('to') || 'EUR' - const urlSourceAmount = searchParams.get('amount') ? parseFloat(searchParams.get('amount')!) : 10 - - // Exchange rate hook handles all the conversion logic - const { - sourceAmount, - destinationAmount, - exchangeRate, - isLoading, - isError, - handleSourceAmountChange, - handleDestinationAmountChange, - getDestinationDisplayValue, - } = useExchangeRate({ - sourceCurrency, - destinationCurrency, - initialSourceAmount: urlSourceAmount, - }) - - const debouncedSourceAmount = useDebounce(sourceAmount, 500) - - // Function to update URL parameters - const updateUrlParams = useCallback( - (params: { from?: string; to?: string; amount?: number }) => { - const newSearchParams = new URLSearchParams(searchParams.toString()) - - if (params.from) newSearchParams.set('from', params.from) - if (params.to) newSearchParams.set('to', params.to) - if (params.amount !== undefined) newSearchParams.set('amount', params.amount.toString()) - - router.replace(`?${newSearchParams.toString()}`, { scroll: false }) - }, - [searchParams, router] - ) - - // Setter functions that update URL - const setSourceCurrency = useCallback( - (currency: string) => { - updateUrlParams({ from: currency }) - }, - [updateUrlParams] - ) - - const setDestinationCurrency = useCallback( - (currency: string) => { - updateUrlParams({ to: currency }) - }, - [updateUrlParams] - ) - - // Update URL when source amount changes (only for valid numbers) - useEffect(() => { - if (typeof debouncedSourceAmount === 'number' && debouncedSourceAmount !== urlSourceAmount) { - updateUrlParams({ amount: debouncedSourceAmount }) - } - }, [debouncedSourceAmount, urlSourceAmount, updateUrlParams]) - useEffect(() => { const handleResize = () => { setScreenWidth(window.innerWidth) @@ -104,23 +36,6 @@ export function NoFees() { } } - const sourceCurrencyFlag = useMemo( - () => countryCurrencyMappings.find((currency) => currency.currencyCode === sourceCurrency)?.flagCode, - [sourceCurrency] - ) - - const destinationCurrencyFlag = useMemo( - () => countryCurrencyMappings.find((currency) => currency.currencyCode === destinationCurrency)?.flagCode, - [destinationCurrency] - ) - - // Determine delivery time text based on destination currency - const deliveryTimeText = useMemo(() => { - return destinationCurrency === 'USD' - ? 'Should arrive in hours. Estimate.' - : 'Should arrive in minutes. Estimate.' - }, [destinationCurrency]) - return (
@@ -207,140 +122,7 @@ export function NoFees() { />
-
-
-

You Send

-
- {isLoading ? ( -
-
-
- ) : ( - { - const inputValue = e.target.value - if (inputValue === '') { - handleSourceAmountChange('') - } else { - const value = parseFloat(inputValue) - handleSourceAmountChange(isNaN(value) ? '' : value) - } - }} - type="number" - className="w-full bg-transparent outline-none" - /> - )} - - {`${sourceCurrencyFlag} - {sourceCurrency} - - } - /> -
-
- -
-

Recipient Gets

-
- {isLoading ? ( -
-
-
- ) : ( - { - const inputValue = e.target.value - if (inputValue === '') { - handleDestinationAmountChange('', '') - } else { - const value = parseFloat(inputValue) - handleDestinationAmountChange(inputValue, isNaN(value) ? '' : value) - } - }} - type="number" - className="w-full bg-transparent outline-none" - /> - )} - - {`${destinationCurrencyFlag} - {destinationCurrency}{' '} - - - } - /> -
-
- -
- {isLoading ? ( -
- ) : isError ? ( - Rate currently unavailable - ) : ( - <> - 1 {sourceCurrency} = {exchangeRate.toFixed(4)} {destinationCurrency} - - )} -
- - {typeof destinationAmount === 'number' && destinationAmount > 0 && ( -
-
-

Bank fee

-

Free!

-
- -
-

Peanut fee

-

Free!

-
-
- )} - - - - {typeof destinationAmount === 'number' && destinationAmount > 0 && ( -
- -

{deliveryTimeText}

-
- )} -
+
) From 79d14a8b7f88bfa2ecc09fa5eed6ef2809feda67 Mon Sep 17 00:00:00 2001 From: Zishan Mohd Date: Thu, 11 Sep 2025 22:47:19 +0530 Subject: [PATCH 2/5] Add exchange rate widget in settings --- .../profile/exchange-rate/page.tsx | 23 +++++++++++++++++++ .../Global/ExchangeRateWidget/index.tsx | 18 ++++++++++----- src/components/LandingPage/noFees.tsx | 8 ++++++- src/components/Profile/index.tsx | 3 +-- 4 files changed, 43 insertions(+), 9 deletions(-) create mode 100644 src/app/(mobile-ui)/profile/exchange-rate/page.tsx diff --git a/src/app/(mobile-ui)/profile/exchange-rate/page.tsx b/src/app/(mobile-ui)/profile/exchange-rate/page.tsx new file mode 100644 index 000000000..e54dc5ac4 --- /dev/null +++ b/src/app/(mobile-ui)/profile/exchange-rate/page.tsx @@ -0,0 +1,23 @@ +'use client' + +import PageContainer from '@/components/0_Bruddle/PageContainer' +import ExchangeRateWiget from '@/components/Global/ExchangeRateWidget' +import NavHeader from '@/components/Global/NavHeader' +import { useRouter } from 'next/navigation' + +export default function ExchangeRatePage() { + const router = useRouter() + + return ( + + +
+ router.push('/add-money')} + /> +
+
+ ) +} diff --git a/src/components/Global/ExchangeRateWidget/index.tsx b/src/components/Global/ExchangeRateWidget/index.tsx index 8efb56a60..6cb3fb73a 100644 --- a/src/components/Global/ExchangeRateWidget/index.tsx +++ b/src/components/Global/ExchangeRateWidget/index.tsx @@ -4,11 +4,17 @@ import { useDebounce } from '@/hooks/useDebounce' import { useExchangeRate } from '@/hooks/useExchangeRate' import Image from 'next/image' import { useRouter, useSearchParams } from 'next/navigation' -import { useCallback, useEffect, useMemo } from 'react' -import { Icon } from '../Icons/Icon' +import { FC, useCallback, useEffect, useMemo } from 'react' +import { Icon, IconName } from '../Icons/Icon' import { Button } from '@/components/0_Bruddle' -const ExchangeRateWiget = () => { +interface IExchangeRateWidgetProps { + ctaLabel: string + ctaIcon: IconName + ctaAction: () => void +} + +const ExchangeRateWiget: FC = ({ ctaLabel, ctaIcon, ctaAction }) => { const searchParams = useSearchParams() const router = useRouter() @@ -206,13 +212,13 @@ const ExchangeRateWiget = () => { )} {typeof destinationAmount === 'number' && destinationAmount > 0 && ( diff --git a/src/components/LandingPage/noFees.tsx b/src/components/LandingPage/noFees.tsx index 8d3a5e9dc..725f097dc 100644 --- a/src/components/LandingPage/noFees.tsx +++ b/src/components/LandingPage/noFees.tsx @@ -7,9 +7,11 @@ import noHiddenFees from '@/assets/illustrations/no-hidden-fees.svg' import { Star } from '@/assets' import Image from 'next/image' import ExchangeRateWiget from '../Global/ExchangeRateWidget' +import { useRouter } from 'next/navigation' export function NoFees() { const [screenWidth, setScreenWidth] = useState(typeof window !== 'undefined' ? window.innerWidth : 1200) + const router = useRouter() useEffect(() => { const handleResize = () => { @@ -122,7 +124,11 @@ export function NoFees() { />
- + router.push('/setup')} + />
) diff --git a/src/components/Profile/index.tsx b/src/components/Profile/index.tsx index 9f9bafe80..d19efac72 100644 --- a/src/components/Profile/index.tsx +++ b/src/components/Profile/index.tsx @@ -92,9 +92,8 @@ export const Profile = () => {
{/* Logout Button */} From a1c9ab0859a43f52947997988dd31746639e7579 Mon Sep 17 00:00:00 2001 From: Zishan Mohd Date: Thu, 11 Sep 2025 22:48:51 +0530 Subject: [PATCH 3/5] disable referrals button --- src/components/Profile/index.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/Profile/index.tsx b/src/components/Profile/index.tsx index d19efac72..c988eef83 100644 --- a/src/components/Profile/index.tsx +++ b/src/components/Profile/index.tsx @@ -44,13 +44,14 @@ export const Profile = () => {
{/* Menu Item - Invite Entry */} - + /> */} {/* Menu Items - First Group */}
@@ -91,7 +92,7 @@ export const Profile = () => { From 68bcbe5252031433dabad5f1bb20c7bae063a784 Mon Sep 17 00:00:00 2001 From: Zishan Mohd Date: Thu, 11 Sep 2025 22:56:36 +0530 Subject: [PATCH 4/5] fix: add previous action --- src/app/(mobile-ui)/profile/exchange-rate/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/(mobile-ui)/profile/exchange-rate/page.tsx b/src/app/(mobile-ui)/profile/exchange-rate/page.tsx index e54dc5ac4..8c0c82f7b 100644 --- a/src/app/(mobile-ui)/profile/exchange-rate/page.tsx +++ b/src/app/(mobile-ui)/profile/exchange-rate/page.tsx @@ -10,7 +10,7 @@ export default function ExchangeRatePage() { return ( - + router.replace('/profile')} />
Date: Thu, 11 Sep 2025 23:16:50 +0530 Subject: [PATCH 5/5] fix typo and apply coderabbit suggestions --- src/app/(mobile-ui)/profile/exchange-rate/page.tsx | 4 ++-- src/components/Global/ExchangeRateWidget/index.tsx | 10 ++++++---- src/components/LandingPage/noFees.tsx | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/app/(mobile-ui)/profile/exchange-rate/page.tsx b/src/app/(mobile-ui)/profile/exchange-rate/page.tsx index 8c0c82f7b..f1ec7be9d 100644 --- a/src/app/(mobile-ui)/profile/exchange-rate/page.tsx +++ b/src/app/(mobile-ui)/profile/exchange-rate/page.tsx @@ -1,7 +1,7 @@ 'use client' import PageContainer from '@/components/0_Bruddle/PageContainer' -import ExchangeRateWiget from '@/components/Global/ExchangeRateWidget' +import ExchangeRateWidget from '@/components/Global/ExchangeRateWidget' import NavHeader from '@/components/Global/NavHeader' import { useRouter } from 'next/navigation' @@ -12,7 +12,7 @@ export default function ExchangeRatePage() { router.replace('/profile')} />
- router.push('/add-money')} diff --git a/src/components/Global/ExchangeRateWidget/index.tsx b/src/components/Global/ExchangeRateWidget/index.tsx index 6cb3fb73a..37d907c6b 100644 --- a/src/components/Global/ExchangeRateWidget/index.tsx +++ b/src/components/Global/ExchangeRateWidget/index.tsx @@ -1,5 +1,5 @@ import CurrencySelect from '@/components/LandingPage/CurrencySelect' -import countryCurrencyMappings from '@/constants/countryCurrencyMapping' +import { countryCurrencyMappings } from '@/constants/countryCurrencyMapping' import { useDebounce } from '@/hooks/useDebounce' import { useExchangeRate } from '@/hooks/useExchangeRate' import Image from 'next/image' @@ -14,14 +14,16 @@ interface IExchangeRateWidgetProps { ctaAction: () => void } -const ExchangeRateWiget: FC = ({ ctaLabel, ctaIcon, ctaAction }) => { +const ExchangeRateWidget: FC = ({ ctaLabel, ctaIcon, ctaAction }) => { const searchParams = useSearchParams() const router = useRouter() // Get values from URL or use defaults const sourceCurrency = searchParams.get('from') || 'USD' const destinationCurrency = searchParams.get('to') || 'EUR' - const urlSourceAmount = searchParams.get('amount') ? parseFloat(searchParams.get('amount')!) : 10 + const rawAmount = searchParams.get('amount') + const parsedAmount = rawAmount !== null ? Number(rawAmount) : 10 + const urlSourceAmount = Number.isFinite(parsedAmount) && parsedAmount > 0 ? parsedAmount : 10 // Exchange rate hook handles all the conversion logic const { @@ -231,4 +233,4 @@ const ExchangeRateWiget: FC = ({ ctaLabel, ctaIcon, ct ) } -export default ExchangeRateWiget +export default ExchangeRateWidget diff --git a/src/components/LandingPage/noFees.tsx b/src/components/LandingPage/noFees.tsx index 725f097dc..6789b4083 100644 --- a/src/components/LandingPage/noFees.tsx +++ b/src/components/LandingPage/noFees.tsx @@ -6,7 +6,7 @@ import borderCloud from '@/assets/illustrations/border-cloud.svg' import noHiddenFees from '@/assets/illustrations/no-hidden-fees.svg' import { Star } from '@/assets' import Image from 'next/image' -import ExchangeRateWiget from '../Global/ExchangeRateWidget' +import ExchangeRateWidget from '../Global/ExchangeRateWidget' import { useRouter } from 'next/navigation' export function NoFees() { @@ -124,7 +124,7 @@ export function NoFees() { />
- router.push('/setup')}