From 504349234610b45f4f5ca48080fec61950d6a892 Mon Sep 17 00:00:00 2001 From: Tyler Diorio <109099227+TylerDiorio@users.noreply.github.com> Date: Thu, 13 Nov 2025 10:18:54 -0800 Subject: [PATCH 1/3] Respect CurrencyToggle within ContributeToFundraiseModal --- components/modals/BalanceInfo.tsx | 27 ++- .../modals/ContributeToFundraiseModal.tsx | 160 ++++++++++++------ 2 files changed, 128 insertions(+), 59 deletions(-) diff --git a/components/modals/BalanceInfo.tsx b/components/modals/BalanceInfo.tsx index 7d463466c..fd5a5cbae 100644 --- a/components/modals/BalanceInfo.tsx +++ b/components/modals/BalanceInfo.tsx @@ -1,6 +1,9 @@ 'use client'; import { useUser } from '@/contexts/UserContext'; +import { useCurrencyPreference } from '@/contexts/CurrencyPreferenceContext'; +import { useExchangeRate } from '@/contexts/ExchangeRateContext'; +import { formatCurrency } from '@/utils/currency'; interface BalanceInfoProps { amount: number; @@ -14,19 +17,37 @@ export function BalanceInfo({ includeLockedBalance = false, }: BalanceInfoProps) { const { user } = useUser(); + const { showUSD } = useCurrencyPreference(); + const { exchangeRate } = useExchangeRate(); const userBalance = user?.balance || 0; const lockedBalance = user?.lockedBalance || 0; const totalAvailableBalance = includeLockedBalance ? userBalance + lockedBalance : userBalance; + const useUsd = showUSD && exchangeRate > 0; + const primary = formatCurrency({ amount: totalAvailableBalance, showUSD: useUsd, exchangeRate }); + const secondary = + exchangeRate > 0 + ? formatCurrency({ amount: totalAvailableBalance, showUSD: !useUsd, exchangeRate }) + : undefined; + + const deficit = amount - totalAvailableBalance; + const deficitFormatted = + deficit > 0 ? formatCurrency({ amount: deficit, showUSD: useUsd, exchangeRate }) : undefined; + return (
Current RSC Balance: - {totalAvailableBalance.toLocaleString()} RSC + {useUsd ? `${primary} USD` : `${primary} RSC`}
- {showWarning && ( + {secondary && ( +
+ ≈ {useUsd ? `${secondary} RSC` : `${secondary} USD`} +
+ )} + {showWarning && deficitFormatted && (
- {`You need ${(amount - totalAvailableBalance).toLocaleString()} RSC more for this contribution`} + {`You need ${useUsd ? `${deficitFormatted} USD` : `${deficitFormatted} RSC`} more for this contribution`}
)}
diff --git a/components/modals/ContributeToFundraiseModal.tsx b/components/modals/ContributeToFundraiseModal.tsx index fe8f0c8d2..693f27aca 100644 --- a/components/modals/ContributeToFundraiseModal.tsx +++ b/components/modals/ContributeToFundraiseModal.tsx @@ -12,6 +12,8 @@ import { toast } from 'react-hot-toast'; import { FundraiseService } from '@/services/fundraise.service'; import { useUser } from '@/contexts/UserContext'; import { useExchangeRate } from '@/contexts/ExchangeRateContext'; +import { useCurrencyPreference } from '@/contexts/CurrencyPreferenceContext'; +import { formatCurrency } from '@/utils/currency'; import { Fundraise } from '@/types/funding'; interface ContributeToFundraiseModalProps { @@ -26,10 +28,14 @@ const CurrencyInput = ({ value, onChange, error, + currencyLabel, + helperText, }: { value: string; onChange: (e: React.ChangeEvent) => void; error?: string; + currencyLabel: string; + helperText?: string; }) => { return (
@@ -45,9 +51,10 @@ const CurrencyInput = ({ className={`w-full text-left h-12 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 ${error ? 'border-red-500' : ''}`} rightElement={
- RSC + {currencyLabel}
} + helperText={helperText} /> {error &&

{error}

}
@@ -59,48 +66,60 @@ const FeeBreakdown = ({ contributionAmount, platformFee, totalAmount, + showUSD, + exchangeRate, }: { contributionAmount: number; platformFee: number; totalAmount: number; -}) => ( -
-
- Your contribution: - {contributionAmount.toLocaleString()} RSC -
+ showUSD: boolean; + exchangeRate: number; +}) => { + const useUsd = showUSD && exchangeRate > 0; + const formatAmt = (v: number) => { + const s = formatCurrency({ amount: v, showUSD: useUsd, exchangeRate }); + return useUsd ? `${s} USD` : `${s} RSC`; + }; -
-
-
- Platform fees (9%) - -
- - - -
-
+ return ( +
+
+ Your contribution: + {formatAmt(contributionAmount)} +
+ +
+
+
+ Platform fees (9%) + +
+ + + +
+
+
+ + {formatAmt(platformFee)}
- + {platformFee.toLocaleString()} RSC
-
-
+
-
- Total amount: - {totalAmount.toLocaleString()} RSC +
+ Total amount: + {formatAmt(totalAmount)} +
-
-); + ); +}; const ModalHeader = ({ title, @@ -140,7 +159,9 @@ export function ContributeToFundraiseModal({ fundraise, }: ContributeToFundraiseModalProps) { const { user } = useUser(); - const [inputAmount, setInputAmount] = useState(100); + const { showUSD } = useCurrencyPreference(); + const { exchangeRate } = useExchangeRate(); + const [inputAmountRsc, setInputAmountRsc] = useState(100); const [isContributing, setIsContributing] = useState(false); const [error, setError] = useState(null); const [isSuccess, setIsSuccess] = useState(false); @@ -151,36 +172,59 @@ export function ContributeToFundraiseModal({ const lockedBalance = user?.lockedBalance || 0; const totalAvailableBalance = userBalance + lockedBalance; + const useUsd = showUSD && exchangeRate > 0; + // Utility functions const handleAmountChange = (e: React.ChangeEvent) => { const rawValue = e.target.value.replace(/[^0-9.]/g, ''); const numValue = parseFloat(rawValue); - if (!isNaN(numValue)) { - setInputAmount(numValue); + if (isNaN(numValue)) { + setInputAmountRsc(0); + setAmountError('Please enter a valid amount'); + return; + } - // Validate minimum amount - if (numValue < 10) { - setAmountError('Minimum contribution amount is 10 RSC'); - } else { - setAmountError(undefined); - } + // Convert from preferred currency to RSC + const rsc = useUsd ? numValue / exchangeRate : numValue; + setInputAmountRsc(rsc); + + // Validate minimum amount (10 RSC) + if (rsc < 10) { + const minUsd = 10 * exchangeRate; + setAmountError( + useUsd + ? `Minimum contribution amount is $${minUsd.toFixed(2)} USD` + : 'Minimum contribution amount is 10 RSC' + ); } else { - setInputAmount(0); - setAmountError('Please enter a valid amount'); + setAmountError(undefined); } }; const getFormattedInputValue = () => { - if (inputAmount === 0) return ''; - return inputAmount.toLocaleString(); + if (inputAmountRsc === 0) return ''; + const displayValueNum = useUsd ? inputAmountRsc * exchangeRate : inputAmountRsc; + return displayValueNum.toLocaleString(undefined, { maximumFractionDigits: 2 }); }; + const secondaryText = + exchangeRate > 0 + ? useUsd + ? `≈ ${formatCurrency({ amount: inputAmountRsc, showUSD: false, exchangeRate })} RSC` + : `≈ ${formatCurrency({ amount: inputAmountRsc, showUSD: true, exchangeRate })} USD` + : undefined; + const handleContribute = async () => { try { - // Validate minimum amount before proceeding - if (inputAmount < 10) { - setError('Minimum contribution amount is 10 RSC'); + // Validate minimum amount before proceeding (10 RSC) + if (inputAmountRsc < 10) { + const minUsd = 10 * exchangeRate; + setError( + useUsd + ? `Minimum contribution amount is $${minUsd.toFixed(2)} USD` + : 'Minimum contribution amount is 10 RSC' + ); return; } @@ -188,8 +232,8 @@ export function ContributeToFundraiseModal({ setError(null); // Pass the contribution amount without the platform fee - // The API expects the net contribution amount - await FundraiseService.contributeToFundraise(fundraise.id, inputAmount); + // The API expects the net contribution amount in RSC + await FundraiseService.contributeToFundraise(fundraise.id, inputAmountRsc); toast.success('Your contribution has been successfully added to the fundraise.'); @@ -211,8 +255,8 @@ export function ContributeToFundraiseModal({ } }; - const platformFee = Math.round(inputAmount * 0.09 * 100) / 100; - const totalAmount = inputAmount + platformFee; + const platformFee = Math.round(inputAmountRsc * 0.09 * 100) / 100; + const totalAmount = inputAmountRsc + platformFee; const insufficientBalance = totalAvailableBalance < totalAmount; return ( @@ -266,6 +310,8 @@ export function ContributeToFundraiseModal({ value={getFormattedInputValue()} onChange={handleAmountChange} error={amountError} + currencyLabel={useUsd ? 'USD' : 'RSC'} + helperText={secondaryText} />
@@ -275,9 +321,11 @@ export function ContributeToFundraiseModal({

Fees Breakdown

@@ -299,10 +347,10 @@ export function ContributeToFundraiseModal({ variant="default" disabled={ isContributing || - !inputAmount || + !inputAmountRsc || insufficientBalance || !!amountError || - inputAmount < 10 + inputAmountRsc < 10 } className="w-full h-12 text-base" onClick={handleContribute} From f1d3334ef2bf2dfaf471ab2ed251fda119b0b5ba Mon Sep 17 00:00:00 2001 From: Tyler Diorio <109099227+TylerDiorio@users.noreply.github.com> Date: Thu, 13 Nov 2025 10:30:47 -0800 Subject: [PATCH 2/3] Resolve SonarCloud issues --- components/modals/BalanceInfo.tsx | 20 ++++++++++++------- .../modals/ContributeToFundraiseModal.tsx | 18 ++++++++++------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/components/modals/BalanceInfo.tsx b/components/modals/BalanceInfo.tsx index fd5a5cbae..b680a1c7d 100644 --- a/components/modals/BalanceInfo.tsx +++ b/components/modals/BalanceInfo.tsx @@ -34,20 +34,26 @@ export function BalanceInfo({ const deficitFormatted = deficit > 0 ? formatCurrency({ amount: deficit, showUSD: useUsd, exchangeRate }) : undefined; + const primaryUnit = useUsd ? 'USD' : 'RSC'; + const primaryWithUnit = `${primary} ${primaryUnit}`; + + const secondaryUnit = useUsd ? 'RSC' : 'USD'; + const secondaryWithUnit = secondary ? `${secondary} ${secondaryUnit}` : undefined; + + const deficitWithUnit = deficitFormatted ? `${deficitFormatted} ${primaryUnit}` : undefined; + return (
Current RSC Balance: - {useUsd ? `${primary} USD` : `${primary} RSC`} + {primaryWithUnit}
- {secondary && ( -
- ≈ {useUsd ? `${secondary} RSC` : `${secondary} USD`} -
+ {secondaryWithUnit && ( +
≈ {secondaryWithUnit}
)} - {showWarning && deficitFormatted && ( + {showWarning && deficitWithUnit && (
- {`You need ${useUsd ? `${deficitFormatted} USD` : `${deficitFormatted} RSC`} more for this contribution`} + {`You need ${deficitWithUnit} more for this contribution`}
)}
diff --git a/components/modals/ContributeToFundraiseModal.tsx b/components/modals/ContributeToFundraiseModal.tsx index 693f27aca..9a87a4984 100644 --- a/components/modals/ContributeToFundraiseModal.tsx +++ b/components/modals/ContributeToFundraiseModal.tsx @@ -179,7 +179,7 @@ export function ContributeToFundraiseModal({ const rawValue = e.target.value.replace(/[^0-9.]/g, ''); const numValue = parseFloat(rawValue); - if (isNaN(numValue)) { + if (Number.isNaN(numValue)) { setInputAmountRsc(0); setAmountError('Please enter a valid amount'); return; @@ -208,12 +208,16 @@ export function ContributeToFundraiseModal({ return displayValueNum.toLocaleString(undefined, { maximumFractionDigits: 2 }); }; - const secondaryText = - exchangeRate > 0 - ? useUsd - ? `≈ ${formatCurrency({ amount: inputAmountRsc, showUSD: false, exchangeRate })} RSC` - : `≈ ${formatCurrency({ amount: inputAmountRsc, showUSD: true, exchangeRate })} USD` - : undefined; + let secondaryText: string | undefined; + if (exchangeRate > 0) { + const otherFormatted = formatCurrency({ + amount: inputAmountRsc, + showUSD: !useUsd, + exchangeRate, + }); + const secondaryUnit = useUsd ? 'RSC' : 'USD'; + secondaryText = `≈ ${otherFormatted} ${secondaryUnit}`; + } const handleContribute = async () => { try { From 67000daab6a76b6df4a365e1fb6a9350e080d58f Mon Sep 17 00:00:00 2001 From: Tyler Diorio <109099227+TylerDiorio@users.noreply.github.com> Date: Thu, 13 Nov 2025 11:17:14 -0800 Subject: [PATCH 3/3] Fix issue and remove useless comments --- .../modals/ContributeToFundraiseModal.tsx | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/components/modals/ContributeToFundraiseModal.tsx b/components/modals/ContributeToFundraiseModal.tsx index 9a87a4984..2d74045cf 100644 --- a/components/modals/ContributeToFundraiseModal.tsx +++ b/components/modals/ContributeToFundraiseModal.tsx @@ -1,7 +1,7 @@ 'use client'; import { Dialog, Transition } from '@headlessui/react'; -import { Fragment, useState } from 'react'; +import { Fragment, useEffect, useState } from 'react'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/form/Input'; import { Alert } from '@/components/ui/Alert'; @@ -23,7 +23,6 @@ interface ContributeToFundraiseModalProps { fundraise: Fundraise; } -// Currency Input Component const CurrencyInput = ({ value, onChange, @@ -61,7 +60,6 @@ const CurrencyInput = ({ ); }; -// Fee Breakdown Component const FeeBreakdown = ({ contributionAmount, platformFee, @@ -167,14 +165,12 @@ export function ContributeToFundraiseModal({ const [isSuccess, setIsSuccess] = useState(false); const [amountError, setAmountError] = useState(undefined); - // Calculate total available balance including locked balance for fundraise contributions const userBalance = user?.balance || 0; const lockedBalance = user?.lockedBalance || 0; const totalAvailableBalance = userBalance + lockedBalance; const useUsd = showUSD && exchangeRate > 0; - // Utility functions const handleAmountChange = (e: React.ChangeEvent) => { const rawValue = e.target.value.replace(/[^0-9.]/g, ''); const numValue = parseFloat(rawValue); @@ -202,6 +198,18 @@ export function ContributeToFundraiseModal({ } }; + // Keep min-amount validation message in sync with currency preference/exchange rate + useEffect(() => { + if (inputAmountRsc > 0 && inputAmountRsc < 10) { + const minUsd = 10 * exchangeRate; + setAmountError( + useUsd + ? `Minimum contribution amount is $${minUsd.toFixed(2)} USD` + : 'Minimum contribution amount is 10 RSC' + ); + } + }, [useUsd, exchangeRate, inputAmountRsc]); + const getFormattedInputValue = () => { if (inputAmountRsc === 0) return ''; const displayValueNum = useUsd ? inputAmountRsc * exchangeRate : inputAmountRsc; @@ -221,7 +229,6 @@ export function ContributeToFundraiseModal({ const handleContribute = async () => { try { - // Validate minimum amount before proceeding (10 RSC) if (inputAmountRsc < 10) { const minUsd = 10 * exchangeRate; setError( @@ -235,21 +242,16 @@ export function ContributeToFundraiseModal({ setIsContributing(true); setError(null); - // Pass the contribution amount without the platform fee - // The API expects the net contribution amount in RSC await FundraiseService.contributeToFundraise(fundraise.id, inputAmountRsc); toast.success('Your contribution has been successfully added to the fundraise.'); - // Set success flag setIsSuccess(true); - // Call onContributeSuccess if provided if (onContributeSuccess) { onContributeSuccess(); } - // Close the modal onClose(); } catch (error) { console.error('Failed to contribute to fundraise:', error);