Skip to content

Commit

Permalink
select currency layout
Browse files Browse the repository at this point in the history
  • Loading branch information
lendihop committed Feb 10, 2025
1 parent 7109ca5 commit 9752f99
Show file tree
Hide file tree
Showing 4 changed files with 278 additions and 12 deletions.
82 changes: 75 additions & 7 deletions src/app/pages/Market/debit-credit-card/components/AssetIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,23 @@ import React, { memo, useCallback, useMemo, useState } from 'react';

import clsx from 'clsx';

import { Flag } from 'app/atoms/Flag';
import { TOKEN_FALLBACK_ICON_SRC, TOKENS_ICONS_SRC } from 'lib/icons';

interface Props {
src: string;
code: string;
useFlagIcon?: boolean;
size?: number;
alt?: string;
className?: string;
}

export const AssetIcon = memo<Props>(({ src, code, size = 40, alt, className }) => {
export const AssetIcon = memo<Props>(({ src, code, useFlagIcon, size = 40, alt, className }) => {
const [isFailed, setIsFailed] = useState(false);

const countryCode = currencyToLocaleMap[code];

const localSrc = useMemo(() => {
if (isFailed) return TOKEN_FALLBACK_ICON_SRC;
if (code === 'XTZ') return TOKENS_ICONS_SRC.TEZ;
Expand All @@ -26,12 +30,76 @@ export const AssetIcon = memo<Props>(({ src, code, size = 40, alt, className })

return (
<div className="flex justify-center items-center" style={{ width: size, height: size }}>
<img
src={localSrc}
alt={alt}
className={clsx('rounded-full w-full h-auto p-0.5', className)}
onError={handleError}
/>
{countryCode && useFlagIcon ? (
<Flag alt={code} countryCode={countryCode} />
) : (
<img
src={localSrc}
alt={alt}
className={clsx('rounded-full w-full h-auto p-0.5', className)}
onError={handleError}
/>
)}
</div>
);
});

const currencyToLocaleMap: Record<string, string> = {
AUD: 'au',
BGN: 'bg',
BRL: 'br',
CAD: 'ca',
CHF: 'ch',
COP: 'co',
CZK: 'cz',
DKK: 'dk',
DOP: 'do',
EGP: 'eg',
EUR: 'eu',
GBP: 'gb',
HKD: 'hk',
IDR: 'id',
ILS: 'il',
JOD: 'jo',
KES: 'ke',
KWD: 'kw',
LKR: 'lk',
MXN: 'mx',
NGN: 'ng',
NOK: 'no',
NZD: 'nz',
OMR: 'om',
PEN: 'pe',
PLN: 'pl',
RON: 'ro',
SEK: 'se',
THB: 'th',
TRY: 'tr',
TWD: 'tw',
USD: 'us',
VND: 'vn',
ZAR: 'za',
AED: 'ae',
ARS: 'ar',
AZN: 'az',
BHD: 'bh',
CLP: 'cl',
CRC: 'cr',
GEL: 'ge',
GTQ: 'gt',
HNL: 'hn',
HRK: 'hr',
HUF: 'hu',
INR: 'in',
JPY: 'jp',
KRW: 'kr',
MDL: 'md',
MYR: 'my',
PHP: 'ph',
PYG: 'py',
QAR: 'qa',
RWF: 'rw',
SAR: 'sa',
UAH: 'ua',
UYU: 'uy'
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@ import { AssetIcon } from './AssetIcon';

interface Props {
currency: TopUpInputInterface;
useFlagIcon?: boolean;
onClick?: EmptyFn;
}

export const SelectAssetButton = memo<Props>(({ currency, onClick }) => (
export const SelectAssetButton = memo<Props>(({ currency, useFlagIcon, onClick }) => (
<Button
className="cursor-pointer flex justify-between items-center bg-white py-0.5 px-2 gap-x-1 rounded-8 w-[144px] h-[46px] border-0.5 border-transparent hover:border-lines"
onClick={onClick}
>
<div className="flex items-center gap-x-2">
<AssetIcon src={currency.icon} code={currency.code} />
<AssetIcon useFlagIcon={useFlagIcon} src={currency.icon} code={currency.code} />
<div className="text-start">
<p className="text-font-description-bold">{currency.code}</p>
<p className="text-font-num-12 text-grey-1 w-[52px] truncate">{currency.name}</p>
Expand Down
4 changes: 3 additions & 1 deletion src/app/pages/Market/debit-credit-card/contents/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ export const Form: FC<Props> = ({ setModalContent }) => {
onBlur={onBlur}
onChange={v => onChange(v ?? '')}
assetDecimals={inputCurrency.precision}
rightSideComponent={<SelectAssetButton currency={inputCurrency} onClick={handleSelectCurrency} />}
rightSideComponent={
<SelectAssetButton useFlagIcon currency={inputCurrency} onClick={handleSelectCurrency} />
}
rightSideContainerStyle={{ right: 2 }}
style={{ paddingRight: 158 }}
underneathComponent={
Expand Down
199 changes: 197 additions & 2 deletions src/app/pages/Market/debit-credit-card/contents/SelectCurrency.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,215 @@
import React, { FC, useLayoutEffect } from 'react';
import React, { FC, useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react';

import { useFormContext } from 'react-hook-form-v7';
import { useDebounce } from 'use-debounce';

import { EmptyState } from 'app/atoms/EmptyState';
import { BackButton } from 'app/atoms/PageModal';
import { RadioButton } from 'app/atoms/RadioButton';
import { SearchBarField } from 'app/templates/SearchField';
import { t } from 'lib/i18n';
import { useScrollIntoViewOnMount } from 'lib/ui/use-scroll-into-view';
import { isSearchStringApplicable, searchAndFilterItems } from 'lib/utils/search-items';

import { ModalHeaderConfig } from '../../types';
import { AssetIcon } from '../components/AssetIcon';
import { FormData } from '../config';
import { TopUpInputInterface } from '../topup.interface';

interface Props {
setModalHeaderConfig: SyncFn<ModalHeaderConfig>;
onGoBack: EmptyFn;
}

export const SelectCurrency: FC<Props> = ({ setModalHeaderConfig, onGoBack }) => {
const [searchValue, setSearchValue] = useState('');
const [searchValueDebounced] = useDebounce(searchValue, 300);
const inSearch = isSearchStringApplicable(searchValueDebounced);

const [attractSelectedCurrency, setAttractSelectedCurrency] = useState(true);

const { watch, setValue } = useFormContext<FormData>();

const activeCurrency = watch('inputCurrency');

useLayoutEffect(
() => void setModalHeaderConfig({ title: t('selectCurrency'), titleLeft: <BackButton onClick={onGoBack} /> }),
[]
);

return <div></div>;
const searchedCurrencies = useMemo(
() => (inSearch ? searchAndFilterCurrencies(currenciesMock, searchValueDebounced) : currenciesMock),
[inSearch, searchValueDebounced]
);

useEffect(() => {
if (searchValueDebounced) setAttractSelectedCurrency(false);
}, [searchValueDebounced]);

const onCurrencySelect = useCallback(
(currency: TopUpInputInterface) => {
setValue('inputCurrency', currency);
onGoBack();
},
[setValue, onGoBack]
);

return (
<>
<div className="p-4">
<SearchBarField value={searchValue} onValueChange={setSearchValue} className="p-4" />
</div>

<div className="px-4 pb-1 flex-grow flex flex-col overflow-y-auto">
{searchedCurrencies.length === 0 && <EmptyState />}

{searchedCurrencies.map(currency => (
<Currency
key={currency.code}
currency={currency}
activeCurrency={activeCurrency}
attractSelf={attractSelectedCurrency}
showBalance
onClick={onCurrencySelect}
/>
))}
</div>
</>
);
};

interface CurrencyProps {
currency: TopUpInputInterface;
activeCurrency: TopUpInputInterface;
attractSelf?: boolean;
showBalance?: boolean;
iconSize?: number;
onClick?: (currency: TopUpInputInterface) => void;
}

export const Currency: FC<CurrencyProps> = ({ currency, activeCurrency, attractSelf, iconSize = 24, onClick }) => {
const active = currency.code === activeCurrency.code;

const elemRef = useScrollIntoViewOnMount<HTMLDivElement>(active && attractSelf);

const handleClick = useCallback(() => onClick?.(currency), [currency, onClick]);

return (
<div
ref={elemRef}
className="cursor-pointer mb-3 flex justify-between items-center py-2 px-3 rounded-lg shadow-bottom border-0.5 border-transparent group"
onClick={handleClick}
>
<div className="flex items-center gap-x-2">
<AssetIcon useFlagIcon size={iconSize} src={currency.icon} code={currency.code} />

<div className="flex flex-col">
<span className="text-font-medium-bold">{currency.code}</span>

<span className="text-grey-1 text-font-small">{currency.name}</span>
</div>
</div>

<RadioButton active={active} className={active ? undefined : 'opacity-0 group-hover:opacity-100'} />
</div>
);
};

const searchAndFilterCurrencies = (currencies: TopUpInputInterface[], searchValue: string) =>
searchAndFilterItems(currencies, searchValue.trim(), [
{ name: 'name', weight: 1 },
{ name: 'code', weight: 1 }
]);

const currenciesMock: TopUpInputInterface[] = [
{
name: 'US Dollar',
code: 'USD',
codeToDisplay: 'USD',
icon: 'https://static.moonpay.com/widget/currencies/usd.svg',
minAmount: 35,
maxAmount: 16000,
precision: 2
},
{
name: 'Australian Dollar',
code: 'AUD',
codeToDisplay: 'AUD',
icon: 'https://static.moonpay.com/widget/currencies/aud.svg',
minAmount: 35,
maxAmount: 16000,
precision: 2
},
{
name: 'Bulgarian Lev',
code: 'BGN',
codeToDisplay: 'BGN',
icon: 'https://static.moonpay.com/widget/currencies/bgn.svg',
minAmount: 40,
maxAmount: 20000,
precision: 2
},
{
name: 'Brazilian Real',
code: 'BRL',
codeToDisplay: 'BRL',
icon: 'https://static.moonpay.com/widget/currencies/brl.svg',
minAmount: 130,
maxAmount: 65000,
precision: 2
},
{
name: 'Canadian Dollar',
code: 'CAD',
codeToDisplay: 'CAD',
icon: 'https://static.moonpay.com/widget/currencies/cad.svg',
minAmount: 30,
maxAmount: 16000,
precision: 2
},
{
name: 'Swiss Franc',
code: 'CHF',
codeToDisplay: 'CHF',
icon: 'https://static.moonpay.com/widget/currencies/chf.svg',
minAmount: 20,
maxAmount: 11000,
precision: 2
},
{
name: 'Colombian Peso',
code: 'COP',
codeToDisplay: 'COP',
icon: 'https://static.moonpay.com/widget/currencies/cop.svg',
minAmount: 100000,
maxAmount: 42500000,
precision: 0
},
{
name: 'Czech Koruna',
code: 'CZK',
codeToDisplay: 'CZK',
icon: 'https://static.moonpay.com/widget/currencies/czk.svg',
minAmount: 500,
maxAmount: 260000,
precision: 2
},
{
name: 'Danish Krone',
code: 'DKK',
codeToDisplay: 'DKK',
icon: 'https://static.moonpay.com/widget/currencies/dkk.svg',
minAmount: 150,
maxAmount: 75000,
precision: 2
},
{
name: 'Dominican Peso',
code: 'DOP',
codeToDisplay: 'DOP',
icon: 'https://static.moonpay.com/widget/currencies/dop.svg',
minAmount: 1500,
maxAmount: 680000,
precision: 2
}
];

0 comments on commit 9752f99

Please sign in to comment.