Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: amount input currency #1083

Merged
merged 32 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0412aa5
feat: currency select modal
Sep 15, 2023
c16354b
chore: added icon for currency
Sep 18, 2023
af0a888
feat: added currency mode to amount input
Sep 19, 2023
d15309a
feat: price provider model
tuul-wq Sep 20, 2023
dc3f4d6
Merge branch 'dev' into feat/coingeko_model
tuul-wq Sep 20, 2023
7e3c192
feat: load assets prices
tuul-wq Sep 20, 2023
790c3e4
chore: code style, export events
tuul-wq Sep 20, 2023
3601197
chore: merged coingecko service
Sep 20, 2023
036002c
feat: integrated currency modal to currenc settings
Sep 20, 2023
cdf84b1
chore: updated crouped select semantics
Sep 20, 2023
f8bb7dc
chore: fixed General Action test
Sep 20, 2023
ef04239
chore: fixed grouped select active state
Sep 20, 2023
89e3121
feat: tests for models, review fixes
tuul-wq Sep 21, 2023
a297522
Merge branch 'feat/coingeko_model' into feat/currency-settings
tuul-wq Sep 21, 2023
d143269
Merge branch 'feat/currency-settings' of https://github.com/novasamat…
Sep 21, 2023
011349d
Merge branch 'feat/fiat_values' of https://github.com/novasamatech/no…
Sep 21, 2023
784ea45
chore: fixed pr comments
Sep 21, 2023
d29c01f
chore: added currency form model
Sep 22, 2023
5d04462
chore: merged feat/fiat_value
Sep 22, 2023
652815d
fix: fixed modal reload after submit
Sep 22, 2023
ac94149
feat: [wip] amount input currency integration
Sep 22, 2023
45c70b0
chore: removed button disabled condition
Sep 22, 2023
7548f1b
feat: amount input with currency integration
Sep 22, 2023
7b919b8
fix: tests
Asmadek Sep 25, 2023
774cc08
feat: form model, test
tuul-wq Sep 25, 2023
c7c4352
chore: removed GroupedSelect
tuul-wq Sep 25, 2023
ab8eab2
Merge branch 'feat/currency-settings' of https://github.com/nova-wall…
Asmadek Sep 26, 2023
496fe7e
fix: ui
Asmadek Sep 26, 2023
cea4988
Merge branch 'feat/fiat_values' of https://github.com/nova-wallet/omn…
Asmadek Sep 26, 2023
319155d
fix: revert commented code
Asmadek Sep 26, 2023
1f34134
chore: merge fiat-branch
tuul-wq Sep 26, 2023
dd2182e
Merge remote-tracking branch 'origin/feat/input-currency' into feat/i…
tuul-wq Sep 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/renderer/app/styles/theme/default.css
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
--alert-border: rgb(123, 41, 255);
--alert-border-warning: rgb(246, 143, 7);
--alert-border-negative: rgb(245, 33, 99);
--token-border: #c3c3cb;
--token-border: #5e5e69;
--border-dark: #6e6e78;

/* DIVIDER */
Expand All @@ -71,7 +71,7 @@
--left-navigation-menu-background: #ffffff;
--main-app-background: rgba(69, 69, 137, 0.04);
--token-container-background: #ffffff;
--token-background: #79797d;
--token-background: #363643;
--input-background: #fff;
--input-background-disabled: rgba(69, 69, 137, 0.04);
--action-background-hover: rgba(69, 69, 137, 0.06);
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/assets/images/arrows/swap-arrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/renderer/entities/asset/ui/AssetIcon/AssetIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type Props = {
className?: string;
};

// TODO add currency support
export const AssetIcon = ({ src, name, size = 32, className }: Props) => {
const [isImgLoaded, toggleImgLoaded] = useToggle();

Expand Down
1 change: 1 addition & 0 deletions src/renderer/entities/price/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { priceProviderModel } from './model/price-provider-model';
export { currencyModel } from './model/currency-model';
export { useCurrencyRate } from './lib/useCurrencyRate';
13 changes: 13 additions & 0 deletions src/renderer/entities/price/lib/useCurrencyRate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useUnit } from 'effector-react';

import { currencyModel, priceProviderModel } from '@renderer/entities/price';

export const useCurrencyRate = (assetId?: string, showCurrency?: boolean): number | null => {
const fiatFlag = useUnit(priceProviderModel.$fiatFlag);
const activeCurrency = useUnit(currencyModel.$activeCurrency);
const assetsPrices = useUnit(priceProviderModel.$assetsPrices);

if (!showCurrency || !fiatFlag || !activeCurrency || !assetsPrices || !assetId) return null;

return assetsPrices[assetId][activeCurrency.coingeckoId].price;
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,26 @@ import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';

import { GeneralActions } from './GeneralActions';
import { Paths } from '../../../../../app/providers/routes/paths';

jest.mock('@renderer/app/providers', () => ({
useI18n: jest.fn().mockReturnValue({
t: (key: string) => key,
}),
Paths: {
NETWORK: '/settings/network',
CURRENCY: '/settings/currency',
},
}));

describe('pages/Settings/Overview/GeneralActions', () => {
test('should render label and link to network', () => {
describe('screens/Settings/Overview/GeneralActions', () => {
test('should render label and link to network and currency', () => {
render(<GeneralActions />, { wrapper: MemoryRouter });

const label = screen.getByText('settings.overview.generalLabel');
const link = screen.getAllByRole('link')[0];
const links = screen.getAllByRole('link');
expect(label).toBeInTheDocument();
expect(link).toBeInTheDocument();
expect(link).toHaveAttribute('href', Paths.NETWORK);

expect(links[0]).toHaveAttribute('href', '/settings/network');
expect(links[1]).toHaveAttribute('href', '/settings/currency');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { Link } from 'react-router-dom';
import { useUnit } from 'effector-react/effector-react.umd';

import { Icon, BodyText, Plate, FootnoteText, HelpText } from '@renderer/shared/ui';
import { useI18n } from '@renderer/app/providers';
import { Paths } from '../../../../../app/providers/routes/paths';
import { useI18n, Paths } from '@renderer/app/providers';
import { cnTw } from '@renderer/shared/lib/utils';
import { currencyModel, priceProviderModel } from '@renderer/entities/price';

Expand Down
2 changes: 2 additions & 0 deletions src/renderer/shared/ui/Icon/data/arrow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import ArrowLeftCutoutImg, { ReactComponent as ArrowLeftCutoutSvg } from '@image
import SendArrowImg, { ReactComponent as SendArrowSvg } from '@images/arrows/send-arrow.svg';
import ReceiveArrowImg, { ReactComponent as ReceiveArrowSvg } from '@images/arrows/receive-arrow.svg';
import CurveArrowImg, { ReactComponent as CurveArrowSvg } from '@images/arrows/arrow-curve-left-right.svg';
import SwapArrowImg, { ReactComponent as SwapArrowSvg } from '@images/arrows/swap-arrow.svg';
import CrossChainImg, { ReactComponent as CrossChainSvg } from '@images/arrows/cross-chain-arrow.svg';

const ArrowImages = {
Expand All @@ -17,6 +18,7 @@ const ArrowImages = {
sendArrow: { svg: SendArrowSvg, img: SendArrowImg },
receiveArrow: { svg: ReceiveArrowSvg, img: ReceiveArrowImg },
curveArrow: { svg: CurveArrowSvg, img: CurveArrowImg },
swapArrow: { svg: SwapArrowSvg, img: SwapArrowImg },
crossChain: { svg: CrossChainSvg, img: CrossChainImg },
} as const;

Expand Down
76 changes: 61 additions & 15 deletions src/renderer/shared/ui/Inputs/AmountInput/AmountInput.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { useCallback } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { useUnit } from 'effector-react';

import { AssetBalance, AssetIcon, Asset } from '@renderer/entities/asset';
import { cleanAmount, cnTw, formatGroups, validatePrecision, validateSymbols } from '@renderer/shared/lib/utils';
import { useI18n } from '@renderer/app/providers';
import { FootnoteText, TitleText } from '../../Typography';
// FIXME components in shared shouldn't use components from entity so we need to move it to entity
import { AssetBalance, AssetIcon, Asset } from '@renderer/entities/asset';
import Input from '../Input/Input';
import { cleanAmount, formatGroups, validatePrecision, validateSymbols } from '@renderer/shared/lib/utils';
import { IconButton } from '@renderer/shared/ui';
import { useToggle } from '@renderer/shared/lib/hooks';
import { currencyModel, useCurrencyRate } from '@renderer/entities/price';

type Props = {
name?: string;
Expand All @@ -16,10 +19,11 @@ type Props = {
balancePlaceholder?: string;
balance?: string | string[];
invalid?: boolean;
showCurrency?: boolean;
onChange?: (value: string) => void;
};

const AmountInput = ({
export const AmountInput = ({
name,
value,
asset,
Expand All @@ -28,35 +32,52 @@ const AmountInput = ({
placeholder,
disabled,
invalid,
showCurrency = true,
onChange,
}: Props) => {
const { t } = useI18n();
const rate = useCurrencyRate(asset.priceId, showCurrency);
const activeCurrency = useUnit(currencyModel.$activeCurrency);
const [currencyMode, toggleCurrencyMode] = useToggle(false);
const [internalValue, setInternalValue] = useState(value);

const handleChange = (amount: string) => {
const cleanedAmount = cleanAmount(amount);
setInternalValue(cleanedAmount);

if (validateSymbols(cleanedAmount) && validatePrecision(cleanedAmount, asset.precision)) {
onChange?.(cleanedAmount);
if (validateSymbols(cleanedAmount) && (currencyMode || validatePrecision(cleanedAmount, asset.precision))) {
onChange?.(currencyMode && rate ? (Number(cleanedAmount) * (1 / rate)).toString() : cleanedAmount);
} else {
onChange?.(value);
}
};

useEffect(() => {
if (currencyMode && rate) {
setInternalValue((Number(value) * rate).toString());
}
}, [currencyMode]);

const getBalance = useCallback(() => {
if (!balance) return;

if (Array.isArray(balance)) {
return (
<span className="flex gap-x-1">
<AssetBalance className="text-text-primary text-footnote" value={balance[0]} asset={asset} />
<AssetBalance className="text-text-tertiary text-footnote" value={balance[0]} asset={asset} />
<span>-</span>
<AssetBalance className="text-text-primary text-footnote" value={balance[1]} asset={asset} />
<AssetBalance className="text-text-tertiary text-footnote" value={balance[1]} asset={asset} />
</span>
);
}

return (
<AssetBalance className="inline text-text-primary text-footnote" value={balance} asset={asset} showIcon={false} />
<AssetBalance
className="inline text-text-tertiary text-footnote"
value={balance}
asset={asset}
showIcon={false}
/>
);
}, [balance]);

Expand All @@ -72,26 +93,51 @@ const AmountInput = ({
</div>
);

const currencyIcon = showCurrency && activeCurrency && (
<div className="flex items-center gap-x-1 min-w-fit">
<div className="relative rounded-full bg-token-background border border-token-border p-[1px] w-8 h-8">
<TitleText align="center" className="text-white">
{activeCurrency.symbol || activeCurrency.code}
</TitleText>
</div>
<TitleText>{activeCurrency.code}</TitleText>
</div>
);

const prefixElement = (
<div className="flex items-center gap-x-1 min-w-fit">
<AssetIcon src={asset.icon} name={asset.name} size={28} className="flex" />
<TitleText>{asset.symbol}</TitleText>
</div>
);

const suffixElement = showCurrency && rate && (
<div className="flex items-center gap-x-2 absolute right-3 bottom-3">
<IconButton
name="swapArrow"
alt={t(currencyMode ? 'transfer.swapToCryptoModeAlt' : 'transfer.swapToCurrencyModeAlt')}
size={16}
onClick={toggleCurrencyMode}
/>
<FootnoteText className="uppercase text-text-tertiary">
{currencyMode ? `${value} ${asset.symbol}` : `${activeCurrency?.symbol} ${Number(value) * rate}`}
</FootnoteText>
</div>
);

return (
<Input
name={name}
className="text-right text-title font-manrope"
className={cnTw('text-right text-title font-manrope', activeCurrency && rate && 'mb-7')}
wrapperClass="py-3 items-start"
label={label}
value={formatGroups(value)}
value={formatGroups(currencyMode ? internalValue : value)}
placeholder={t('transfer.amountPlaceholder')}
invalid={invalid}
prefixElement={prefixElement}
prefixElement={currencyMode ? currencyIcon : prefixElement}
suffixElement={suffixElement}
disabled={disabled}
onChange={handleChange}
/>
);
};

export default AmountInput;
2 changes: 1 addition & 1 deletion src/renderer/shared/ui/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Input from './Inputs/Input/Input';
import Plate from './Plate/Plate';
import AmountInput from './Inputs/AmountInput/AmountInput';
import { AmountInput } from './Inputs/AmountInput/AmountInput';
import PasswordInput from './Inputs/PasswordInput/PasswordInput';
import InputHint from './InputHint/InputHint';
import Button from './Buttons/Button/Button';
Expand Down
2 changes: 2 additions & 0 deletions src/shared/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,8 @@
"signatoryLabel": "Signatory",
"startSigningButton": "Sign with Polkadot Vault",
"successMessage": "Operation Success",
"swapToCryptoModeAlt": "swap input to crypto",
"swapToCurrencyModeAlt": "swap input to currency",
"title": "Transfer { asset } on",
"xcmTitle": "Transfer { asset } from"
},
Expand Down
2 changes: 2 additions & 0 deletions src/shared/locale/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,8 @@
"signatoryLabel": "Подписант",
"startSigningButton": "Подписать в Polkadot Vault",
"successMessage": "Транзакция отправлена!",
"swapToCryptoModeAlt": "swap input to crypto",
"swapToCurrencyModeAlt": "swap input to currency",
"title": "Перевод { asset } в сети",
"xcmTitle": "Transfer { asset } from"
},
Expand Down
Loading