From e7da21962a970c84a4b78a32e95112d2d18cffe5 Mon Sep 17 00:00:00 2001 From: aleka Date: Fri, 27 Oct 2023 16:14:34 -0400 Subject: [PATCH] add entry point to token migration (#98) * update migrate panel * address feedback --- .env.example | 3 +- src/components/Icon.tsx | 3 + src/components/Panel.tsx | 57 ++-- src/icons/index.ts | 1 + src/icons/migrate.svg | 9 + src/lib/wagmi.ts | 24 +- src/pages/Profile.tsx | 24 +- src/pages/rewards/MigratePanel.tsx | 222 +++++++++++++++ src/pages/rewards/RewardsPage.tsx | 279 +++---------------- src/views/dialogs/ExternalNavKeplrDialog.tsx | 1 + 10 files changed, 345 insertions(+), 278 deletions(-) create mode 100644 src/icons/migrate.svg create mode 100644 src/pages/rewards/MigratePanel.tsx diff --git a/.env.example b/.env.example index 3a0d1b2..b7a842e 100644 --- a/.env.example +++ b/.env.example @@ -6,4 +6,5 @@ VITE_PK_ENCRYPTION_KEY= VITE_WALLETCONNECT2_PROJECT_ID= -VITE_V3_TOKEN_ADDRESS= \ No newline at end of file +VITE_V3_TOKEN_ADDRESS= +VITE_TOKEN_MIGRATION_URI= \ No newline at end of file diff --git a/src/components/Icon.tsx b/src/components/Icon.tsx index b7bf634..5e8c7cf 100644 --- a/src/components/Icon.tsx +++ b/src/components/Icon.tsx @@ -40,6 +40,7 @@ import { LockIcon, LogoShortIcon, MenuIcon, + MigrateIcon, MintscanIcon, OrderbookIcon, OrderCanceledIcon, @@ -108,6 +109,7 @@ export enum IconName { Lock = 'Lock', LogoShort = 'LogoShort', Menu = 'Menu', + Migrate = 'Migrate', Mintscan = 'Mintscan', Onboarding = 'Onboarding', Orderbook = 'OrderbookIcon', @@ -177,6 +179,7 @@ const icons = { [IconName.Lock]: LockIcon, [IconName.LogoShort]: LogoShortIcon, [IconName.Menu]: MenuIcon, + [IconName.Migrate]: MigrateIcon, [IconName.Mintscan]: MintscanIcon, [IconName.Orderbook]: OrderbookIcon, [IconName.OrderCanceled]: OrderCanceledIcon, diff --git a/src/components/Panel.tsx b/src/components/Panel.tsx index cd33955..dec52ea 100644 --- a/src/components/Panel.tsx +++ b/src/components/Panel.tsx @@ -8,6 +8,7 @@ import { layoutMixins } from '@/styles/layoutMixins'; type PanelProps = { slotHeaderContent?: string; slotHeader?: React.ReactNode; + slotRight?: React.ReactNode; children?: React.ReactNode; href?: string; onHeaderClick?: () => void; @@ -22,6 +23,7 @@ type PanelStyleProps = { export const Panel = ({ slotHeaderContent, slotHeader, + slotRight, children, href, onHeaderClick, @@ -30,55 +32,61 @@ export const Panel = ({ className, }: PanelProps & PanelStyleProps) => ( - {href ? ( - - {slotHeader ? ( - slotHeader - ) : ( + + {href ? ( + + {slotHeader ? ( + slotHeader + ) : ( + + {slotHeaderContent} + + + )} + + ) : slotHeader ? ( + slotHeader + ) : ( + slotHeaderContent && ( {slotHeaderContent} - - )} - - ) : slotHeader ? ( - slotHeader - ) : ( - slotHeaderContent && ( - - {slotHeaderContent} - - ) - )} - {children} + ) + )} + {children} + + {slotRight} ); const Styled: Record = {}; Styled.Panel = styled.section` - ${layoutMixins.flexColumn} + ${layoutMixins.row} background-color: var(--color-layer-3); border-radius: 0.875rem; `; +Styled.Left = styled.div` + ${layoutMixins.flexColumn} + width: 100%; +`; + Styled.Header = styled.header<{ hasSeparator?: boolean }>` ${layoutMixins.spacedRow} - - padding: 0.875rem 1rem 0.625rem; - font-size: 0.875rem; + padding: 1rem 1rem; ${({ hasSeparator }) => hasSeparator && css` + padding-bottom: 0.625rem; box-shadow: 0 var(--border-width) var(--border-color); `} `; Styled.Icon = styled(Icon)` color: var(--color-text-0); - font-size: 0.625rem; `; @@ -86,6 +94,5 @@ Styled.Content = styled.div` ${layoutMixins.scrollArea} ${layoutMixins.stickyArea0} --stickyArea0-background: transparent; - - padding: 0.5rem 1rem; + padding: 1rem 1rem; `; diff --git a/src/icons/index.ts b/src/icons/index.ts index f840de8..a1c0667 100644 --- a/src/icons/index.ts +++ b/src/icons/index.ts @@ -35,6 +35,7 @@ export { default as LockIcon } from './lock.svg'; export { default as LogoShortIcon } from './logo-short'; export { default as MarketsIcon } from './markets.svg'; export { default as MenuIcon } from './menu.svg'; +export { default as MigrateIcon } from './migrate.svg'; export { default as MintscanIcon } from './logos/mintscan.svg'; export { default as OrderbookIcon } from './orderbook.svg'; export { default as OverviewIcon } from './overview.svg'; diff --git a/src/icons/migrate.svg b/src/icons/migrate.svg new file mode 100644 index 0000000..c77b877 --- /dev/null +++ b/src/icons/migrate.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/lib/wagmi.ts b/src/lib/wagmi.ts index 469a41f..3a0216a 100644 --- a/src/lib/wagmi.ts +++ b/src/lib/wagmi.ts @@ -46,6 +46,8 @@ import { WALLET_CONNECT_EXPLORER_RECOMMENDED_IDS, } from '@/constants/wallets'; +import { isTruthy } from './isTruthy'; + // Config export const WAGMI_SUPPORTED_CHAINS: Chain[] = [ @@ -78,13 +80,17 @@ export const WAGMI_SUPPORTED_CHAINS: Chain[] = [ celoAlfajores, ]; -const { chains, publicClient, webSocketPublicClient } = configureChains(WAGMI_SUPPORTED_CHAINS, [ - // alchemyProvider({ apiKey: import.meta.env.VITE_ALCHEMY_API_KEY }), - jsonRpcProvider({ - rpc: (chain) => ({ http: chain.rpcUrls.default.http[0] }), - }), - publicProvider(), -]); +const { chains, publicClient, webSocketPublicClient } = configureChains( + WAGMI_SUPPORTED_CHAINS, + [ + import.meta.env.VITE_ALCHEMY_API_KEY && + alchemyProvider({ apiKey: import.meta.env.VITE_ALCHEMY_API_KEY }), + jsonRpcProvider({ + rpc: (chain) => ({ http: chain.rpcUrls.default.http[0] }), + }), + publicProvider(), + ].filter(isTruthy) +); const injectedConnectorOptions = { chains, @@ -202,5 +208,7 @@ export const resolveWagmiConnector = ({ ? createInjectedConnectorWithProvider(walletConnection.provider) : walletConnection.type === WalletConnectionType.WalletConnect2 && walletConfig.walletconnect2Id ? createWalletConnect2ConnectorWithId(walletConfig.walletconnect2Id, walletConnectConfig) - : getConnectors(walletConnectConfig).find(({ id }: { id: string }) => id === walletConnectionConfig.wagmiConnectorId); + : getConnectors(walletConnectConfig).find( + ({ id }: { id: string }) => id === walletConnectionConfig.wagmiConnectorId + ); }; diff --git a/src/pages/Profile.tsx b/src/pages/Profile.tsx index b671e12..03f1908 100644 --- a/src/pages/Profile.tsx +++ b/src/pages/Profile.tsx @@ -17,7 +17,7 @@ import { DialogTypes } from '@/constants/dialogs'; import { STRING_KEYS } from '@/constants/localization'; import { AppRoute, PortfolioRoute, HistoryRoute } from '@/constants/routes'; import { wallets, WalletType } from '@/constants/wallets'; -import { useAccounts, useStringGetter } from '@/hooks'; +import { useAccounts, useStringGetter, useTokenConfigs } from '@/hooks'; import { getOnboardingState } from '@/state/accountSelectors'; import { openDialog } from '@/state/dialogs'; @@ -37,6 +37,7 @@ const Profile = () => { const isConnected = onboardingState !== OnboardingState.Disconnected; const { evmAddress, dydxAddress, walletType } = useAccounts(); + const { chainTokenLabel } = useTokenConfigs(); const { data: ensName } = useEnsName({ address: evmAddress, @@ -137,7 +138,11 @@ const Profile = () => { layout="grid" /> - + { { + const { isNotTablet } = useBreakpoints(); + const stringGetter = useStringGetter(); + + const selectedNetwork = useSelector(getSelectedNetwork); + + const chainId = Number(ENVIRONMENT_CONFIG_MAP[selectedNetwork].ethereumChainId); + + // v3 token is only on mainnet + const { balance: tokenBalance } = useAccountBalance({ + addressOrDenom: chainId === 1 ? import.meta.env.VITE_V3_TOKEN_ADDRESS : undefined, + chainId: 1, + isCosmosChain: false, + }); + + return isNotTablet ? ( + {stringGetter({ key: STRING_KEYS.MIGRATE })}} + slotRight={ + +
+
{stringGetter({ key: STRING_KEYS.AVAILABLE_TO_MIGRATE })}
+ +
+ {import.meta.env.VITE_TOKEN_MIGRATION_URI && ( + + )} +
+ } + > + + {stringGetter({ key: STRING_KEYS.MIGRATE_DESCRIPTION })} + + {stringGetter({ key: STRING_KEYS.LEARN_MORE })} → + + +
+ ) : ( + +

+ + {stringGetter({ key: STRING_KEYS.MIGRATE })} +

+ + + {stringGetter({ + key: STRING_KEYS.FROM_TO, + params: { FROM: Ethereum, TO: dYdX Chain }, + })} + + + } + > + + {stringGetter({ key: STRING_KEYS.AVAILABLE_TO_MIGRATE })} + DYDX + + ), + value: , + }, + ]} + /> + } + > + {import.meta.env.VITE_TOKEN_MIGRATION_URI && ( + + )} + + + {stringGetter({ key: STRING_KEYS.WANT_TO_LEARN })} + + {stringGetter({ key: STRING_KEYS.CLICK_HERE })} + + +
+ ); +}; + +const Styled: Record = {}; + +Styled.MigratePanel = styled(Panel)` + padding: 1rem 1.5rem; +`; + +Styled.Title = styled.h3` + font: var(--font-medium-book); + color: var(--color-text-2); + + padding: 1rem 1.5rem 0; +`; + +Styled.MigrateAction = styled.div` + ${layoutMixins.flexEqualColumns} + align-items: center; + margin-right: 1rem; + gap: 1rem; + padding: 1rem; + width: 100%; + + background-color: var(--color-layer-2); + border: solid var(--border-width) var(--color-border); + border-radius: 0.75rem; +`; + +Styled.Token = styled(Output)` + font: var(--font-large-book); + color: var(--color-text-2); +`; + +Styled.Description = styled.div` + color: var(--color-text-0); + --link-color: var(--color-text-1); +`; + +Styled.MobileMigratePanel = styled(Panel)` + ${layoutMixins.flexColumn} + gap: 1rem; + + align-items: center; +`; + +Styled.MobileMigrateHeader = styled.div` + ${layoutMixins.inlineRow} + gap: 1ch; + + font: var(--font-small-book); + color: var(--color-text-0); + + padding: 1rem 1.5rem 0; + + h3 { + ${layoutMixins.inlineRow} + font: var(--font-large-book); + color: var(--color-text-2); + + svg { + font-size: 1.75rem; + } + } + + span { + margin-top: 0.2rem; + + b { + font-weight: var(--fontWeight-book); + color: var(--color-text-1); + } + } +`; + +Styled.VerticalSeparator = styled(VerticalSeparator)` + z-index: 1; + + && { + height: 1.5rem; + } +`; + +Styled.Details = styled(Details)` + padding: 0.5rem 1rem; +`; + +Styled.WithReceipt = styled(WithReceipt)` + width: 100%; +`; + +Styled.InlineRow = styled.div` + ${layoutMixins.inlineRow} + color: var(--color-text-0); + --link-color: var(--color-text-1); +`; diff --git a/src/pages/rewards/RewardsPage.tsx b/src/pages/rewards/RewardsPage.tsx index ff8b0cd..86affbd 100644 --- a/src/pages/rewards/RewardsPage.tsx +++ b/src/pages/rewards/RewardsPage.tsx @@ -1,134 +1,49 @@ import styled, { AnyStyledComponent } from 'styled-components'; -import { useDispatch, useSelector } from 'react-redux'; +import { useDispatch } from 'react-redux'; -import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks'; import { STRING_KEYS } from '@/constants/localization'; -import { ButtonAction, ButtonSize, ButtonState, ButtonType } from '@/constants/buttons'; +import { ButtonAction, ButtonSize } from '@/constants/buttons'; import { DialogTypes } from '@/constants/dialogs'; -import { useAccountBalance, useBreakpoints, useStringGetter } from '@/hooks'; +import { useBreakpoints, useStringGetter } from '@/hooks'; import { breakpoints } from '@/styles'; import { layoutMixins } from '@/styles/layoutMixins'; -import { Details } from '@/components/Details'; import { Panel } from '@/components/Panel'; import { IconName } from '@/components/Icon'; -import { Button } from '@/components/Button'; import { IconButton } from '@/components/IconButton'; import { Link } from '@/components/Link'; -import { Output, OutputType } from '@/components/Output'; -import { VerticalSeparator } from '@/components/Separator'; -import { WithReceipt } from '@/components/WithReceipt'; import { openDialog } from '@/state/dialogs'; -import { getSelectedNetwork } from '@/state/appSelectors'; import { DYDXBalancePanel } from './DYDXBalancePanel'; +import { MigratePanel } from './MigratePanel'; -// TODO: replace placeholder URL with real URLs when avaialble +// TODO: consolidate help link urls to env variables const GOVERNANCE_HELP_URL = 'https://help.dydx.exchange/'; -const STAKING_HELP_URL = 'https://help.dydx.exchange/'; +const STAKING_HELP_URL = + 'https://docs.dydx.community/dydx-chain-documentation/staking/how-to-stake'; export const RewardsPage = () => { const dispatch = useDispatch(); const stringGetter = useStringGetter(); + const { isTablet, isNotTablet } = useBreakpoints(); - const selectedNetwork = useSelector(getSelectedNetwork); - - // const chainId = Number(ENVIRONMENT_CONFIG_MAP[selectedNetwork].ethereumChainId); - - // const { balance } = useAccountBalance({ - // addressOrDenom: import.meta.env.VITE_V3_TOKEN_ADDRESS, - // assetSymbol: 'DYDX', - // chainId, - // isCosmosChain: false, - // }); - - // const tokenBalance = import.meta.env.VITE_V3_TOKEN_ADDRESS ? balance : 0; + const panelArrow = ( + + + + ); return ( - {/* {isNotTablet ? ( - - -
- - {stringGetter({ key: STRING_KEYS.MIGRATE })} - - - {stringGetter({ key: STRING_KEYS.MIGRATE_DESCRIPTION })} - - {stringGetter({ key: STRING_KEYS.LEARN_MORE })} → - - -
- -
-
{stringGetter({ key: STRING_KEYS.AVAILABLE_TO_MIGRATE })}
-
- -
-
- -
-
-
- ) : ( - -

{stringGetter({ key: STRING_KEYS.MIGRATE })}

- - - {stringGetter({ - key: STRING_KEYS.FROM_TO, - params: { FROM: Ethereum, TO: dYdX Chain }, - })} - - - } - > - , - }, - ]} - /> - } - > - - - - {stringGetter({ key: STRING_KEYS.WANT_TO_LEARN })} - {stringGetter({ key: STRING_KEYS.CLICK_HERE })} - -
- )} */} - + {import.meta.env.VITE_V3_TOKEN_ADDRESS && } {isTablet && ( @@ -138,42 +53,28 @@ export const RewardsPage = () => { {stringGetter({ key: STRING_KEYS.GOVERNANCE })}} + slotRight={panelArrow} onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))} > - - - {stringGetter({ key: STRING_KEYS.GOVERNANCE_DESCRIPTION })} - e.stopPropagation()}> - {stringGetter({ key: STRING_KEYS.LEARN_MORE })} → - - - {/* TODO: vertically center based on Panel height */} - - + + {stringGetter({ key: STRING_KEYS.GOVERNANCE_DESCRIPTION })} + e.stopPropagation()}> + {stringGetter({ key: STRING_KEYS.LEARN_MORE })} → + + {stringGetter({ key: STRING_KEYS.STAKING })}} + slotRight={panelArrow} onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))} > - - - {stringGetter({ key: STRING_KEYS.STAKING_DESCRIPTION })} - e.stopPropagation()}> - {stringGetter({ key: STRING_KEYS.LEARN_MORE })} → - - - {/* TODO: vertically center based on Panel height */} - - + + {stringGetter({ key: STRING_KEYS.STAKING_DESCRIPTION })} + e.stopPropagation()}> + {stringGetter({ key: STRING_KEYS.LEARN_MORE })} → + + {isNotTablet && ( @@ -191,61 +92,25 @@ const Styled: Record = {}; Styled.Page = styled.div` ${layoutMixins.contentContainerPage} gap: 1.5rem; - - @media ${breakpoints.tablet} { - padding: 1rem; - } `; Styled.Panel = styled(Panel)` - padding: 0 1.5rem 1rem; - - @media ${breakpoints.tablet} { - max-width: calc(100vw - 2rem); - } + padding: 1rem 1.5rem; `; -Styled.Row = styled.div` - ${layoutMixins.spacedRow} - gap: 1rem; - - align-items: center; +Styled.Title = styled.h3` + font: var(--font-medium-book); + color: var(--color-text-2); + padding: 1rem 1.5rem 0; `; Styled.Description = styled.div` color: var(--color-text-0); - - a { - color: var(--color-text-1); - } + --link-color: var(--color-text-1); `; -Styled.Migrate = styled.section` - max-width: min(100vw, var(--content-max-width)); - - padding: 1.5rem; - - background-color: var(--color-layer-3); - border-radius: 0.875rem; -`; - -Styled.TwoItemRow = styled(Styled.Row)` - grid-template-columns: 1fr 1fr; -`; - -Styled.MigrateAction = styled(Styled.TwoItemRow)` - padding: 1rem; - - background-color: var(--color-layer-2); - border: solid var(--border-width) var(--color-border); - border-radius: 0.75rem; -`; - -Styled.Token = styled(Output)` - font: var(--font-large-book); -`; - -Styled.PanelRow = styled(Styled.Row)` +Styled.PanelRow = styled.div` + ${layoutMixins.spacedRow} gap: 1.5rem; max-width: min(100vw, var(--content-max-width)); align-items: flex-start; @@ -269,65 +134,11 @@ Styled.BalancePanelContainer = styled.div` } `; -Styled.Title = styled.h3` - ${layoutMixins.inlineRow} - padding: 1.25rem 1.5rem 0.5rem; - - font: var(--font-medium-book); - color: var(--color-text-2); -`; - -Styled.MigrateTitle = styled(Styled.Title)` - padding: 0 0 0.5rem; -`; - -Styled.MobileMigrateCard = styled(Styled.Panel)` - ${layoutMixins.flexColumn} - gap: 1rem; - - align-items: center; -`; - -Styled.MobileMigrateHeader = styled(Styled.Title)` - ${layoutMixins.inlineRow} - gap: 1ch; - padding-bottom: 1rem; - - font: var(--font-small-book); - color: var(--color-text-0); - - h3 { - ${layoutMixins.inlineRow} - font: var(--font-large-book); - color: var(--color-text-2); - - svg { - font-size: 1.75rem; - } - } - - span { - margin-top: 0.2rem; - b { - font-weight: var(--fontWeight-book); - color: var(--color-text-1); - } - } -`; - -Styled.Details = styled(Details)` - padding: 0.5rem 1rem; -`; - -Styled.WithReceipt = styled(WithReceipt)` - width: 100%; -`; - -Styled.LearnMore = styled(Styled.Description)` - ${layoutMixins.row} - gap: 1ch; -`; - Styled.IconButton = styled(IconButton)` color: var(--color-text-0); + --color-border: var(--color-layer-6); +`; + +Styled.Arrow = styled.div` + padding: 1rem 1.5rem; `; diff --git a/src/views/dialogs/ExternalNavKeplrDialog.tsx b/src/views/dialogs/ExternalNavKeplrDialog.tsx index 505d2a2..02b6da2 100644 --- a/src/views/dialogs/ExternalNavKeplrDialog.tsx +++ b/src/views/dialogs/ExternalNavKeplrDialog.tsx @@ -104,4 +104,5 @@ Styled.Button = styled(Button)` Styled.IconButton = styled(IconButton)` color: var(--color-text-0); + --color-border: var(--color-layer-6); `;