Skip to content

Commit 8e8f709

Browse files
committed
feat: add base Boost tab
1 parent efa938f commit 8e8f709

10 files changed

Lines changed: 788 additions & 2 deletions

File tree

apps/evm/src/libs/translations/translations/en.json

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -658,8 +658,17 @@
658658
},
659659
"operationForm": {
660660
"availableAmount": "Available",
661+
"boost": {
662+
"flows": {
663+
"borrow": "Borrow {{ tokenSymbol }}",
664+
"supply": "Supply {{ tokenSymbol }}",
665+
"swap": "Swap"
666+
},
667+
"supply": "Supply"
668+
},
661669
"boostTabAltText": "Rocket icon",
662670
"boostTabTitle": "Boost",
671+
"borrowBalance": "Borrow balance ({{ tokenSymbol }})",
663672
"borrowTabTitle": "Borrow",
664673
"error": {
665674
"borrowCapReached": "The borrow cap of {{assetBorrowCap}} has been reached for this pool. You can not borrow from this market anymore until loans are repaid or its borrow cap is increased.",
@@ -677,6 +686,10 @@
677686
"unwrappingUnsupported": "Unwrapping unsupported",
678687
"wrappingUnsupported": "Wrapping unsupported"
679688
},
689+
"leverage": {
690+
"label": "Leverage",
691+
"tooltip": "TRANSLATION NEEDED"
692+
},
680693
"receiveNativeToken": {
681694
"label": "Receive {{tokenSymbol}}",
682695
"tooltip": "Unwrap borrowed {{wrappedNativeTokenSymbol}} to {{nativeTokenSymbol}}"
@@ -693,6 +706,7 @@
693706
},
694707
"safeMaxButtonLabel": "SAFE MAX",
695708
"submitButtonLabel": {
709+
"boost": "Boost",
696710
"borrow": "Borrow",
697711
"enterValidAmount": "Enter valid amount",
698712
"repay": "Repay",
@@ -702,6 +716,7 @@
702716
"supply": {
703717
"collateral": "Collateral"
704718
},
719+
"supplyBalance": "Supply balance ({{ tokenSymbol }})",
705720
"supplyTabTitle": "Supply",
706721
"swapDetails": {
707722
"label": {
@@ -1523,4 +1538,4 @@
15231538
},
15241539
"withdrawTabTitle": "Withdraw"
15251540
}
1526-
}
1541+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Icon } from 'components';
2+
import { useTranslation } from 'libs/translations';
3+
4+
export interface FlowProps {
5+
fromTokenSymbol: string;
6+
toTokenSymbol: string;
7+
}
8+
9+
export const Flow: React.FC<FlowProps> = ({ fromTokenSymbol, toTokenSymbol }) => {
10+
const { t } = useTranslation();
11+
12+
return (
13+
<div className="flex items-center gap-x-[2px] text-grey text-sm">
14+
<p>
15+
{t('operationForm.boost.flows.borrow', {
16+
tokenSymbol: fromTokenSymbol,
17+
})}
18+
</p>
19+
20+
<Icon className="w-5 h-5" name="chevronRight" />
21+
22+
<p>{t('operationForm.boost.flows.swap')}</p>
23+
24+
<Icon className="w-5 h-5" name="chevronRight" />
25+
26+
<p>
27+
{t('operationForm.boost.flows.supply', {
28+
tokenSymbol: toTokenSymbol,
29+
})}
30+
</p>
31+
</div>
32+
);
33+
};
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import BigNumber from 'bignumber.js';
2+
import { useState } from 'react';
3+
4+
import {
5+
LabeledInlineContent,
6+
Slider,
7+
TextField,
8+
type TextFieldProps,
9+
ValueUpdate,
10+
} from 'components';
11+
import { useTranslation } from 'libs/translations';
12+
import type { Asset } from 'types';
13+
import { formatTokensToReadableValue, getDecimals } from 'utilities';
14+
15+
const MAX_LEVERAGE_FACTOR_DECIMALS = 1;
16+
17+
export interface LeverageSliderProps {
18+
fromAsset: Asset;
19+
toAsset: Asset;
20+
value: string;
21+
onChange: (newValue: string) => void;
22+
disabled?: boolean;
23+
}
24+
25+
export const LeverageSlider: React.FC<LeverageSliderProps> = ({
26+
value,
27+
fromAsset,
28+
toAsset,
29+
onChange,
30+
disabled = false,
31+
}) => {
32+
const { t } = useTranslation();
33+
const [isLeverageFactorInputFocused, setIsLeverageFactorInputFocused] = useState(false);
34+
35+
const maxLeverageFactor = Number(
36+
new BigNumber(1 / (1 - toAsset.userCollateralFactor)).toFixed(1, BigNumber.ROUND_DOWN),
37+
);
38+
39+
const step = maxLeverageFactor / 5;
40+
const steps = [];
41+
42+
for (let v = 0; v < maxLeverageFactor + 0.0001; v += step) {
43+
steps.push(Number(v.toFixed(1)));
44+
}
45+
46+
steps[steps.length - 1] = maxLeverageFactor;
47+
48+
const handleChange: TextFieldProps['onChange'] = ({ currentTarget: { value } }) => {
49+
const valueDecimals = getDecimals({ value });
50+
51+
if (valueDecimals <= MAX_LEVERAGE_FACTOR_DECIMALS) {
52+
onChange(value);
53+
}
54+
};
55+
56+
return (
57+
<div className="space-y-4">
58+
<LabeledInlineContent
59+
label={t('operationForm.leverage.label')}
60+
tooltip={t('operationForm.leverage.tooltip')}
61+
>
62+
<TextField
63+
disabled={disabled}
64+
onChange={handleChange}
65+
onFocus={() => setIsLeverageFactorInputFocused(true)}
66+
onBlur={() => setIsLeverageFactorInputFocused(false)}
67+
value={isLeverageFactorInputFocused ? value : `${value || 0}x`}
68+
step={0.1}
69+
variant="tertiary"
70+
size="xxs"
71+
className="w-[54px]"
72+
type={isLeverageFactorInputFocused ? 'number' : 'string'}
73+
min={0}
74+
max={maxLeverageFactor}
75+
/>
76+
</LabeledInlineContent>
77+
78+
<div className="space-y-2">
79+
<Slider
80+
disabled={disabled}
81+
value={Number(value)}
82+
onChange={value => onChange(String(value))}
83+
max={maxLeverageFactor}
84+
step={0.1}
85+
/>
86+
87+
<div className="flex justify-between">
88+
{steps.map(step => (
89+
<p className="text-grey text-xs">{step}x</p>
90+
))}
91+
</div>
92+
</div>
93+
94+
<div className="space-y-2">
95+
<LabeledInlineContent
96+
label={t('operationForm.supplyBalance', {
97+
tokenSymbol: toAsset.vToken.underlyingToken.symbol,
98+
})}
99+
iconSrc={toAsset.vToken.underlyingToken}
100+
>
101+
<ValueUpdate
102+
original={formatTokensToReadableValue({
103+
value: toAsset.userSupplyBalanceTokens,
104+
token: toAsset.vToken.underlyingToken,
105+
addSymbol: false,
106+
})}
107+
// TODO: add hypothetical supply balance
108+
/>
109+
</LabeledInlineContent>
110+
111+
<LabeledInlineContent
112+
label={t('operationForm.borrowBalance', {
113+
tokenSymbol: fromAsset.vToken.underlyingToken.symbol,
114+
})}
115+
iconSrc={fromAsset.vToken.underlyingToken}
116+
>
117+
<ValueUpdate
118+
original={formatTokensToReadableValue({
119+
value: fromAsset.userBorrowBalanceTokens,
120+
token: fromAsset.vToken.underlyingToken,
121+
addSymbol: false,
122+
})}
123+
// TODO: add hypothetical borrow balance
124+
/>
125+
</LabeledInlineContent>
126+
</div>
127+
</div>
128+
);
129+
};
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { useMemo } from 'react';
2+
3+
import { PrimaryButton } from 'components';
4+
import { SwitchChain } from 'containers/SwitchChain';
5+
import { useTranslation } from 'libs/translations';
6+
import type { FormErrorCode } from '../useForm';
7+
8+
export interface SubmitSectionProps {
9+
isFormValid: boolean;
10+
isFormSubmitting: boolean;
11+
formErrorCode?: FormErrorCode;
12+
}
13+
14+
export const SubmitSection: React.FC<SubmitSectionProps> = ({
15+
isFormValid,
16+
isFormSubmitting,
17+
formErrorCode,
18+
}) => {
19+
const { t } = useTranslation();
20+
21+
const submitButtonLabel = useMemo(() => {
22+
if (!isFormValid && formErrorCode !== 'REQUIRES_RISK_ACKNOWLEDGEMENT') {
23+
return t('operationForm.submitButtonLabel.enterValidAmount');
24+
}
25+
26+
return t('operationForm.submitButtonLabel.boost');
27+
}, [isFormValid, t, formErrorCode]);
28+
29+
let dom = (
30+
<PrimaryButton
31+
type="submit"
32+
loading={isFormSubmitting}
33+
disabled={!isFormValid || isFormSubmitting}
34+
className="w-full"
35+
>
36+
{submitButtonLabel}
37+
</PrimaryButton>
38+
);
39+
40+
if (isFormValid) {
41+
dom = <SwitchChain>{dom}</SwitchChain>;
42+
}
43+
44+
return dom;
45+
};
46+
47+
export default SubmitSection;

0 commit comments

Comments
 (0)