diff --git a/components/modals/BalanceInfo.tsx b/components/modals/BalanceInfo.tsx
index 7d463466c..b680a1c7d 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,43 @@ 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;
+
+ 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:
- {totalAvailableBalance.toLocaleString()} RSC
+ {primaryWithUnit}
- {showWarning && (
+ {secondaryWithUnit && (
+
≈ {secondaryWithUnit}
+ )}
+ {showWarning && deficitWithUnit && (
- {`You need ${(amount - totalAvailableBalance).toLocaleString()} 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 fe8f0c8d2..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';
@@ -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 {
@@ -21,15 +23,18 @@ interface ContributeToFundraiseModalProps {
fundraise: Fundraise;
}
-// Currency Input Component
const CurrencyInput = ({
value,
onChange,
error,
+ currencyLabel,
+ helperText,
}: {
value: string;
onChange: (e: React.ChangeEvent) => void;
error?: string;
+ currencyLabel: string;
+ helperText?: string;
}) => {
return (
@@ -45,62 +50,74 @@ 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}
}
);
};
-// Fee Breakdown Component
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`;
+ };
+
+ return (
+
+
+ Your contribution:
+ {formatAmt(contributionAmount)}
+
-
-
-
-
Platform fees (9%)
-
-
-
+
+
+
+
Platform fees (9%)
+
+
+
+
+
+ {formatAmt(platformFee)}
-
+ {platformFee.toLocaleString()} RSC
-
-
+
-
-
Total amount:
-
{totalAmount.toLocaleString()} RSC
+
+ Total amount:
+ {formatAmt(totalAmount)}
+
-
-);
+ );
+};
const ModalHeader = ({
title,
@@ -140,68 +157,101 @@ 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);
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;
- // Utility functions
+ const useUsd = showUSD && exchangeRate > 0;
+
const handleAmountChange = (e: React.ChangeEvent) => {
const rawValue = e.target.value.replace(/[^0-9.]/g, '');
const numValue = parseFloat(rawValue);
- if (!isNaN(numValue)) {
- setInputAmount(numValue);
+ if (Number.isNaN(numValue)) {
+ setInputAmountRsc(0);
+ setAmountError('Please enter a valid amount');
+ return;
+ }
+
+ // Convert from preferred currency to RSC
+ const rsc = useUsd ? numValue / exchangeRate : numValue;
+ setInputAmountRsc(rsc);
- // Validate minimum amount
- if (numValue < 10) {
- setAmountError('Minimum contribution amount is 10 RSC');
- } else {
- setAmountError(undefined);
- }
+ // 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);
}
};
+ // 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 (inputAmount === 0) return '';
- return inputAmount.toLocaleString();
+ if (inputAmountRsc === 0) return '';
+ const displayValueNum = useUsd ? inputAmountRsc * exchangeRate : inputAmountRsc;
+ return displayValueNum.toLocaleString(undefined, { maximumFractionDigits: 2 });
};
+ 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 {
- // Validate minimum amount before proceeding
- if (inputAmount < 10) {
- setError('Minimum contribution amount is 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;
}
setIsContributing(true);
setError(null);
- // Pass the contribution amount without the platform fee
- // The API expects the net contribution amount
- await FundraiseService.contributeToFundraise(fundraise.id, inputAmount);
+ 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);
@@ -211,8 +261,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 +316,8 @@ export function ContributeToFundraiseModal({
value={getFormattedInputValue()}
onChange={handleAmountChange}
error={amountError}
+ currencyLabel={useUsd ? 'USD' : 'RSC'}
+ helperText={secondaryText}
/>
@@ -275,9 +327,11 @@ export function ContributeToFundraiseModal({
Fees Breakdown
@@ -299,10 +353,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}