From e1883de662ce34836e0e3c9a3a65187d1980d966 Mon Sep 17 00:00:00 2001 From: zvspany Date: Tue, 10 Mar 2026 18:44:56 +0100 Subject: [PATCH] feat(converter): add multi-conversion panel --- components/converter/converter-card.tsx | 82 +++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/components/converter/converter-card.tsx b/components/converter/converter-card.tsx index 73c0e4f..40a5d66 100644 --- a/components/converter/converter-card.tsx +++ b/components/converter/converter-card.tsx @@ -46,11 +46,14 @@ import { validateAmount } from "@/lib/validation"; const DEFAULT_FROM = "USD"; const DEFAULT_TO = "EUR"; const QUICK_AMOUNTS = [10, 50, 100, 500, 1000] as const; +const DEFAULT_MULTI_CONVERSION_CODES = ["USD", "EUR", "BTC", "ETH", "SOL"] as const; +const MAX_MULTI_CONVERSIONS = 4; interface ConverterCardProps { forcedFromCode?: string; forcedToCode?: string; onPairChange?: (fromCode: string, toCode: string) => void; + multiConversionCodes?: string[]; } function ConverterSkeleton() { @@ -124,6 +127,7 @@ export function ConverterCard({ forcedFromCode, forcedToCode, onPairChange, + multiConversionCodes, }: ConverterCardProps) { const { data, error, isLoading, refresh } = useMarketRates(); @@ -188,6 +192,35 @@ export function ConverterCard({ return convertAmount(debouncedValidation.value, fromAsset, toAsset); }, [fromAsset, toAsset, debouncedValidation]); + const resolvedMultiConversionCodes = useMemo(() => { + const configuredCodes = + multiConversionCodes && multiConversionCodes.length > 0 + ? multiConversionCodes + : [...DEFAULT_MULTI_CONVERSION_CODES]; + + return Array.from( + new Set( + [toCode, ...configuredCodes] + .map((code) => code.trim().toUpperCase()) + .filter(Boolean), + ), + ).slice(0, MAX_MULTI_CONVERSIONS); + }, [multiConversionCodes, toCode]); + + const multiConversions = useMemo(() => { + if (!fromAsset || !debouncedValidation.ok) { + return []; + } + + return resolvedMultiConversionCodes + .map((code) => rateMap.get(code)) + .filter((asset): asset is NonNullable => Boolean(asset)) + .map((asset) => ({ + asset, + value: convertAmount(debouncedValidation.value, fromAsset, asset), + })); + }, [fromAsset, debouncedValidation, resolvedMultiConversionCodes, rateMap]); + const currentRate = useMemo(() => { if (!fromAsset || !toAsset) { return null; @@ -501,6 +534,55 @@ export function ConverterCard({ ) : null} +
+
+

+ Multi conversion +

+ {fromAsset && debouncedValidation.ok ? ( + + for {formatAmount(debouncedValidation.value, fromAsset)} {fromAsset.code} + + ) : null} +
+ + {!debouncedValidation.ok ? ( +

{debouncedValidation.error}

+ ) : null} + + {debouncedValidation.ok && multiConversions.length === 0 ? ( +

+ No additional assets available for multi conversion. +

+ ) : null} + + {debouncedValidation.ok && multiConversions.length > 0 ? ( +
+ {multiConversions.map(({ asset, value }) => ( +
+
+ + {asset.code} +
+

+ {formatAmount(value, asset)} {asset.code} +

+
+ ))} +
+ ) : null} + + {isLoading ? ( +
+ + Updating multi conversion... +
+ ) : null} +
+