Skip to content

Commit

Permalink
Peter/earn strategies feat deposit withdraw form (#1229)
Browse files Browse the repository at this point in the history
* chore: update monetary to latest 0.7.3

* wip

* feat(earn-strategies): add deposit and withdrawal form components

* refactor: add padding under tabs in earn strategy forms

* chore(earn-strategies): change file structure
  • Loading branch information
peterslany authored May 30, 2023
1 parent 891de67 commit 918e944
Show file tree
Hide file tree
Showing 19 changed files with 390 additions and 12 deletions.
7 changes: 6 additions & 1 deletion src/assets/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"reimbursed": "Reimbursed",
"online": "Online",
"offline": "Offline",
"available": "Available",
"unavailable": "Unavailable",
"ok": "OK",
"pending": "Pending",
Expand Down Expand Up @@ -154,6 +155,7 @@
"unlocks": "Unlocks",
"staked": "Staked",
"sign_t&cs": "Sign T&Cs",
"receivable_assets": "Receivable Assets",
"redeem_page": {
"maximum_in_single_request": "Max redeemable in single request",
"redeem": "Redeem",
Expand Down Expand Up @@ -586,7 +588,6 @@
"pool_name": "Pool Name",
"add_liquidity": "Add Liquidity",
"remove_liquidity": "Remove Liquidity",
"receivable_assets": "Receivable Assets",
"initial_rate_warning": "Note: You are setting the initial exchange rate of this pool. Make sure it reflects the exchange rate on other markets, please."
},
"swap": "Swap",
Expand Down Expand Up @@ -631,5 +632,9 @@
"total_governance_locked": "Total {{token}} Locked",
"available_to_stake": "Available to stake",
"voting_power_governance": "Voting Power {{token}}"
},
"earn_strategy": {
"withdraw_rewards_in_wrapped": "Withdraw rewards in {{wrappedCurrencySymbol}}:",
"update_position": "Update position"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ import { CoinIcon, Dd, Dl, DlGroup, Dt, Flex, P } from '@/component-library';
import { getTokenPrice } from '@/utils/helpers/prices';
import { Prices } from '@/utils/hooks/api/use-get-prices';

type WithdrawAssetsProps = {
pooledAmounts: MonetaryAmount<CurrencyExt>[];
type ReceivableAssetsProps = {
assetAmounts: MonetaryAmount<CurrencyExt>[];
prices?: Prices;
};

const WithdrawAssets = ({ pooledAmounts, prices }: WithdrawAssetsProps): JSX.Element => {
const ReceivableAssets = ({ assetAmounts: pooledAmounts, prices }: ReceivableAssetsProps): JSX.Element => {
const { t } = useTranslation();

return (
<Flex direction='column' gap='spacing4'>
<P align='center' size='xs'>
{t('amm.pools.receivable_assets')}
{t('receivable_assets')}
</P>
<Dl direction='column' gap='spacing2'>
{pooledAmounts.map((amount) => {
Expand Down Expand Up @@ -50,7 +50,7 @@ const WithdrawAssets = ({ pooledAmounts, prices }: WithdrawAssetsProps): JSX.Ele
);
};

WithdrawAssets.displayName = 'WithdrawAssets';
ReceivableAssets.displayName = 'ReceivableAssets';

export { WithdrawAssets };
export type { WithdrawAssetsProps };
export { ReceivableAssets };
export type { ReceivableAssetsProps };
1 change: 1 addition & 0 deletions src/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export type { LoanPositionsTableProps } from './LoanPositionsTable';
export { LoanPositionsTable } from './LoanPositionsTable';
export type { PoolsTableProps } from './PoolsTable';
export { PoolsTable } from './PoolsTable';
export { ReceivableAssets } from './ReceivableAssets';
21 changes: 21 additions & 0 deletions src/lib/form/schemas/earn-strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { EarnStrategyFormType } from '@/pages/EarnStrategies/types/form';

import yup, { MaxAmountValidationParams, MinAmountValidationParams } from '../yup.custom';

type EarnStrategyValidationParams = MaxAmountValidationParams & MinAmountValidationParams;

const earnStrategySchema = (
earnStrategyFormType: EarnStrategyFormType,
params: EarnStrategyValidationParams
): yup.ObjectSchema<any> => {
return yup.object().shape({
[earnStrategyFormType]: yup
.string()
.requiredAmount(earnStrategyFormType)
.maxAmount(params)
.minAmount(params, earnStrategyFormType)
});
};

export { earnStrategySchema };
export type { EarnStrategyValidationParams };
1 change: 1 addition & 0 deletions src/lib/form/schemas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type {
WithdrawLiquidityPoolValidationParams
} from './amm';
export { depositLiquidityPoolSchema, WITHDRAW_LIQUIDITY_POOL_FIELD, withdrawLiquidityPoolSchema } from './amm';
export { earnStrategySchema } from './earn-strategy';
export type { LoanFormData, LoanValidationParams } from './loans';
export { loanSchema } from './loans';
export type { SwapFormData, SwapValidationParams } from './swap';
Expand Down
5 changes: 2 additions & 3 deletions src/pages/AMM/Pools/components/WithdrawForm/WithdrawForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
newSafeMonetaryAmount
} from '@/common/utils/utils';
import { Dd, DlGroup, Dt, Flex, TokenInput } from '@/component-library';
import { AuthCTA } from '@/components';
import { AuthCTA, ReceivableAssets } from '@/components';
import { GOVERNANCE_TOKEN, TRANSACTION_FEE_AMOUNT } from '@/config/relay-chains';
import { isFormDisabled, useForm, WITHDRAW_LIQUIDITY_POOL_FIELD } from '@/lib/form';
import { WithdrawLiquidityPoolFormData, withdrawLiquidityPoolSchema } from '@/lib/form/schemas';
Expand All @@ -23,7 +23,6 @@ import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import useAccountId from '@/utils/hooks/use-account-id';

import { PoolName } from '../PoolName';
import { WithdrawAssets } from './WithdrawAssets';
import { StyledDl } from './WithdrawForm.styles';

type WithdrawFormProps = {
Expand Down Expand Up @@ -126,7 +125,7 @@ const WithdrawForm = ({ pool, slippageModalRef, onWithdraw }: WithdrawFormProps)
{...form.getFieldProps(WITHDRAW_LIQUIDITY_POOL_FIELD)}
/>
</Flex>
<WithdrawAssets pooledAmounts={pooledAmounts} prices={prices} />
<ReceivableAssets assetAmounts={pooledAmounts} prices={prices} />
<StyledDl direction='column' gap='spacing2'>
<DlGroup justifyContent='space-between'>
<Dt size='xs' color='primary'>
Expand Down
13 changes: 13 additions & 0 deletions src/pages/EarnStrategies/EarnStrategies.style.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import styled from 'styled-components';

import { theme } from '@/component-library';
const StyledEarnStrategiesLayout = styled.div`
display: grid;
gap: ${theme.spacing.spacing6};
@media (min-width: 80em) {
grid-template-columns: 1fr 1fr;
}
padding: ${theme.spacing.spacing6};
`;

export { StyledEarnStrategiesLayout };
9 changes: 8 additions & 1 deletion src/pages/EarnStrategies/EarnStrategies.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@ import { withErrorBoundary } from 'react-error-boundary';

import ErrorFallback from '@/legacy-components/ErrorFallback';

import { EarnStrategyForm } from './components/EarnStrategyForm';
import { StyledEarnStrategiesLayout } from './EarnStrategies.style';

const EarnStrategies = (): JSX.Element => {
return <h1>Earn Strategies</h1>;
return (
<StyledEarnStrategiesLayout>
<EarnStrategyForm riskVariant='low' />
</StyledEarnStrategiesLayout>
);
};

export default withErrorBoundary(EarnStrategies, {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { newMonetaryAmount } from '@interlay/interbtc-api';
import { mergeProps } from '@react-aria/utils';
import { useTranslation } from 'react-i18next';

import { convertMonetaryAmountToValueInUSD, newSafeMonetaryAmount } from '@/common/utils/utils';
import { TokenInput } from '@/component-library';
import { AuthCTA } from '@/components';
import { TRANSACTION_FEE_AMOUNT, WRAPPED_TOKEN, WRAPPED_TOKEN_SYMBOL } from '@/config/relay-chains';
import { earnStrategySchema, isFormDisabled, useForm } from '@/lib/form';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
import { useTransaction } from '@/utils/hooks/transaction';

import { EarnStrategyDepositFormData } from '../../../types/form';
import { EarnStrategyFormBaseProps } from '../EarnStrategyForm';
import { StyledEarnStrategyFormContent } from '../EarnStrategyForm.style';
import { EarnStrategyFormFees } from '../EarnStrategyFormFees';

const EarnStrategyDepositForm = ({ riskVariant, hasActiveStrategy }: EarnStrategyFormBaseProps): JSX.Element => {
const { getAvailableBalance } = useGetBalances();
const prices = useGetPrices();
const { t } = useTranslation();
// TODO: add transaction
const transaction = useTransaction();

const handleSubmit = (data: EarnStrategyDepositFormData) => {
// TODO: Execute transaction with params
// transaction.execute();
console.log(`transaction should be executed with parameters: ${data}, ${riskVariant}`);
};

const minAmount = newMonetaryAmount(1, WRAPPED_TOKEN);
const maxDepositAmount = getAvailableBalance(WRAPPED_TOKEN_SYMBOL) || newMonetaryAmount(0, WRAPPED_TOKEN);

const form = useForm<EarnStrategyDepositFormData>({
initialValues: { deposit: '' },
validationSchema: earnStrategySchema('deposit', { maxAmount: maxDepositAmount, minAmount }),
onSubmit: handleSubmit
});

const inputMonetaryAmount = newSafeMonetaryAmount(form.values['deposit'] || 0, WRAPPED_TOKEN, true);
const inputUSDValue = convertMonetaryAmountToValueInUSD(inputMonetaryAmount, prices?.[WRAPPED_TOKEN_SYMBOL].usd);
const isSubmitButtonDisabled = isFormDisabled(form);

return (
<form onSubmit={form.handleSubmit}>
<StyledEarnStrategyFormContent>
<TokenInput
placeholder='0.00'
ticker={WRAPPED_TOKEN_SYMBOL}
aria-label={t('forms.field_amount', { field: t('deposit') })}
balance={maxDepositAmount?.toString()}
humanBalance={maxDepositAmount?.toString()}
valueUSD={inputUSDValue ?? undefined}
{...mergeProps(form.getFieldProps('deposit'))}
/>
<EarnStrategyFormFees amount={TRANSACTION_FEE_AMOUNT} />
<AuthCTA type='submit' size='large' disabled={isSubmitButtonDisabled} loading={transaction.isLoading}>
{hasActiveStrategy ? t('earn_strategy.update_position') : t('deposit')}
</AuthCTA>
</StyledEarnStrategyFormContent>
</form>
);
};

export { EarnStrategyDepositForm };
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { EarnStrategyDepositForm } from './EarnStrategyDepositForm';
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import styled from 'styled-components';

import { Dl, Flex, theme } from '@/component-library';

const StyledEarnStrategyForm = styled(Flex)`
margin-top: ${theme.spacing.spacing8};
background: ${theme.colors.bgPrimary};
padding: ${theme.spacing.spacing6};
border-radius: ${theme.rounded.md};
`;

const StyledDl = styled(Dl)`
background-color: ${theme.card.bg.secondary};
padding: ${theme.spacing.spacing4};
font-size: ${theme.text.xs};
border-radius: ${theme.rounded.rg};
`;

const StyledEarnStrategyFormContent = styled(Flex)`
margin-top: ${theme.spacing.spacing8};
flex-direction: column;
gap: ${theme.spacing.spacing8};
`;

const StyledSwitchLabel = styled('label')`
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
font-weight: ${theme.fontWeight.bold};
`;

export { StyledDl, StyledEarnStrategyForm, StyledEarnStrategyFormContent, StyledSwitchLabel };
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { newMonetaryAmount } from '@interlay/interbtc-api';

import { Tabs, TabsItem } from '@/component-library';
import { WRAPPED_TOKEN } from '@/config/relay-chains';

import { EarnStrategyFormType, EarnStrategyRiskVariant } from '../../types/form';
import { EarnStrategyDepositForm } from './EarnStrategyDepositForm';
import { StyledEarnStrategyForm } from './EarnStrategyForm.style';
import { EarnStrategyWithdrawalForm } from './EarnStrategyWithdrawalForm';

interface EarnStrategyFormProps {
riskVariant: EarnStrategyRiskVariant;
}

interface EarnStrategyFormBaseProps extends EarnStrategyFormProps {
hasActiveStrategy: boolean | undefined;
}

type TabData = { type: EarnStrategyFormType; title: string };

const tabs: Array<TabData> = [
{
type: 'deposit',
title: 'Deposit'
},
{
type: 'withdraw',
title: 'Withdraw'
}
];

const EarnStrategyForm = ({ riskVariant }: EarnStrategyFormProps): JSX.Element => {
// TODO: replace with actually withdrawable amount once we know how to get that information,
// for now it's statically set for display purposes
const maxWithdrawableAmount = newMonetaryAmount(1.337, WRAPPED_TOKEN, true);
const hasActiveStrategy = maxWithdrawableAmount && !maxWithdrawableAmount.isZero();

return (
<StyledEarnStrategyForm>
<Tabs fullWidth size='large'>
{tabs.map(({ type, title }) => (
<TabsItem key={type} title={title}>
{type === 'deposit' ? (
<EarnStrategyDepositForm key={type} riskVariant={riskVariant} hasActiveStrategy={hasActiveStrategy} />
) : (
<EarnStrategyWithdrawalForm
key={type}
riskVariant={riskVariant}
hasActiveStrategy={hasActiveStrategy}
maxWithdrawableAmount={maxWithdrawableAmount}
/>
)}
</TabsItem>
))}
</Tabs>
</StyledEarnStrategyForm>
);
};

export { EarnStrategyForm };
export type { EarnStrategyFormBaseProps, EarnStrategyFormProps };
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { GovernanceCurrency } from '@interlay/interbtc-api';
import { MonetaryAmount } from '@interlay/monetary-js';
import { useTranslation } from 'react-i18next';

import { displayMonetaryAmountInUSDFormat } from '@/common/utils/utils';
import { Dd, DlGroup, Dt } from '@/component-library';
import { getTokenPrice } from '@/utils/helpers/prices';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';

import { StyledDl } from './EarnStrategyForm.style';

interface EarnStrategyFormFeesProps {
amount: MonetaryAmount<GovernanceCurrency>;
}

const EarnStrategyFormFees = ({ amount }: EarnStrategyFormFeesProps): JSX.Element => {
const prices = useGetPrices();
const { t } = useTranslation();

return (
<StyledDl direction='column' gap='spacing2'>
<DlGroup justifyContent='space-between'>
<Dt size='xs' color='primary'>
{t('fees')}
</Dt>
<Dd size='xs'>
{amount.toHuman()} {amount.currency.ticker} (
{displayMonetaryAmountInUSDFormat(amount, getTokenPrice(prices, amount.currency.ticker)?.usd)})
</Dd>
</DlGroup>
</StyledDl>
);
};

export { EarnStrategyFormFees };
Loading

2 comments on commit 918e944

@vercel
Copy link

@vercel vercel bot commented on 918e944 May 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 918e944 May 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.