Skip to content

Commit

Permalink
feat(staking): preview dialog for in-app staking + unstaking (#742)
Browse files Browse the repository at this point in the history
  • Loading branch information
moo-onthelawn authored Jul 3, 2024
1 parent 2d77953 commit 4b32ba4
Show file tree
Hide file tree
Showing 26 changed files with 1,412 additions and 793 deletions.
3 changes: 3 additions & 0 deletions src/components/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
DownloadIcon,
EtherscanIcon,
ExportKeysIcon,
FastForwardIcon,
FeedbackIcon,
FileIcon,
FundingChartIcon,
Expand Down Expand Up @@ -119,6 +120,7 @@ export enum IconName {
Discord = 'Discord',
Etherscan = 'Etherscan',
ExportKeys = 'ExportKeys',
FastForward = 'FastForward',
Feedback = 'Feedback',
File = 'File',
FundingChart = 'FundingChart',
Expand Down Expand Up @@ -207,6 +209,7 @@ const icons = {
[IconName.Discord]: DiscordIcon,
[IconName.Etherscan]: EtherscanIcon,
[IconName.ExportKeys]: ExportKeysIcon,
[IconName.FastForward]: FastForwardIcon,
[IconName.Feedback]: FeedbackIcon,
[IconName.File]: FileIcon,
[IconName.FundingChart]: FundingChartIcon,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { useState } from 'react';

import { Validator } from '@dydxprotocol/v4-client-js/build/node_modules/@dydxprotocol/v4-proto/src/codegen/cosmos/staking/v1beta1/staking';
import styled from 'styled-components';

import { layoutMixins } from '@/styles/layoutMixins';

import { Link } from '@/components/Link';

const URL_START = 'https://';

export const ValidatorFaviconIcon = ({
Expand Down Expand Up @@ -44,37 +39,6 @@ export const ValidatorFaviconIcon = ({
return fallback;
};

export const ValidatorName = ({ validator }: { validator?: Validator }) => {
return (
<$ValidatorName>
<ValidatorFaviconIcon
url={validator?.description?.website}
fallbackText={validator?.description?.moniker}
/>
{validator?.description?.website ? (
<Link href={validator?.description?.website}>
<$TruncatedText>{validator?.description?.moniker} </$TruncatedText>
</Link>
) : (
<$TruncatedText>{validator?.description?.moniker} </$TruncatedText>
)}
</$ValidatorName>
);
};

const $Img = styled.img`
width: 1.5em;
height: 1.5em;
border-radius: 50%;
object-fit: cover;
margin-right: 0.25em;
`;

const $ValidatorName = styled.div`
display: flex;
align-items: center;
`;

const $IconContainer = styled.div`
display: flex;
align-items: center;
Expand All @@ -88,7 +52,10 @@ const $IconContainer = styled.div`
margin-right: 0.25em;
`;

const $TruncatedText = styled.div`
${layoutMixins.textTruncate}
color: var(--color-text-1);
const $Img = styled.img`
width: 1.5em;
height: 1.5em;
border-radius: 50%;
object-fit: cover;
margin-right: 0.25em;
`;
94 changes: 94 additions & 0 deletions src/components/ValidatorIcons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { Validator } from '@dydxprotocol/v4-client-js/build/node_modules/@dydxprotocol/v4-proto/src/codegen/cosmos/staking/v1beta1/staking';
import styled from 'styled-components';

import { ValidatorFaviconIcon } from './ValidatorFaviconIcon';
import { WithTooltip } from './WithTooltip';

type ElementProps = {
numToShow?: number;
validators: Validator[];
};

type StyleProps = {
className?: string;
};

export const ValidatorIcons = ({
numToShow = 3,
validators,
className,
}: ElementProps & StyleProps) => {
const validatorNames = validators.map((validator) => validator.description?.moniker).join(', ');
return (
<$ValidatorIcons className={className}>
{validators?.length <= numToShow
? validators.map((validator) => (
<$ValidatorIcon
key={validator.description?.moniker}
url={validator.description?.website}
fallbackText={validator.description?.moniker}
/>
))
: validators
.slice(0, numToShow - 1)
.map((validator) => (
<$ValidatorIcon
key={validator.description?.moniker}
url={validator.description?.website}
fallbackText={validator.description?.moniker}
/>
))
.concat(
<WithTooltip tooltipString={validatorNames}>
<$OverflowIcon key="overflow">
{`+${validators.length - (numToShow - 1)}`}
</$OverflowIcon>
</WithTooltip>
)}
</$ValidatorIcons>
);
};

const $ValidatorIcons = styled.div`
--border-color: var(--color-border);
--icon-size: 2.25rem;
display: flex;
> * {
&:nth-child(1) {
z-index: 2;
}
&:nth-child(2) {
z-index: 1;
}
&:not(:first-child) {
margin-left: -0.66em;
}
}
`;

const $ValidatorIcon = styled(ValidatorFaviconIcon)`
height: var(--icon-size);
width: var(--icon-size);
margin: 0;
border: 2px solid var(--border-color);
`;

const $OverflowIcon = styled.div`
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
margin: 0;
border: 2px solid var(--border-color);
height: var(--icon-size);
width: var(--icon-size);
background-color: var(--color-layer-6);
color: var(--color-text-1);
`;
4 changes: 4 additions & 0 deletions src/constants/stakingForms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum StakeFormSteps {
EditInputs = 'EditInputs',
PreviewOrder = 'PreviewOrder',
}
1 change: 1 addition & 0 deletions src/icons/fast-forward.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/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export { default as DepthChartIcon } from './depth-chart.svg';
export { default as DiscordIcon } from './discord.svg';
export { default as DownloadIcon } from './download.svg';
export { default as ExportKeysIcon } from './export-keys.svg';
export { default as FastForwardIcon } from './fast-forward.svg';
export { default as FeedbackIcon } from './feedback.svg';
export { default as FileIcon } from './file.svg';
export { default as FundingChartIcon } from './funding-chart.svg';
Expand Down
2 changes: 1 addition & 1 deletion src/pages/token/rewards/UnbondingPanels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { layoutMixins } from '@/styles/layoutMixins';
import { AssetIcon } from '@/components/AssetIcon';
import { Output, OutputType } from '@/components/Output';
import { Panel } from '@/components/Panel';
import { ValidatorFaviconIcon } from '@/components/ValidatorName';
import { ValidatorFaviconIcon } from '@/components/ValidatorFaviconIcon';

import { calculateSortedUnbondingDelegations } from '@/state/accountCalculators';
import { useAppSelector } from '@/state/appTypes';
Expand Down
21 changes: 21 additions & 0 deletions src/styles/formMixins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,25 @@ export const formMixins = {
--form-input-gap: 1rem;
}
`,

stakingForm: css`
${() => inputsColumn}
--form-input-gap: 1.25rem;
--form-input-height: 3.5rem;
--form-input-height-mobile: 4rem;
--form-input-paddingY: 0.5rem;
--form-input-paddingX: 1rem;
--withReceipt-backgroundColor: var(--color-layer-2);
height: 100%;
label {
--label-textColor: var(--color-text-1);
}
@media ${breakpoints.tablet} {
--form-input-gap: 1rem;
}
`,
} satisfies Record<string, FlattenSimpleInterpolation | FlattenInterpolation<ThemeProps<any>>>;
7 changes: 7 additions & 0 deletions src/styles/layoutMixins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,13 @@ export const layoutMixins = {
min-width: 1px;
`,

textLineClamp: css`
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
-webkit-line-clamp: 2;
`,

textOverflow: css`
display: inline-block;
overflow-x: auto;
Expand Down
77 changes: 71 additions & 6 deletions src/views/dialogs/StakeDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { useState } from 'react';

import styled from 'styled-components';

import { DialogProps, StakeDialogProps } from '@/constants/dialogs';
import { DialogProps, DialogTypes, StakeDialogProps } from '@/constants/dialogs';
import { STRING_KEYS } from '@/constants/localization';
import { StakeFormSteps } from '@/constants/stakingForms';

import { useStakingAPR } from '@/hooks/useStakingAPR';
import { useStringGetter } from '@/hooks/useStringGetter';
Expand All @@ -11,24 +14,50 @@ import { layoutMixins } from '@/styles/layoutMixins';

import { AssetIcon } from '@/components/AssetIcon';
import { Dialog } from '@/components/Dialog';
import { Link } from '@/components/Link';
import { Output, OutputType } from '@/components/Output';
import { Tag, TagSign } from '@/components/Tag';
import { StakeForm } from '@/views/forms/StakeForm';
import { StakeForm } from '@/views/forms/StakingForms/StakeForm';

import { useAppDispatch } from '@/state/appTypes';
import { forceOpenDialog } from '@/state/dialogs';

export const StakeDialog = ({ setIsOpen }: DialogProps<StakeDialogProps>) => {
const stringGetter = useStringGetter();

const { chainTokenLabel } = useTokenConfigs();
const stakingApr = useStakingAPR();

const [currentStep, setCurrentStep] = useState<StakeFormSteps>(StakeFormSteps.EditInputs);

const closeDialog = () => setIsOpen?.(false);

const dialogProps: {
[key in StakeFormSteps]: {
title: string;
description: string;
slotIcon?: JSX.Element;
};
} = {
[StakeFormSteps.EditInputs]: {
title: stringGetter({ key: STRING_KEYS.STAKE }),
description: stringGetter({ key: STRING_KEYS.STAKE_DESCRIPTION }),
slotIcon: <AssetIcon symbol={chainTokenLabel} />,
},
[StakeFormSteps.PreviewOrder]: {
title: stringGetter({ key: STRING_KEYS.CONFIRM_STAKE }),
description: stringGetter({ key: STRING_KEYS.STAKE_CONFIRMATION_DESCRIPTOR }),
},
};

return (
<$Dialog
isOpen
setIsOpen={setIsOpen}
slotIcon={<AssetIcon symbol={chainTokenLabel} />}
slotIcon={dialogProps[currentStep].slotIcon}
slotFooter={<LegalDisclaimer />}
title={
<$Title>
{stringGetter({ key: STRING_KEYS.STAKE })}
{dialogProps[currentStep].title}
{stakingApr && (
<$Tag sign={TagSign.Positive}>
{stringGetter({
Expand All @@ -39,14 +68,44 @@ export const StakeDialog = ({ setIsOpen }: DialogProps<StakeDialogProps>) => {
)}
</$Title>
}
description={dialogProps[currentStep].description}
>
<StakeForm onDone={() => setIsOpen?.(false)} />
<StakeForm currentStep={currentStep} setCurrentStep={setCurrentStep} onDone={closeDialog} />
</$Dialog>
);
};

const LegalDisclaimer = () => {
const dispatch = useAppDispatch();
const stringGetter = useStringGetter();

const openKeplrDialog = () => dispatch(forceOpenDialog(DialogTypes.ExternalNavKeplr()));
const openStrideDialog = () => dispatch(forceOpenDialog(DialogTypes.ExternalNavStride()));

return (
<$LegalDisclaimer>
{stringGetter({
key: STRING_KEYS.STAKING_LEGAL_DISCLAIMER_WITH_DEFAULT,
params: {
KEPLR_DASHBOARD_LINK: (
<Link withIcon onClick={openKeplrDialog} isInline>
{stringGetter({ key: STRING_KEYS.KEPLR_DASHBOARD })}
</Link>
),
STRIDE_LINK: (
<Link withIcon onClick={openStrideDialog} isInline>
Stride
</Link>
),
},
})}
</$LegalDisclaimer>
);
};

const $Dialog = styled(Dialog)`
--dialog-content-paddingTop: var(--default-border-width);
--dialog-content-paddingBottom: 1rem;
`;

const $Title = styled.span`
Expand All @@ -60,3 +119,9 @@ const $Tag = styled(Tag)`
const $Output = styled(Output)`
display: inline-block;
`;

const $LegalDisclaimer = styled.div`
text-align: center;
color: var(--color-text-0);
font: var(--font-mini-book);
`;
2 changes: 1 addition & 1 deletion src/views/dialogs/StakingRewardDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { Tag } from '@/components/Tag';
import {
StakeRewardButtonAndReceipt,
type StakeButtonAlert,
} from '@/views/StakeRewardButtonAndReceipt';
} from '@/views/forms/StakingForms/shared/StakeRewardButtonAndReceipt';

import { getSubaccountEquity } from '@/state/accountSelectors';
import { useAppSelector } from '@/state/appTypes';
Expand Down
Loading

0 comments on commit 4b32ba4

Please sign in to comment.