Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/yield-plus-pr02-shared-groundwork.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@venusprotocol/evm": patch
---

extract shared form, validation, balance, and table building blocks needed by Yield+ and reuse them in existing market flows
1 change: 1 addition & 0 deletions apps/evm/src/assets/styles/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
* {
&::-webkit-scrollbar {
width: 4px;
height: 4px;
}

/* Track */
Expand Down
2 changes: 1 addition & 1 deletion apps/evm/src/components/Apy/PrimeBadge/PrimeApy/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const PrimeApy: React.FC<PrimeApyProps> = ({ apyPercentage, className, ..
return (
<SenaryButton
className={cn(
'hover:border-lightGrey h-6 rounded-full p-1 font-normal whitespace-nowrap font-semibold',
'hover:border-lightGrey h-6 rounded-full p-1 whitespace-nowrap font-semibold shrink-0',
className,
)}
{...otherProps}
Expand Down
5 changes: 3 additions & 2 deletions apps/evm/src/components/Apy/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import { PrimeBadge } from './PrimeBadge';
export interface ApyProps {
asset: Asset;
type: 'supply' | 'borrow';
showPrimeSimulation?: boolean;
className?: string;
}

export const Apy: React.FC<ApyProps> = ({ asset, type, className }) => {
export const Apy: React.FC<ApyProps> = ({ asset, type, showPrimeSimulation = true, className }) => {
const combinedDistributionApys = getCombinedDistributionApys({ asset });

const baseApyPercentage =
Expand Down Expand Up @@ -89,7 +90,7 @@ export const Apy: React.FC<ApyProps> = ({ asset, type, className }) => {
<p className={cn(shouldBeGreyedOut && 'text-grey')}>{readableApy}</p>
)}

{isPrimeAsset && !isApyBoostedByPrime && (
{showPrimeSimulation && isPrimeAsset && !isApyBoostedByPrime && (
<PrimeBadge
type={type}
token={asset.vToken.underlyingToken}
Expand Down
32 changes: 32 additions & 0 deletions apps/evm/src/components/AvailableBalance/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { cn } from '@venusprotocol/ui';
import { LabeledInlineContent } from 'components/LabeledInlineContent';
import { useTranslation } from 'libs/translations';

export interface AvailableBalanceProps {
readableBalance: string;
label?: string;
onClick?: () => void;
}

export const AvailableBalance: React.FC<AvailableBalanceProps> = ({
onClick,
label,
readableBalance,
}) => {
const { t } = useTranslation();

const isDisabled = !onClick;

return (
<LabeledInlineContent label={label || t('availableBalance.label')}>
<button
className={cn('transition-colors', !isDisabled && 'cursor-pointer hover:text-blue')}
type="button"
disabled={isDisabled}
onClick={onClick}
>
{readableBalance}
</button>
</LabeledInlineContent>
);
};
102 changes: 43 additions & 59 deletions apps/evm/src/components/BalanceUpdates/index.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,21 @@
import type BigNumber from 'bignumber.js';
import { LabeledInlineContent, type LabeledInlineContentProps, ValueUpdate } from 'components';
import BigNumber from 'bignumber.js';

import { LabeledValueUpdate, type LabeledValueUpdateProps } from 'components';
import { useTranslation } from 'libs/translations';
import type { BalanceMutation, Pool } from 'types';
import {
areAddressesEqual,
formatCentsToReadableValue,
formatTokensToReadableValue,
} from 'utilities';

interface Row {
labeledInlineContentProps: LabeledInlineContentProps;
readableAmountDollars?: string;
}
import { areAddressesEqual, formatTokensToReadableValue } from 'utilities';

export interface BalanceUpdatesProps {
pool: Pool;
simulatedPool?: Pool;
balanceMutations?: BalanceMutation[];
}

export const BalanceUpdates: React.FC<BalanceUpdatesProps> = ({
pool,
simulatedPool,
balanceMutations = [],
}) => {
export const BalanceUpdates: React.FC<BalanceUpdatesProps> = ({ pool, balanceMutations = [] }) => {
const { t } = useTranslation();

const balanceUpdateRows: Row[] = balanceMutations.reduce<Row[]>((acc, balanceMutation) => {
const balanceUpdateRows: LabeledValueUpdateProps[] = balanceMutations.reduce<
LabeledValueUpdateProps[]
>((acc, balanceMutation) => {
// Skip VAI updates
if (balanceMutation.type === 'vai') {
return acc;
Expand All @@ -36,29 +25,39 @@ export const BalanceUpdates: React.FC<BalanceUpdatesProps> = ({
areAddressesEqual(asset.vToken.address, balanceMutation.vTokenAddress),
);

const simulatedAsset = simulatedPool?.assets.find(asset =>
areAddressesEqual(asset.vToken.address, balanceMutation.vTokenAddress),
);

if (!asset) {
// This case should never happen
return acc;
}

let label: undefined | string;
let balanceTokens: undefined | BigNumber;
let simulatedBalanceTokens: undefined | BigNumber;
let balanceTokens = balanceMutation.balanceTokens;

if (!balanceTokens) {
balanceTokens =
balanceMutation.action === 'borrow' || balanceMutation.action === 'repay'
? asset.userBorrowBalanceTokens
: asset.userSupplyBalanceTokens;
}

let simulatedBalanceTokens: BigNumber | undefined;

if (balanceMutation.amountTokens.isGreaterThan(0)) {
simulatedBalanceTokens =
balanceMutation.action === 'supply' || balanceMutation.action === 'borrow'
? balanceTokens.plus(balanceMutation.amountTokens)
: balanceTokens.minus(balanceMutation.amountTokens);

if (balanceMutation.action === 'borrow' || balanceMutation.action === 'repay') {
label = t('accountData.balanceUpdate.borrowBalance');
// Clamp balance to 0
simulatedBalanceTokens = BigNumber.max(simulatedBalanceTokens, 0);
}

balanceTokens = asset.userBorrowBalanceTokens;
simulatedBalanceTokens = simulatedAsset?.userBorrowBalanceTokens;
} else {
label = t('accountData.balanceUpdate.supplyBalance');
let label = balanceMutation.label;

balanceTokens = asset.userSupplyBalanceTokens;
simulatedBalanceTokens = simulatedAsset?.userSupplyBalanceTokens;
if (!label) {
label =
balanceMutation.action === 'borrow' || balanceMutation.action === 'repay'
? t('accountData.balanceUpdate.borrowBalance')
: t('accountData.balanceUpdate.supplyBalance');
}

const original = formatTokensToReadableValue({
Expand All @@ -79,40 +78,25 @@ export const BalanceUpdates: React.FC<BalanceUpdatesProps> = ({
? simulatedBalanceTokens.minus(balanceTokens)
: undefined;

let readableAmountDollars = updateAmountTokens
? formatCentsToReadableValue({
value: asset.tokenPriceCents.times(updateAmountTokens).absoluteValue(),
})
const deltaAmountCents = updateAmountTokens
? asset.tokenPriceCents.times(updateAmountTokens)
: undefined;

if (readableAmountDollars && updateAmountTokens) {
const sign = updateAmountTokens.isLessThan(0) ? '-' : '+';
readableAmountDollars = `${sign} ${readableAmountDollars}`;
}

const row: Row = {
readableAmountDollars,
labeledInlineContentProps: {
iconSrc: asset.vToken.underlyingToken,
label,
children: <ValueUpdate original={original} update={update} />,
},
const row: LabeledValueUpdateProps = {
deltaAmountCents,
iconSrc: asset.vToken.underlyingToken,
label,
original,
update,
};

return [...acc, row];
}, []);

return (
<div className="space-y-2">
{balanceUpdateRows.map(({ labeledInlineContentProps, readableAmountDollars }, index) => (
<div
className="flex flex-col items-end"
key={labeledInlineContentProps.label?.toString() ?? index}
>
<LabeledInlineContent {...labeledInlineContentProps} />

{readableAmountDollars && <p className="text-grey text-sm">{readableAmountDollars}</p>}
</div>
{balanceUpdateRows.map((row, index) => (
<LabeledValueUpdate key={row.label?.toString() ?? index} {...row} />
))}
</div>
);
Expand Down
9 changes: 8 additions & 1 deletion apps/evm/src/components/ButtonGroup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ export const ButtonGroup: React.FC<ButtonGroupProps> = ({
buttonClassName,
buttonSize,
}) => {
const handleButtonClick = (e: React.MouseEvent, index: number) => {
e.preventDefault();
e.stopPropagation();

onButtonClick(index);
};

return (
<div
className={cn(
Expand All @@ -35,7 +42,7 @@ export const ButtonGroup: React.FC<ButtonGroupProps> = ({
{buttonLabels.map((label, index) => (
<SecondaryButton
key={`button-group-button-${label}`}
onClick={() => onButtonClick(index)}
onClick={e => handleButtonClick(e, index)}
className={cn(
'flex-1 border-transparent hover:border-transparent hover:text-white active:bg-blue active:border-blue',
!fullWidth && 'max-sm:flex-auto',
Expand Down
2 changes: 1 addition & 1 deletion apps/evm/src/components/Cell/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ export const Cell: React.FC<CellProps> = ({ className, tooltip, label, value, ..
</div>
)}

<p className="text-lg">{value}</p>
<p className="text-p3s">{value}</p>
</div>
);
14 changes: 6 additions & 8 deletions apps/evm/src/components/CellGroup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ export const CellGroup: React.FC<CellGroupProps> = ({
}) => (
<Card
className={cn(
'border-0',
'min-w-0 border-0',
variant === 'primary' &&
'p-4 grid grid-cols-2 rounded-xl gap-4 sm:flex sm:flex-wrap sm:gap-0 sm:px-6 sm:py-4',
variant === 'secondary' &&
'p-0 flex overflow-y-auto scrollbar-hidden bg-transparent sm:p-0 md:p-0 xl:p-0',
'p-0 flex overflow-x-auto overflow-y-hidden scrollbar-hidden bg-transparent sm:p-0 md:p-0 xl:p-0',
variant === 'tertiary' &&
'gap-2 bg-transparent p-0 grid grid-cols-2 sm:p-0 xl:bg-cards xl:flex xl:p-6 xl:flex-wrap xl:rounded-xl xl:gap-x-0',
className,
Expand All @@ -37,13 +37,11 @@ export const CellGroup: React.FC<CellGroupProps> = ({
key={`cell-group-item-${cell.label}`}
{...cell}
className={cn(
'xl:bg-transparent',
variant === 'primary' &&
'sm:px-6 sm:first-of-type:pl-0 sm:last-of-type:pr-0 sm:border-r sm:last-of-type:border-r-0 sm:border-lightGrey',
variant === 'secondary' &&
'px-4 md:px-6 first-of-type:pl-0 last-of-type:pr-0 border-r border-r-white/10 last-of-type:border-r-0',
'shrink-0 xl:bg-transparent',
variant === 'primary' && 'sm:px-6 sm:first-of-type:pl-0 sm:last-of-type:pr-0',
variant === 'secondary' && 'px-4 md:px-6 first-of-type:pl-0 last-of-type:pr-0',
variant === 'tertiary' &&
'bg-cards rounded-xl p-4 xl:py-0 xl:px-6 xl:rounded-none xl:first-of-type:pl-0 xl:last-of-type:pr-0 xl:border-r xl:last-of-type:border-r-0 xl:border-lightGrey',
'bg-cards rounded-xl p-4 xl:py-0 xl:px-6 xl:rounded-none xl:first-of-type:pl-0 xl:last-of-type:pr-0',
cell.className,
)}
/>
Expand Down
27 changes: 27 additions & 0 deletions apps/evm/src/components/LabeledSlider/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Slider } from 'components';
import { useTranslation } from 'libs/translations';

export interface LabeledSliderProps {
value: number;
onChange: (newValue: number) => void;
disabled?: boolean;
}

export const LabeledSlider: React.FC<LabeledSliderProps> = ({
value,
onChange,
disabled = false,
}) => {
const { t } = useTranslation();

return (
<div className="space-y-2">
<Slider disabled={disabled} value={value} onChange={onChange} max={100} step={1} />

<div className="flex justify-between text-grey text-xs">
<p>{t('operationForm.riskSlider.0')}</p>
<p>{t('operationForm.riskSlider.100')}</p>
</div>
</div>
);
};
45 changes: 45 additions & 0 deletions apps/evm/src/components/LabeledValueUpdate/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { cn } from '@venusprotocol/ui';
import BigNumber from 'bignumber.js';

import {
LabeledInlineContent,
type LabeledInlineContentProps,
} from 'components/LabeledInlineContent';
import { ValueUpdate, type ValueUpdateProps } from 'components/ValueUpdate';
import { formatCentsToReadableValue } from 'utilities';

export interface LabeledValueUpdateProps
extends Omit<LabeledInlineContentProps, 'children'>,
Pick<ValueUpdateProps, 'original' | 'update'> {
deltaAmountCents?: BigNumber.Value;
className?: string;
}

export const LabeledValueUpdate: React.FC<LabeledValueUpdateProps> = ({
className,
deltaAmountCents,
original,
update,
...otherProps
}) => {
let readableAmountDollars = deltaAmountCents
? formatCentsToReadableValue({
value: new BigNumber(deltaAmountCents).absoluteValue(),
})
: undefined;

if (readableAmountDollars && deltaAmountCents) {
const sign = new BigNumber(deltaAmountCents).isLessThan(0) ? '-' : '+';
readableAmountDollars = `${sign} ${readableAmountDollars}`;
}

return (
<div className={cn('flex flex-col items-end', className)}>
<LabeledInlineContent {...otherProps}>
<ValueUpdate original={original} update={update} />
</LabeledInlineContent>

{readableAmountDollars && <p className="text-grey text-sm">{readableAmountDollars}</p>}
</div>
);
};
12 changes: 9 additions & 3 deletions apps/evm/src/components/LayeredValues/index.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import { cn } from '@venusprotocol/ui';

export interface LayeredValuesProps {
topValue: string | number;
bottomValue: string | number;
bottomValue?: string | number;
className?: string;
bottomValueClassName?: string;
topValueClassName?: string;
}

export const LayeredValues: React.FC<LayeredValuesProps> = ({
topValue,
bottomValue,
className,
topValueClassName,
bottomValueClassName,
}) => (
<div className={className}>
<p>{topValue}</p>
<p className="text-grey">{bottomValue}</p>
<p className={topValueClassName}>{topValue}</p>
{bottomValue && <p className={cn('text-grey', bottomValueClassName)}>{bottomValue}</p>}
</div>
);

Expand Down
Loading
Loading