Liquid Staking CTA (#281)

* 🚧 feat(token-page): Scaffold sidebar components for token page

* 🚧 feat(staking/governance): Scaffold adding Panels to Staking and Governance page

*  Update and add icons

* 🧱 Add strideZoneApp to shared config

* 🧱 Add dialogs, update ExternalLinkDialog

* 🚧 Add New Panels/Update paths

* Fix mobile Profile Panel

* 💄 fix desktop padding

*  Add New tag

* 💄 Highlight tag on StrideStakingPanel

* 💄 Single columns for panels on mobile breakpoint

* 💄 fix import nits and alignment nits on panels

* Add circular keplr icon
This commit is contained in:
Jared Vu 2024-02-07 11:17:54 -08:00 committed by GitHub
parent 26b426c9e9
commit 1a41ccaf2a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 2056 additions and 1475 deletions

File diff suppressed because it is too large Load Diff

BIN
public/third-party/keplr.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
public/third-party/stride.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -52,9 +52,9 @@ const AlertsPage = lazy(() => import('@/pages/AlertsPage'));
const ProfilePage = lazy(() => import('@/pages/Profile'));
const SettingsPage = lazy(() => import('@/pages/settings/Settings'));
const TradePage = lazy(() => import('@/pages/trade/Trade'));
const RewardsPage = lazy(() => import('@/pages/rewards/RewardsPage'));
const TermsOfUsePage = lazy(() => import('@/pages/TermsOfUsePage'));
const PrivacyPolicyPage = lazy(() => import('@/pages/PrivacyPolicyPage'));
const TokenPage = lazy(() => import('@/pages/token/Token'));
const queryClient = new QueryClient();
@ -87,7 +87,7 @@ const Content = () => {
<Route path={MarketsRoute.New} element={<NewMarket />} />
<Route path={AppRoute.Markets} element={<MarketsPage />} />
</Route>
<Route path={`/${chainTokenLabel}`} element={<RewardsPage />} />
<Route path={`/${chainTokenLabel}/*`} element={<TokenPage />} />
{isTablet && (
<>
<Route path={AppRoute.Alerts} element={<AlertsPage />} />
@ -207,6 +207,7 @@ Styled.Content = styled.div<{ isShowingHeader: boolean; isShowingFooter: boolean
Styled.Main = styled.main`
${layoutMixins.contentSectionAttached}
box-shadow: none;
grid-area: Main;

View File

@ -6,6 +6,7 @@ import { layoutMixins } from '@/styles/layoutMixins';
type ElementProps = {
title: string;
subtitle?: React.ReactNode;
slotLeft?: React.ReactNode;
slotRight?: React.ReactNode;
};
@ -16,14 +17,16 @@ type StyleProps = {
export const ContentSectionHeader = ({
title,
subtitle,
slotLeft,
slotRight,
className,
}: ElementProps & StyleProps) => (
<Styled.ContentSectionHeader className={className}>
<div>
{slotLeft}
<Styled.Header>
{title && <h3>{title}</h3>}
{subtitle && <p>{subtitle}</p>}
</div>
</Styled.Header>
{slotRight}
</Styled.ContentSectionHeader>
);
@ -40,9 +43,15 @@ Styled.ContentSectionHeader = styled.header<StyleProps>`
padding: 1rem var(--header-horizontal-padding);
> div {
${layoutMixins.column}
@media ${breakpoints.tablet} {
flex-wrap: wrap;
--header-horizontal-padding: 1.25rem;
}
`;
Styled.Header = styled.div`
${layoutMixins.column}
flex: 1;
h3 {
color: var(--color-text-2);
@ -54,9 +63,4 @@ Styled.ContentSectionHeader = styled.header<StyleProps>`
font: var(--font-small-book);
margin-top: 0.25rem;
}
@media ${breakpoints.tablet} {
flex-wrap: wrap;
--header-horizontal-padding: 1.25rem;
}
`;

View File

@ -25,6 +25,7 @@ import {
CoinsIcon,
CommentIcon,
CopyIcon,
CurrencySignIcon,
DepositIcon,
DepthChartIcon,
DiscordIcon,
@ -35,6 +36,7 @@ import {
FundingChartIcon,
GearIcon,
GiftboxIcon,
GovernanceIcon,
HelpCircleIcon,
HideIcon,
HistoryIcon,
@ -70,6 +72,7 @@ import {
StarIcon,
SunIcon,
TerminalIcon,
TokenIcon,
TradeIcon,
TransferIcon,
TriangleIcon,
@ -103,6 +106,7 @@ export enum IconName {
Coins = 'Coins',
Comment = 'Comment',
Copy = 'Copy',
CurrencySign = 'CurrencySign',
Deposit = 'Deposit',
DepthChart = 'DepthChart',
Discord = 'Discord',
@ -113,6 +117,7 @@ export enum IconName {
FundingChart = 'FundingChart',
Gear = 'Gear',
Giftbox = 'Giftbox',
Governance = 'Governance',
HelpCircle = 'HelpCircle',
Hide = 'Hide',
History = 'History',
@ -149,6 +154,7 @@ export enum IconName {
Sun = 'Sun',
Terminal = 'Terminal',
TogglesMenu = 'TogglesMenu',
Token = 'Token',
Trade = 'Trade',
Transfer = 'Transfer',
Triangle = 'Triangle',
@ -182,6 +188,7 @@ const icons = {
[IconName.Coins]: CoinsIcon,
[IconName.Comment]: CommentIcon,
[IconName.Copy]: CopyIcon,
[IconName.CurrencySign]: CurrencySignIcon,
[IconName.Deposit]: DepositIcon,
[IconName.DepthChart]: DepthChartIcon,
[IconName.Discord]: DiscordIcon,
@ -192,6 +199,7 @@ const icons = {
[IconName.FundingChart]: FundingChartIcon,
[IconName.Gear]: GearIcon,
[IconName.Giftbox]: GiftboxIcon,
[IconName.Governance]: GovernanceIcon,
[IconName.HelpCircle]: HelpCircleIcon,
[IconName.Hide]: HideIcon,
[IconName.History]: HistoryIcon,
@ -227,6 +235,7 @@ const icons = {
[IconName.Sun]: SunIcon,
[IconName.Terminal]: TerminalIcon,
[IconName.TogglesMenu]: TogglesMenuIcon,
[IconName.Token]: TokenIcon,
[IconName.Trade]: TradeIcon,
[IconName.Transfer]: TransferIcon,
[IconName.Triangle]: TriangleIcon,

View File

@ -106,8 +106,6 @@ Styled.Container = styled.div`
--stickyArea1-leftGap: var(--border-width);
min-height: var(--stickyArea-height);
${layoutMixins.withOuterAndInnerBorders}
display: grid;
grid-template: var(--withSidebar-gridTemplate);
`;
@ -120,6 +118,7 @@ Styled.Side = styled.aside`
${layoutMixins.sticky}
max-height: var(--stickyArea-height);
backdrop-filter: none;
background-color: var(--color-layer-2);
${layoutMixins.stack}
`;

View File

@ -5,6 +5,7 @@ export enum DialogTypes {
DisplaySettings = 'DisplaySettings',
ExchangeOffline = 'ExchangeOffline',
ExternalLink = 'ExternalLink',
ExternalNavStride = 'ExternalNavStride',
FillDetails = 'FillDetails',
Help = 'Help',
ExternalNavKeplr = 'ExternalNavKeplr',

View File

@ -29,6 +29,12 @@ export enum HistoryRoute {
Payments = 'payments',
}
export enum TokenRoute {
TradingRewards = 'trading-rewards',
StakingRewards = 'staking-rewards',
Governance = 'governance',
}
export enum MobileSettingsRoute {
Language = 'language',
Notifications = 'notifications',

View File

@ -33,7 +33,7 @@ export const useMarketsData = (
return Object.values(allPerpetualMarkets)
.filter(isTruthy)
.map((marketData) => ({
asset: allAssets[marketData.assetId],
asset: allAssets[marketData.assetId] ?? {},
tickSizeDecimals: marketData.configs?.tickSizeDecimals,
...marketData,
...marketData.perpetual,
@ -46,9 +46,10 @@ export const useMarketsData = (
if (searchFilter) {
return filtered.filter(
({ asset }) =>
({ asset, id }) =>
asset?.name?.toLocaleLowerCase().includes(searchFilter.toLowerCase()) ||
asset?.id?.toLocaleLowerCase().includes(searchFilter.toLowerCase())
asset?.id?.toLocaleLowerCase().includes(searchFilter.toLowerCase()) ||
id?.toLocaleLowerCase().includes(searchFilter.toLowerCase())
);
}
return filtered;

View File

@ -21,6 +21,7 @@ export interface LinksConfigs {
newMarketProposalLearnMore: string;
stakingLearnMore?: string;
keplrDashboard?: string;
strideZoneApp?: string;
accountExportLearnMore?: string;
walletLearnMore?: string;
}
@ -46,6 +47,7 @@ export const useURLConfigs = (): LinksConfigs => {
newMarketProposalLearnMore: linksConfigs.newMarketProposalLearnMore || FALLBACK_URL,
stakingLearnMore: linksConfigs.stakingLearnMore || FALLBACK_URL,
keplrDashboard: linksConfigs.keplrDashboard || FALLBACK_URL,
strideZoneApp: linksConfigs.strideZoneApp || FALLBACK_URL,
accountExportLearnMore: linksConfigs.accountExportLearnMore || FALLBACK_URL,
walletLearnMore: linksConfigs.walletLearnMore || FALLBACK_URL,
};

View File

@ -36,8 +36,8 @@ const ChaosLabsIcon: React.FC = () => {
fill={fills[1]}
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
fillRule="evenodd"
clipRule="evenodd"
d="M47.6624 4.38721C46.5167 4.38721 45.5396 4.77222 44.7521 5.54468C43.9625 6.31199 43.5687 7.27749 43.5687 8.41904C43.5687 9.55454 43.9632 10.5194 44.7514 11.2929L44.7527 11.2939C45.5403 12.0594 46.5173 12.4406 47.6624 12.4406C48.8074 12.4406 49.7845 12.0594 50.572 11.2939L50.5734 11.2929C51.3616 10.5194 51.756 9.55454 51.756 8.41904C51.756 7.27749 51.3623 6.312 50.5727 5.54469C49.7851 4.77223 48.8081 4.38721 47.6624 4.38721ZM45.4207 8.41904C45.4207 7.76676 45.6364 7.22501 46.0671 6.77549C46.5039 6.32689 47.0294 6.10561 47.6624 6.10561C48.2954 6.10561 48.8168 6.32697 49.2462 6.77466L49.2476 6.77617C49.6857 7.22615 49.9034 7.7677 49.9034 8.41904C49.9034 9.06265 49.6864 9.60104 49.2476 10.0516L49.2462 10.0531C48.8168 10.5008 48.2954 10.7222 47.6624 10.7222C47.0294 10.7222 46.5039 10.5009 46.0671 10.0523C45.6357 9.60218 45.4207 9.06359 45.4207 8.41904Z"
fill={fills[1]}
/>
@ -50,8 +50,8 @@ const ChaosLabsIcon: React.FC = () => {
fill={fills[1]}
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
fillRule="evenodd"
clipRule="evenodd"
d="M40.3586 4.56055H38.6659L35.2261 12.3667H37.1862L37.9072 10.7289H41.1173L41.8383 12.3667H43.7984L40.3586 4.56055ZM38.6686 9.00014L39.5126 7.08737L40.3559 9.00014H38.6686Z"
fill={fills[1]}
/>

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.9255 9.70119C10.9255 8.14969 9.99456 7.61773 8.13272 7.39612C6.80283 7.21876 6.53685 6.86416 6.53685 6.24348C6.53685 5.62279 6.98018 5.22393 7.86674 5.22393C8.66467 5.22393 9.10801 5.48991 9.32962 6.15485C9.37398 6.28784 9.50697 6.37647 9.63996 6.37647H10.3492C10.5265 6.37647 10.6595 6.24348 10.6595 6.06623V6.02186C10.4822 5.04658 9.68422 4.29301 8.66467 4.20439V3.14047C8.66467 2.96312 8.53169 2.83013 8.31007 2.78577H7.64513C7.46777 2.78577 7.33479 2.91876 7.29042 3.14047V4.16002C5.96053 4.33737 5.11834 5.22393 5.11834 6.33221C5.11834 7.79508 6.0049 8.3713 7.86674 8.59302C9.10801 8.81463 9.50697 9.08061 9.50697 9.78992C9.50697 10.4992 8.88629 10.9868 8.0441 10.9868C6.89145 10.9868 6.49249 10.4991 6.3595 9.83418C6.31524 9.65693 6.18225 9.5682 6.04926 9.5682H5.29559C5.11834 9.5682 4.98535 9.70119 4.98535 9.87854V9.92291C5.1626 11.0311 5.87191 11.829 7.33479 12.0507V13.1146C7.33479 13.2919 7.46778 13.4249 7.68939 13.4692H8.35433C8.53169 13.4692 8.66468 13.3363 8.70904 13.1146V12.0507C10.0389 11.829 10.9255 10.8981 10.9255 9.70119Z" fill="currentColor"/>
<path d="M5.73821 14.3561C2.2805 13.1149 0.507281 9.25822 1.79291 5.84477C2.45786 3.98293 3.92073 2.56441 5.73821 1.89947C5.91557 1.81085 6.00419 1.67786 6.00419 1.45614V0.835558C6.00419 0.658204 5.91557 0.525216 5.73821 0.480957C5.69385 0.480957 5.60522 0.480957 5.56086 0.525215C1.34958 1.8551 -0.955596 6.33247 0.374292 10.5437C1.17223 13.0262 3.07843 14.9324 5.56086 15.7303C5.73821 15.8189 5.91557 15.7303 5.95982 15.553C6.00419 15.5087 6.00419 15.4643 6.00419 15.3757V14.755C6.00419 14.622 5.8712 14.4448 5.73821 14.3561ZM10.4372 0.525215C10.2598 0.436592 10.0825 0.525216 10.0382 0.70257C9.99385 0.746935 9.99385 0.791193 9.99385 0.879924V1.5005C9.99385 1.67786 10.1268 1.8551 10.2598 1.94383C13.7175 3.18499 15.4908 7.04167 14.2051 10.4551C13.5402 12.317 12.0773 13.7355 10.2598 14.4004C10.0825 14.489 9.99385 14.622 9.99385 14.8438V15.4643C9.99385 15.6417 10.0825 15.7747 10.2598 15.8189C10.3042 15.8189 10.3928 15.8189 10.4372 15.7747C14.6485 14.4448 16.9536 9.96742 15.6238 5.75615C14.8258 3.22936 12.8752 1.32315 10.4372 0.525215Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

3
src/icons/governance.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 13.5V8M10.5 13.5V8M5.5 13.5V8M2 5.5L8 1.5L14 5.5M13 13.5V6.388C11.3459 6.12904 9.67421 5.99931 8 6C6.29933 6 4.62933 6.13333 3 6.388V13.5M2 13.5H14M8 4H8.00533V4.00533H8V4Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 360 B

View File

@ -1,4 +1,3 @@
import ChaosLabsIcon from './chaos-labs';
export { default as AddressConnectorIcon } from './address-connector.svg';
export { default as ArrowIcon } from './arrow.svg';
export { default as Bar3Icon } from './bar3.svg';
@ -20,6 +19,7 @@ export { default as CoinMarketCapIcon } from './logos/coinmarketcap.svg';
export { default as CoinsIcon } from './coins.svg';
export { default as CommentIcon } from './comment.svg';
export { default as CopyIcon } from './copy.svg';
export { default as CurrencySignIcon } from './currency-sign.svg';
export { default as DepositIcon } from './deposit.svg';
export { default as DepthChartIcon } from './depth-chart.svg';
export { default as DiscordIcon } from './discord.svg';
@ -29,6 +29,7 @@ export { default as FileIcon } from './file.svg';
export { default as FundingChartIcon } from './funding-chart.svg';
export { default as GearIcon } from './gear.svg';
export { default as GiftboxIcon } from './giftbox.svg';
export { default as GovernanceIcon } from './governance.svg';
export { default as HelpCircleIcon } from './help-circle.svg';
export { default as HideIcon } from './hide.svg';
export { default as HistoryIcon } from './history.svg';
@ -60,6 +61,7 @@ export { default as StarIcon } from './star.svg';
export { default as SunIcon } from './sun.svg';
export { default as TerminalIcon } from './terminal.svg';
export { default as TogglesMenuIcon } from './toggles-menu.svg';
export { default as TokenIcon } from './token.svg';
export { default as TradeIcon } from './trade.svg';
export { default as TransferIcon } from './transfer.svg';
export { default as TriangleIcon } from './triangle.svg';

5
src/icons/token.svg Normal file
View File

@ -0,0 +1,5 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.78448 0L0.498535 11.999H3.0425L11.3714 0H8.78448Z" fill="currentColor"/>
<path d="M3.30742 0L5.74539 3.53569L4.47339 5.46426L0.710449 0H3.30742Z" fill="currentColor"/>
<path d="M9.03109 12L6.32812 8.08934L7.73738 6.21436L11.575 12H9.03109Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 378 B

View File

@ -30,6 +30,7 @@ import { OrderDetailsDialog } from '@/views/dialogs/DetailsDialog/OrderDetailsDi
import { FillDetailsDialog } from '@/views/dialogs/DetailsDialog/FillDetailsDialog';
import { NewMarketMessageDetailsDialog } from '@/views/dialogs/NewMarketMessageDetailsDialog';
import { NewMarketAgreementDialog } from '@/views/dialogs/NewMarketAgreementDialog';
import { ExternalNavStrideDialog } from '@/views/dialogs/ExternalNavStrideDialog';
export const DialogManager = () => {
const dispatch = useDispatch();
@ -59,6 +60,7 @@ export const DialogManager = () => {
[DialogTypes.Help]: <HelpDialog {...modalProps} />,
[DialogTypes.ExternalNavKeplr]: <ExternalNavKeplrDialog {...modalProps} />,
[DialogTypes.ExternalLink]: <ExternalLinkDialog {...modalProps} />,
[DialogTypes.ExternalNavStride]: <ExternalNavStrideDialog {...modalProps} />,
[DialogTypes.MnemonicExport]: <MnemonicExportDialog {...modalProps} />,
[DialogTypes.MobileSignIn]: <MobileSignInDialog {...modalProps} />,
[DialogTypes.Onboarding]: <OnboardingDialog {...modalProps} />,

View File

@ -4,9 +4,15 @@ import { useDispatch, useSelector } from 'react-redux';
import { useEnsName } from 'wagmi';
import { useNavigate } from 'react-router-dom';
import { ButtonSize } from '@/constants/buttons';
import { TransferType } from '@/constants/abacus';
import { OnboardingState } from '@/constants/account';
import { ButtonSize } from '@/constants/buttons';
import { DialogTypes } from '@/constants/dialogs';
import { STRING_KEYS } from '@/constants/localization';
import { AppRoute, PortfolioRoute, HistoryRoute } from '@/constants/routes';
import { wallets } from '@/constants/wallets';
import { useAccounts, useStringGetter, useTokenConfigs } from '@/hooks';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { Details } from '@/components/Details';
@ -16,13 +22,6 @@ import { IconButton, type IconButtonProps } from '@/components/IconButton';
import { Panel } from '@/components/Panel';
import { Toolbar } from '@/components/Toolbar';
import { OnboardingState } from '@/constants/account';
import { DialogTypes } from '@/constants/dialogs';
import { STRING_KEYS } from '@/constants/localization';
import { AppRoute, PortfolioRoute, HistoryRoute } from '@/constants/routes';
import { wallets } from '@/constants/wallets';
import { useAccounts, useStringGetter, useTokenConfigs } from '@/hooks';
import {
getHistoricalTradingRewardsForCurrentWeek,
getOnboardingState,
@ -32,10 +31,12 @@ import { openDialog } from '@/state/dialogs';
import { isTruthy } from '@/lib/isTruthy';
import { truncateAddress } from '@/lib/wallet';
import { DYDXBalancePanel } from './rewards/DYDXBalancePanel';
import { MigratePanel } from './rewards/MigratePanel';
import { GovernancePanel } from './rewards/GovernancePanel';
import { StakingPanel } from './rewards/StakingPanel';
import { DYDXBalancePanel } from './token/rewards/DYDXBalancePanel';
import { MigratePanel } from './token/rewards/MigratePanel';
import { GovernancePanel } from './token/rewards/GovernancePanel';
import { StakingPanel } from './token/staking/StakingPanel';
import { StrideStakingPanel } from './token/staking/StrideStakingPanel';
import { NewMarketsPanel } from './token/rewards/NewMarketsPanel';
const ENS_CHAIN_ID = 1; // Ethereum
@ -236,7 +237,9 @@ const Profile = () => {
</Styled.HistoryPanel>
<Styled.GovernancePanel />
<Styled.NewMarketsPanel />
<Styled.StakingPanel />
<Styled.StrideStakingPanel />
</Styled.MobileProfileLayout>
);
};
@ -249,11 +252,12 @@ Styled.MobileProfileLayout = styled.div`
${layoutMixins.contentContainerPage}
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
padding: 1.25rem 0.9rem;
max-width: 100vw;
grid-template-columns: 1fr 1fr;
grid-template-areas:
'header header'
'actions actions'
@ -262,8 +266,23 @@ Styled.MobileProfileLayout = styled.div`
'balance balance'
'rewards fees'
'history history'
'governance governance'
'staking staking';
'governance newMarkets'
'keplr stride';
@media ${breakpoints.mobile} {
grid-template-areas:
'header header'
'actions actions'
'settings help'
'migrate migrate'
'balance balance'
'rewards fees'
'history history'
'governance governance'
'newMarkets newMarkets'
'keplr keplr'
'stride stride';
}
`;
Styled.Header = styled.header`
@ -431,5 +450,13 @@ Styled.GovernancePanel = styled(GovernancePanel)`
`;
Styled.StakingPanel = styled(StakingPanel)`
grid-area: staking;
grid-area: keplr;
`;
Styled.NewMarketsPanel = styled(NewMarketsPanel)`
grid-area: newMarkets;
`;
Styled.StrideStakingPanel = styled(StrideStakingPanel)`
grid-area: stride;
`;

View File

@ -45,7 +45,8 @@ export default () => {
const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {};
const { nativeTokenBalance } = useAccountBalance();
const { numTotalPositions, numTotalOpenOrders } = useSelector(getTradeInfoNumbers, shallowEqual) || {};
const { numTotalPositions, numTotalOpenOrders } =
useSelector(getTradeInfoNumbers, shallowEqual) || {};
const numPositions = shortenNumberForDisplay(numTotalPositions);
const numOrders = shortenNumberForDisplay(numTotalOpenOrders);
@ -119,30 +120,43 @@ export default () => {
items: [
{
value: PortfolioRoute.Overview,
slotBefore: <Styled.Icon iconName={IconName.Overview} />,
slotBefore: (
<Styled.IconContainer>
<Icon iconName={IconName.Overview} />
</Styled.IconContainer>
),
label: stringGetter({ key: STRING_KEYS.OVERVIEW }),
href: PortfolioRoute.Overview,
},
{
value: PortfolioRoute.Positions,
slotBefore: <Styled.Icon iconName={IconName.Positions} />,
slotBefore: (
<Styled.IconContainer>
<Icon iconName={IconName.Positions} />
</Styled.IconContainer>
),
label: (
<>
{stringGetter({ key: STRING_KEYS.POSITIONS })}
{numPositions > 0 && (
<Tag type={TagType.Number}> {numPositions} </Tag>
)}
{numPositions &&
(typeof numPositions === 'string' || numPositions > 0) && (
<Tag type={TagType.Number}> {numPositions} </Tag>
)}
</>
),
href: PortfolioRoute.Positions,
},
{
value: PortfolioRoute.Orders,
slotBefore: <Styled.Icon iconName={IconName.OrderPending} />,
slotBefore: (
<Styled.IconContainer>
<Icon iconName={IconName.OrderPending} />
</Styled.IconContainer>
),
label: (
<>
{stringGetter({ key: STRING_KEYS.ORDERS })}
{numOrders > 0 && (
{numOrders && (typeof numOrders === 'string' || numOrders > 0) && (
<Tag type={TagType.Number}> {numOrders} </Tag>
)}
</>
@ -151,13 +165,21 @@ export default () => {
},
{
value: PortfolioRoute.Fees,
slotBefore: <Styled.Icon iconName={IconName.Calculator} />,
slotBefore: (
<Styled.IconContainer>
<Icon iconName={IconName.Calculator} />
</Styled.IconContainer>
),
label: stringGetter({ key: STRING_KEYS.FEES }),
href: PortfolioRoute.Fees,
},
{
value: PortfolioRoute.History,
slotBefore: <Styled.Icon iconName={IconName.History} />,
slotBefore: (
<Styled.IconContainer>
<Icon iconName={IconName.History} />
</Styled.IconContainer>
),
label: stringGetter({ key: STRING_KEYS.HISTORY }),
href: PortfolioRoute.History,
},
@ -232,16 +254,14 @@ Styled.NavigationMenu = styled(NavigationMenu)`
padding-top: 0;
`;
Styled.Icon = styled(Icon)`
--icon-backgroundColor: var(--color-layer-4);
width: 1em;
height: 1em;
margin-left: -0.25em;
box-sizing: content-box;
background-color: var(--icon-backgroundColor);
Styled.IconContainer = styled.div`
width: 1.5rem;
height: 1.5rem;
font-size: 0.75rem;
display: flex;
justify-content: center;
align-items: center;
background-color: var(--color-layer-4);
border-radius: 50%;
padding: 0.25em;
margin-left: -0.25rem;
`;

View File

@ -0,0 +1,60 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { STRING_KEYS } from '@/constants/localization';
import { useStringGetter } from '@/hooks';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { DetachedSection } from '@/components/ContentSection';
import { ContentSectionHeader } from '@/components/ContentSectionHeader';
import { GovernancePanel } from './rewards/GovernancePanel';
import { NewMarketsPanel } from './rewards/NewMarketsPanel';
export default () => {
const stringGetter = useStringGetter();
return (
<DetachedSection>
<Styled.HeaderSection>
<ContentSectionHeader
title={stringGetter({ key: STRING_KEYS.GOVERNANCE })}
subtitle="Participate in the ecosystem by voting on Governance proposals or submitting your own."
/>
</Styled.HeaderSection>
<Styled.ContentWrapper>
<Styled.Row>
<GovernancePanel />
<NewMarketsPanel />
</Styled.Row>
</Styled.ContentWrapper>
</DetachedSection>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.HeaderSection = styled.section`
${layoutMixins.contentSectionDetached}
@media ${breakpoints.tablet} {
${layoutMixins.flexColumn}
gap: 1rem;
margin-bottom: 0.5rem;
}
`;
Styled.ContentWrapper = styled.div`
${layoutMixins.flexColumn}
gap: 1.5rem;
max-width: 80rem;
padding: 0 1rem;
`;
Styled.Row = styled.div`
gap: 1rem;
display: grid;
grid-template-columns: repeat(3, 1fr);
`;

126
src/pages/token/Token.tsx Normal file
View File

@ -0,0 +1,126 @@
import { Suspense, lazy } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import styled, { AnyStyledComponent } from 'styled-components';
import { STRING_KEYS } from '@/constants/localization';
import { TokenRoute } from '@/constants/routes';
import { useBreakpoints, useStringGetter } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins';
import { Icon, IconName } from '@/components/Icon';
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
import { NavigationMenu } from '@/components/NavigationMenu';
import { WithSidebar } from '@/components/WithSidebar';
const RewardsPage = lazy(() => import('./rewards/RewardsPage'));
const StakingPage = lazy(() => import('./staking/StakingPage'));
const GovernancePage = lazy(() => import('./Governance'));
export default () => {
const { isTablet } = useBreakpoints();
const stringGetter = useStringGetter();
const routesComponent = (
<Suspense fallback={<LoadingSpace id="token-page" />}>
<Routes>
<Route path={TokenRoute.TradingRewards} element={<RewardsPage />} />
<Route path={TokenRoute.StakingRewards} element={<StakingPage />} />
<Route path={TokenRoute.Governance} element={<GovernancePage />} />
<Route path="*" element={<Navigate to={TokenRoute.TradingRewards} replace />} />
</Routes>
</Suspense>
);
return (
<WithSidebar
sidebar={
isTablet ? null : (
<Styled.SideBar>
<Styled.NavigationMenu
items={[
{
group: 'views',
groupLabel: stringGetter({ key: STRING_KEYS.VIEWS }),
items: [
{
value: TokenRoute.TradingRewards,
slotBefore: (
<Styled.IconContainer>
<Icon iconName={IconName.Token} />
</Styled.IconContainer>
),
label: stringGetter({ key: STRING_KEYS.TRADING_REWARDS }),
href: TokenRoute.TradingRewards,
},
{
value: TokenRoute.StakingRewards,
slotBefore: (
<Styled.IconContainer>
<Icon iconName={IconName.CurrencySign} />
</Styled.IconContainer>
),
label: 'Staking Rewards', // stringGetter({ key: STRING_KEYS.STAKING_REWARDS }),
href: TokenRoute.StakingRewards,
tag: stringGetter({ key: STRING_KEYS.NEW }),
},
{
value: TokenRoute.Governance,
slotBefore: (
<Styled.IconContainer>
<Icon iconName={IconName.Governance} />
</Styled.IconContainer>
),
label: stringGetter({ key: STRING_KEYS.GOVERNANCE }),
href: TokenRoute.Governance,
},
],
},
]}
/>
</Styled.SideBar>
)
}
>
{routesComponent}
</WithSidebar>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.SideBar = styled.div`
${layoutMixins.flexColumn}
justify-content: space-between;
height: 100%;
`;
Styled.Footer = styled.div`
${layoutMixins.row}
flex-wrap: wrap;
padding: 1rem;
gap: 0.5rem;
> button {
flex-grow: 1;
}
`;
Styled.NavigationMenu = styled(NavigationMenu)`
padding: 0.5rem;
padding-top: 0;
`;
Styled.IconContainer = styled.div`
width: 1.5rem;
height: 1.5rem;
font-size: 0.75rem;
display: flex;
justify-content: center;
align-items: center;
background-color: var(--color-layer-4);
border-radius: 50%;
margin-left: -0.25rem;
`;

View File

@ -14,13 +14,14 @@ import { Link } from '@/components/Link';
import { openDialog } from '@/state/dialogs';
export const GovernancePanel = () => {
export const GovernancePanel = ({ className }: { className?: string }) => {
const stringGetter = useStringGetter();
const dispatch = useDispatch();
const { governanceLearnMore } = useURLConfigs();
return (
<Panel
className={className}
slotHeaderContent={
<Styled.Title>{stringGetter({ key: STRING_KEYS.GOVERNANCE })}</Styled.Title>
}
@ -72,4 +73,4 @@ Styled.Title = styled.h3`
font: var(--font-medium-book);
color: var(--color-text-2);
margin-bottom: -1rem;
`;
`;

View File

@ -28,7 +28,7 @@ import { log } from '@/lib/telemetry';
const SEASON_NUMBER = 2;
export const LaunchIncentivesPanel = () => {
export const LaunchIncentivesPanel = ({ className }: { className?: string }) => {
const { isNotTablet } = useBreakpoints();
const dispatch = useDispatch();
@ -37,11 +37,15 @@ export const LaunchIncentivesPanel = () => {
}, []);
return isNotTablet ? (
<Styled.Panel slotHeader={<LaunchIncentivesTitle />} slotRight={<EstimatedRewards />}>
<Styled.Panel
className={className}
slotHeader={<LaunchIncentivesTitle />}
slotRight={<EstimatedRewards />}
>
<LaunchIncentivesContent />
</Styled.Panel>
) : (
<Styled.Panel>
<Styled.Panel className={className}>
<Styled.Column>
<EstimatedRewards />
<LaunchIncentivesTitle />

View File

@ -19,21 +19,22 @@ import { Tag } from '@/components/Tag';
import { MustBigNumber } from '@/lib/numbers';
import { layoutMixins } from '@/styles/layoutMixins';
export const NewMarketsPanel = () => {
export const NewMarketsPanel = ({ className }: { className?: string }) => {
const stringGetter = useStringGetter();
const navigate = useNavigate();
const { hasPotentialMarketsData } = usePotentialMarkets();
const { chainTokenDecimals, chainTokenLabel } = useTokenConfigs();
const { newMarketProposal } = useGovernanceVariables();
const initialDepositAmountDecimals = isMainnet ? 0 : 11;
const initialDepositAmountBN = MustBigNumber(newMarketProposal.initialDepositAmount).div(
Number(`1e${chainTokenDecimals}`)
);
const initialDepositAmountDecimals = isMainnet ? 0 : chainTokenDecimals;
if (!hasPotentialMarketsData) return null;
return (
<Panel
className={className}
slotHeaderContent={
<Styled.Title>
{stringGetter({ key: STRING_KEYS.ADD_A_MARKET })}

View File

@ -10,6 +10,8 @@ import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { BackButton } from '@/components/BackButton';
import { DetachedSection } from '@/components/ContentSection';
import { ContentSectionHeader } from '@/components/ContentSectionHeader';
import { testFlags } from '@/lib/testFlags';
@ -19,9 +21,6 @@ import { MigratePanel } from './MigratePanel';
import { RewardsHelpPanel } from './RewardsHelpPanel';
import { TradingRewardsSummaryPanel } from './TradingRewardsSummaryPanel';
import { RewardHistoryPanel } from './RewardHistoryPanel';
import { GovernancePanel } from './GovernancePanel';
import { StakingPanel } from './StakingPanel';
import { NewMarketsPanel } from './NewMarketsPanel';
const RewardsPage = () => {
const stringGetter = useStringGetter();
@ -29,46 +28,45 @@ const RewardsPage = () => {
const navigate = useNavigate();
return (
<Styled.Page>
<div>
{isTablet && (
<Styled.MobileHeader>
<BackButton onClick={() => navigate(AppRoute.Profile)} />
{stringGetter({ key: STRING_KEYS.TRADING_REWARDS })}
</Styled.MobileHeader>
<ContentSectionHeader
title={stringGetter({ key: STRING_KEYS.TRADING_REWARDS })}
slotLeft={<BackButton onClick={() => navigate(AppRoute.Profile)} />}
/>
)}
<Styled.GridLayout
showTradingRewards={testFlags.showTradingRewards}
showMigratePanel={import.meta.env.VITE_V3_TOKEN_ADDRESS && isNotTablet}
>
{import.meta.env.VITE_V3_TOKEN_ADDRESS && isNotTablet && <Styled.MigratePanel />}
<DetachedSection>
<Styled.GridLayout
showTradingRewards={testFlags.showTradingRewards}
showMigratePanel={import.meta.env.VITE_V3_TOKEN_ADDRESS && isNotTablet}
>
{import.meta.env.VITE_V3_TOKEN_ADDRESS && isNotTablet && <Styled.MigratePanel />}
{isTablet ? (
<Styled.LaunchIncentivesPanel />
) : (
<>
{isTablet ? (
<Styled.LaunchIncentivesPanel />
<Styled.DYDXBalancePanel />
</>
)}
) : (
<>
<Styled.LaunchIncentivesPanel />
<Styled.DYDXBalancePanel />
</>
)}
{testFlags.showTradingRewards && (
<Styled.TradingRewardsColumn>
<TradingRewardsSummaryPanel />
{isTablet && <RewardsHelpPanel />}
<RewardHistoryPanel />
</Styled.TradingRewardsColumn>
)}
{testFlags.showTradingRewards && (
<Styled.TradingRewardsColumn>
<TradingRewardsSummaryPanel />
{isTablet && <RewardsHelpPanel />}
<RewardHistoryPanel />
</Styled.TradingRewardsColumn>
)}
{isNotTablet && (
<Styled.OtherColumn showTradingRewards={testFlags.showTradingRewards}>
<NewMarketsPanel />
<GovernancePanel />
<StakingPanel />
<RewardsHelpPanel />
</Styled.OtherColumn>
)}
</Styled.GridLayout>
</Styled.Page>
{isNotTablet && (
<Styled.OtherColumn showTradingRewards={testFlags.showTradingRewards}>
<RewardsHelpPanel />
</Styled.OtherColumn>
)}
</Styled.GridLayout>
</DetachedSection>
</div>
);
};
@ -76,27 +74,6 @@ export default RewardsPage;
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Page = styled.div`
${layoutMixins.contentContainerPage}
padding: 2rem;
align-items: center;
> * {
--content-max-width: 80rem;
max-width: min(calc(100vw - 4rem), var(--content-max-width));
}
@media ${breakpoints.tablet} {
--stickyArea-topHeight: var(--page-header-height-mobile);
padding: 0 1rem 1rem;
> * {
max-width: calc(100vw - 2rem);
width: 100%;
}
}
`;
Styled.MobileHeader = styled.header`
${layoutMixins.contentSectionDetachedScrollable}
${layoutMixins.stickyHeader}
@ -113,6 +90,7 @@ Styled.GridLayout = styled.div<{ showTradingRewards?: boolean; showMigratePanel?
display: grid;
grid-template-columns: 2fr 1fr;
gap: var(--gap);
max-width: 80rem;
> * {
gap: var(--gap);
@ -123,24 +101,39 @@ Styled.GridLayout = styled.div<{ showTradingRewards?: boolean; showMigratePanel?
? css`
grid-template-areas:
'migrate migrate'
'incentives balance'
'incentives incentives'
'balance balance'
'rewards other';
`
: showTradingRewards
? css`
grid-template-areas: 'incentives balance' 'rewards other';
grid-template-areas:
'incentives balance'
'rewards other';
`
: showMigratePanel
? css`
grid-template-areas: 'migrate migrate' 'incentives balance' 'other other';
grid-template-areas:
'migrate migrate'
'incentives incentives'
'balance balance'
'other other';
`
: css`
grid-template-areas: 'incentives balance' 'other other';
grid-template-areas:
'incentives balance'
'other other';
`};
@media ${breakpoints.notTablet} {
padding: 1rem;
}
@media ${breakpoints.tablet} {
--gap: 1rem;
grid-template-columns: 1fr;
width: calc(100vw - 2rem);
margin: 0 auto;
${({ showTradingRewards }) =>
showTradingRewards
@ -187,13 +180,3 @@ Styled.OtherColumn = styled.div<{ showTradingRewards?: boolean }>`
}
`}
`;
Styled.RewardHistoryHeader = styled.div`
h3 {
font: var(--font-medium-book);
color: var(--color-text-2);
}
padding: 1rem 1.5rem 0;
margin-bottom: -0.5rem;
`;

View File

@ -3,8 +3,8 @@ import styled, { AnyStyledComponent } from 'styled-components';
import { shallowEqual, useSelector } from 'react-redux';
import { STRING_KEYS } from '@/constants/localization';
import { layoutMixins } from '@/styles/layoutMixins';
import { useStringGetter, useTokenConfigs } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins';
import { AssetIcon } from '@/components/AssetIcon';
import { Details } from '@/components/Details';
@ -18,7 +18,10 @@ import abacusStateManager from '@/lib/abacus';
export const TradingRewardsSummaryPanel = () => {
const stringGetter = useStringGetter();
const { chainTokenLabel } = useTokenConfigs();
const currentWeekTradingReward = useSelector(getHistoricalTradingRewardsForCurrentWeek, shallowEqual);
const currentWeekTradingReward = useSelector(
getHistoricalTradingRewardsForCurrentWeek,
shallowEqual
);
useEffect(() => {
abacusStateManager.refreshHistoricalTradingRewards();

View File

@ -0,0 +1,67 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { DetachedSection } from '@/components/ContentSection';
import { ContentSectionHeader } from '@/components/ContentSectionHeader';
import { StakingPanel } from './StakingPanel';
import { StrideStakingPanel } from './StrideStakingPanel';
import { DYDXBalancePanel } from '../rewards/DYDXBalancePanel';
export default () => {
return (
<DetachedSection>
<Styled.HeaderSection>
<ContentSectionHeader
title="Staking Rewards"
subtitle="Stake to earn APR. Unstaking can take up to 30 days."
/>
</Styled.HeaderSection>
<Styled.ContentWrapper>
<Styled.Row>
<Styled.InnerRow>
<StrideStakingPanel />
<StakingPanel />
</Styled.InnerRow>
<DYDXBalancePanel />
</Styled.Row>
</Styled.ContentWrapper>
</DetachedSection>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.HeaderSection = styled.section`
${layoutMixins.contentSectionDetached}
@media ${breakpoints.tablet} {
${layoutMixins.flexColumn}
gap: 1rem;
margin-bottom: 0.5rem;
}
`;
Styled.ContentWrapper = styled.div`
${layoutMixins.flexColumn}
gap: 1.5rem;
max-width: 80rem;
padding: 0 1rem;
`;
Styled.Row = styled.div`
gap: 1rem;
display: grid;
grid-template-columns: 2fr 1fr;
`;
Styled.InnerRow = styled.div`
gap: 1rem;
display: grid;
grid-template-columns: 1fr 1fr;
height: fit-content;
`;

View File

@ -1,7 +1,6 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { useDispatch } from 'react-redux';
import { ButtonAction, ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { DialogTypes } from '@/constants/dialogs';
@ -14,22 +13,19 @@ import { Link } from '@/components/Link';
import { openDialog } from '@/state/dialogs';
export const StakingPanel = () => {
export const StakingPanel = ({ className }: { className?: string }) => {
const stringGetter = useStringGetter();
const dispatch = useDispatch();
const { stakingLearnMore } = useURLConfigs();
return (
<Panel
slotHeaderContent={<Styled.Title>{stringGetter({ key: STRING_KEYS.STAKING })}</Styled.Title>}
slotRight={
<Styled.Arrow>
<Styled.IconButton
action={ButtonAction.Base}
iconName={IconName.Arrow}
size={ButtonSize.Small}
/>
</Styled.Arrow>
<Styled.Panel
className={className}
slotHeaderContent={
<Styled.Header>
<Styled.Title>Stake with Keplr</Styled.Title>
<Styled.Img src="/third-party/keplr.png" alt={stringGetter({ key: STRING_KEYS.KEPLR })} />
</Styled.Header>
}
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
>
@ -39,12 +35,40 @@ export const StakingPanel = () => {
{stringGetter({ key: STRING_KEYS.LEARN_MORE })}
</Link>
</Styled.Description>
</Panel>
</Styled.Panel>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Panel = styled(Panel)`
align-items: start;
header {
justify-content: unset;
padding-bottom: 0;
}
`;
Styled.Header = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
`;
Styled.Title = styled.h3`
font: var(--font-medium-book);
color: var(--color-text-2);
`;
Styled.Img = styled.img`
width: 2rem;
height: 2rem;
margin-left: 0.5rem;
`;
Styled.Description = styled.div`
color: var(--color-text-0);
--link-color: var(--color-text-1);
@ -61,13 +85,3 @@ Styled.IconButton = styled(IconButton)`
color: var(--color-text-0);
--color-border: var(--color-layer-6);
`;
Styled.Arrow = styled.div`
padding-right: 1.5rem;
`;
Styled.Title = styled.h3`
font: var(--font-medium-book);
color: var(--color-text-2);
margin-bottom: -1rem;
`;

View File

@ -0,0 +1,94 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { useDispatch } from 'react-redux';
import { STRING_KEYS } from '@/constants/localization';
import { DialogTypes } from '@/constants/dialogs';
import { useStringGetter, useTokenConfigs, useURLConfigs } from '@/hooks';
import { IconButton } from '@/components/IconButton';
import { Link } from '@/components/Link';
import { Panel } from '@/components/Panel';
import { Tag } from '@/components/Tag';
import { openDialog } from '@/state/dialogs';
export const StrideStakingPanel = ({ className }: { className?: string }) => {
const stringGetter = useStringGetter();
const dispatch = useDispatch();
const { stakingLearnMore } = useURLConfigs();
const { chainTokenLabel } = useTokenConfigs();
return (
<Styled.Panel
className={className}
slotHeaderContent={
<Styled.Header>
<Styled.Title>
Liquid Stake with Stride
<Tag isHighlighted>{stringGetter({ key: STRING_KEYS.NEW })}</Tag>
</Styled.Title>
<Styled.Img src="/third-party/stride.png" alt="Stride" />
</Styled.Header>
}
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavStride }))}
>
<Styled.Description>
{`Stake your ${chainTokenLabel} tokens for st${chainTokenLabel} which you can deploy around the ecosystem.`}
<Link href={stakingLearnMore} onClick={(e) => e.stopPropagation()}>
{stringGetter({ key: STRING_KEYS.LEARN_MORE })}
</Link>
</Styled.Description>
</Styled.Panel>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Panel = styled(Panel)`
align-items: start;
header {
justify-content: unset;
padding-bottom: 0;
}
`;
Styled.Header = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
`;
Styled.Title = styled.h3`
font: var(--font-medium-book);
color: var(--color-text-2);
display: flex;
align-items: center;
gap: 0.5ch;
`;
Styled.Img = styled.img`
width: 2rem;
height: 2rem;
margin-left: 0.5rem;
`;
Styled.Description = styled.div`
color: var(--color-text-0);
--link-color: var(--color-text-1);
a {
display: inline;
::before {
content: ' ';
}
}
`;
Styled.IconButton = styled(IconButton)`
color: var(--color-text-0);
--color-border: var(--color-layer-6);
`;

View File

@ -1,3 +1,4 @@
import type { ReactNode } from 'react';
import styled, { type AnyStyledComponent } from 'styled-components';
import { ButtonAction, ButtonType } from '@/constants/buttons';
@ -10,18 +11,26 @@ import { Dialog } from '@/components/Dialog';
import { layoutMixins } from '@/styles/layoutMixins';
type ElementProps = {
buttonText?: ReactNode;
link: string;
linkDescription?: string;
title?: ReactNode;
setIsOpen: (open: boolean) => void;
};
export const ExternalLinkDialog = ({ setIsOpen, link, linkDescription }: ElementProps) => {
export const ExternalLinkDialog = ({
setIsOpen,
buttonText,
link,
linkDescription,
title,
}: ElementProps) => {
const stringGetter = useStringGetter();
return (
<Dialog
isOpen
setIsOpen={setIsOpen}
title={stringGetter({ key: STRING_KEYS.LEAVING_WEBSITE })}
title={title ?? stringGetter({ key: STRING_KEYS.LEAVING_WEBSITE })}
description={
linkDescription ?? stringGetter({ key: STRING_KEYS.LEAVING_WEBSITE_DESCRIPTION })
}
@ -29,7 +38,7 @@ export const ExternalLinkDialog = ({ setIsOpen, link, linkDescription }: Element
<Styled.Content>
<p>{stringGetter({ key: STRING_KEYS.LEAVING_WEBSITE_DISCLAIMER })}.</p>
<Button type={ButtonType.Link} action={ButtonAction.Primary} href={link}>
{stringGetter({ key: STRING_KEYS.CONTINUE })}
{buttonText ?? stringGetter({ key: STRING_KEYS.CONTINUE })}
</Button>
</Styled.Content>
</Dialog>

View File

@ -0,0 +1,137 @@
import { useCallback } from 'react';
import styled, { type AnyStyledComponent } from 'styled-components';
import { useDispatch } from 'react-redux';
import { ButtonAction, ButtonSize, ButtonType } from '@/constants/buttons';
import { DialogTypes } from '@/constants/dialogs';
import { STRING_KEYS } from '@/constants/localization';
import { useBreakpoints, useStringGetter, useURLConfigs } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins';
import { Button } from '@/components/Button';
import { Dialog, DialogPlacement } from '@/components/Dialog';
import { Icon, IconName } from '@/components/Icon';
import { IconButton } from '@/components/IconButton';
import { closeDialog, openDialog } from '@/state/dialogs';
type ElementProps = {
setIsOpen: (open: boolean) => void;
};
export const ExternalNavStrideDialog = ({ setIsOpen }: ElementProps) => {
const stringGetter = useStringGetter();
const { strideZoneApp, accountExportLearnMore } = useURLConfigs();
const dispatch = useDispatch();
const { isTablet } = useBreakpoints();
const openExternalNavDialog = useCallback(() => {
dispatch(closeDialog());
dispatch(
openDialog({
type: DialogTypes.ExternalLink,
dialogProps: {
buttonText: (
<Styled.Span>
Liquid Stake on Stride <Icon iconName={IconName.LinkOut} />
</Styled.Span>
),
link: strideZoneApp,
title: 'Liquid staking and leaving website',
},
})
);
}, [dispatch]);
return (
<Dialog
isOpen
setIsOpen={setIsOpen}
title={stringGetter({ key: STRING_KEYS.HAVE_YOU_EXPORTED })}
placement={isTablet ? DialogPlacement.FullScreen : DialogPlacement.Default}
>
<Styled.Content>
<Styled.Button
type={ButtonType.Button}
size={ButtonSize.XLarge}
onClick={openExternalNavDialog}
>
<span>
<strong>{stringGetter({ key: STRING_KEYS.YES })}</strong>, Navigate to Stride Zone.
</span>
<Styled.IconButton
action={ButtonAction.Base}
iconName={IconName.Arrow}
size={ButtonSize.XSmall}
/>
</Styled.Button>
<Styled.Button
type={ButtonType.Link}
size={ButtonSize.XLarge}
href={accountExportLearnMore}
>
<span>
{stringGetter({
key: STRING_KEYS.LEARN_TO_EXPORT,
params: {
STRONG_NO: <strong>{stringGetter({ key: STRING_KEYS.NO })}</strong>,
},
})}
</span>
<Styled.IconButton
action={ButtonAction.Base}
iconName={IconName.Arrow}
size={ButtonSize.XSmall}
/>
</Styled.Button>
</Styled.Content>
</Dialog>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.TextToggle = styled.div`
${layoutMixins.stickyFooter}
color: var(--color-accent);
cursor: pointer;
margin-top: auto;
&:hover {
text-decoration: underline;
}
`;
Styled.Content = styled.div`
${layoutMixins.stickyArea0}
--stickyArea0-bottomHeight: 2rem;
--stickyArea0-bottomGap: 1rem;
--stickyArea0-totalInsetBottom: 0.5rem;
${layoutMixins.flexColumn}
gap: 1rem;
`;
Styled.Button = styled(Button)`
--button-font: var(--font-base-book);
--button-padding: 0 1.5rem;
gap: 0;
justify-content: space-between;
`;
Styled.IconButton = styled(IconButton)`
color: var(--color-text-0);
--color-border: var(--color-layer-6);
`;
Styled.Span = styled.span`
display: flex;
align-items: center;
gap: 0.5ch;
`;

View File

@ -1,4 +1,4 @@
import { memo } from 'react';
import { ElementType, memo } from 'react';
import styled, { AnyStyledComponent, css } from 'styled-components';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import type { Dispatch } from '@reduxjs/toolkit';
@ -106,7 +106,7 @@ export const AccountMenu = () => {
{walletType && (
<Styled.SourceIcon>
<Styled.ConnectorIcon iconName={IconName.AddressConnector} />
<Icon iconComponent={wallets[walletType].icon} />
<Icon iconComponent={wallets[walletType].icon as ElementType} />
</Styled.SourceIcon>
)}
<Styled.Column>
@ -220,7 +220,7 @@ export const AccountMenu = () => {
{onboardingState === OnboardingState.WalletConnected ? (
<Styled.WarningIcon iconName={IconName.Warning} />
) : onboardingState === OnboardingState.AccountConnected ? (
walletType && <Icon iconComponent={wallets[walletType].icon} />
walletType && <Icon iconComponent={wallets[walletType].icon as ElementType} />
) : null}
{!isTablet && <Styled.Address>{truncateAddress(dydxAddress)}</Styled.Address>}
</Styled.DropdownMenu>