diff --git a/package.json b/package.json index 1163ab54f4..49db722a78 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "kwenta", - "version": "7.8.0", + "version": "7.9.0", "description": "Kwenta", "main": "index.js", "scripts": { diff --git a/packages/app/package.json b/packages/app/package.json index 58b75d1ebb..8fcd34108f 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@kwenta/app", - "version": "7.8.0", + "version": "7.9.0", "scripts": { "dev": "next", "build": "next build", diff --git a/packages/app/src/components/StakeCard.tsx b/packages/app/src/components/StakeCard.tsx index 3783936675..aea64416b0 100644 --- a/packages/app/src/components/StakeCard.tsx +++ b/packages/app/src/components/StakeCard.tsx @@ -10,8 +10,11 @@ import { FlexDivCol, FlexDivRowCentered } from 'components/layout/flex' import SegmentedControl from 'components/SegmentedControl' import { DEFAULT_TOKEN_DECIMALS } from 'constants/defaults' import { StakingCard } from 'sections/dashboard/Stake/card' +import { useAppSelector } from 'state/hooks' +import { selectStakingV1 } from 'state/staking/selectors' import media from 'styles/media' +import ErrorView from './ErrorView' import { Body, NumericValue } from './Text' type StakeCardProps = { @@ -51,6 +54,7 @@ const StakeCard: FC = memo( const { t } = useTranslation() const [amount, setAmount] = useState('') const [activeTab, setActiveTab] = useState(0) + const stakingV1 = useAppSelector(selectStakingV1) const balance = useMemo(() => { return activeTab === 0 ? stakeBalance : unstakeBalance @@ -136,20 +140,33 @@ const StakeCard: FC = memo( - + + + {!stakingV1 && ( + + )} + ) diff --git a/packages/app/src/components/layout/grid.ts b/packages/app/src/components/layout/grid.ts index ea9ed49f77..723458eea3 100644 --- a/packages/app/src/components/layout/grid.ts +++ b/packages/app/src/components/layout/grid.ts @@ -26,6 +26,12 @@ export const SplitContainer = styled.div` ${media.greaterThan('lg')` grid-template-columns: 1fr 1fr; `} + + ${media.lessThan('lg')` + display: flex; + flex-direction: column-reverse; + row-gap: 25px; + `} ` export const ContainerRowMixin = css` diff --git a/packages/app/src/constants/announcement.ts b/packages/app/src/constants/announcement.ts index 0ae66a82c7..94f7ef7467 100644 --- a/packages/app/src/constants/announcement.ts +++ b/packages/app/src/constants/announcement.ts @@ -5,9 +5,9 @@ export const BANNER_ENABLED = true // Sets the link destination for the banner component, or renders the banner as // plain, un-clickable text if set to a falsey value (`false`, `null`, '', etc...) export const BANNER_LINK_URL = - 'https://mirror.xyz/kwenta.eth/qJaLnoRFh8O_UhT3GZB15g8tJDG7KJmKdAEbd905BjM' + 'https://docs.kwenta.io/kwenta-token/staking-v2-migration/how-to-migrate' // Sets the text displayed on the home page banner -export const BANNER_TEXT = 'Staking V2 Migration on September 21st. Read More ↗' +export const BANNER_TEXT = 'Staking V2 Migration Now Live! Read More ↗' // Sets the height of the banner component on desktop export const BANNER_HEIGHT_DESKTOP = 50 // Sets the height of the banner component on mobile diff --git a/packages/app/src/constants/routes.ts b/packages/app/src/constants/routes.ts index d3f728933c..924930898d 100644 --- a/packages/app/src/constants/routes.ts +++ b/packages/app/src/constants/routes.ts @@ -25,6 +25,7 @@ export const ROUTES = { Earn: normalizeRoute('/dashboard', 'earn', 'tab'), Stake: normalizeRoute('/dashboard', 'staking', 'tab'), Rewards: normalizeRoute('/dashboard', 'rewards', 'tab'), + Migrate: normalizeRoute('/dashboard', 'migrate', 'tab'), TradingRewards: formatUrl('/dashboard/staking', { tab: 'trading-rewards' }), }, Exchange: { diff --git a/packages/app/src/pages/dashboard/index.tsx b/packages/app/src/pages/dashboard/index.tsx index 1c16bf1de6..6b17e5945a 100644 --- a/packages/app/src/pages/dashboard/index.tsx +++ b/packages/app/src/pages/dashboard/index.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next' import DashboardLayout from 'sections/dashboard/DashboardLayout' import Overview from 'sections/dashboard/Overview' -import { usePollDashboardFuturesData } from 'state/futures/hooks' +import { useFetchStakeMigrateData, usePollDashboardFuturesData } from 'state/futures/hooks' type DashboardComponent = React.FC & { getLayout: (page: ReactNode) => JSX.Element } @@ -12,6 +12,7 @@ const Dashboard: DashboardComponent = () => { const { t } = useTranslation() usePollDashboardFuturesData() + useFetchStakeMigrateData() return ( <> diff --git a/packages/app/src/pages/dashboard/migrate.tsx b/packages/app/src/pages/dashboard/migrate.tsx new file mode 100644 index 0000000000..4ce90d3c53 --- /dev/null +++ b/packages/app/src/pages/dashboard/migrate.tsx @@ -0,0 +1,28 @@ +import Head from 'next/head' +import { FC, ReactNode } from 'react' +import { useTranslation } from 'react-i18next' + +import DashboardLayout from 'sections/dashboard/DashboardLayout' +import MigrationSteps from 'sections/dashboard/Stake/MigrationSteps' +import { useFetchStakeMigrateData } from 'state/futures/hooks' + +type MigrateComponent = FC & { getLayout: (page: ReactNode) => JSX.Element } + +const MigratePage: MigrateComponent = () => { + const { t } = useTranslation() + + useFetchStakeMigrateData() + + return ( + <> + + {t('dashboard-stake.page-title')} + + + + ) +} + +MigratePage.getLayout = (page) => {page} + +export default MigratePage diff --git a/packages/app/src/pages/dashboard/staking.tsx b/packages/app/src/pages/dashboard/staking.tsx index 173875780f..82e8c37ae0 100644 --- a/packages/app/src/pages/dashboard/staking.tsx +++ b/packages/app/src/pages/dashboard/staking.tsx @@ -1,33 +1,37 @@ -import { formatNumber } from '@kwenta/sdk/utils' +import { formatNumber, formatTruncatedDuration } from '@kwenta/sdk/utils' import Head from 'next/head' import { useRouter } from 'next/router' -import React, { ReactNode, useCallback, useMemo, useState } from 'react' +import React, { FC, ReactNode, useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' +import { FlexDivCol } from 'components/layout/flex' import { NO_VALUE } from 'constants/placeholder' import DashboardLayout from 'sections/dashboard/DashboardLayout' import EscrowTable from 'sections/dashboard/Stake/EscrowTable' import StakingPortfolio, { StakeTab } from 'sections/dashboard/Stake/StakingPortfolio' +import StakingTab from 'sections/dashboard/Stake/StakingTab' import StakingTabs from 'sections/dashboard/Stake/StakingTabs' import { StakingCards } from 'sections/dashboard/Stake/types' import { useFetchStakeMigrateData } from 'state/futures/hooks' import { useAppSelector } from 'state/hooks' import { selectClaimableBalance, + selectEscrowedKwentaBalance, selectKwentaBalance, selectKwentaRewards, selectStakedEscrowedKwentaBalance, - selectStakedEscrowedKwentaBalanceV2, selectStakedKwentaBalance, - selectStakedKwentaBalanceV2, - selectStakingRollbackRequired, + selectStakedResetTime, + selectStakingV1, selectTotalVestable, - selectTotalVestableV2, } from 'state/staking/selectors' +import { selectRedirectToMigration } from 'state/stakingMigration/selectors' import media from 'styles/media' -type StakingComponent = React.FC & { getLayout: (page: ReactNode) => JSX.Element } +import MigratePage from './migrate' + +type StakingComponent = FC & { getLayout: (page: ReactNode) => JSX.Element } const StakingPage: StakingComponent = () => { const { t } = useTranslation() @@ -35,13 +39,13 @@ const StakingPage: StakingComponent = () => { const claimableBalance = useAppSelector(selectClaimableBalance) const stakedKwentaBalance = useAppSelector(selectStakedKwentaBalance) const totalVestable = useAppSelector(selectTotalVestable) + const escrowedBalance = useAppSelector(selectEscrowedKwentaBalance) const stakedEscrowedKwentaBalance = useAppSelector(selectStakedEscrowedKwentaBalance) - const stakedKwentaBalanceV2 = useAppSelector(selectStakedKwentaBalanceV2) - const totalVestableV2 = useAppSelector(selectTotalVestableV2) - const stakedEscrowedKwentaBalanceV2 = useAppSelector(selectStakedEscrowedKwentaBalanceV2) const kwentaBalance = useAppSelector(selectKwentaBalance) const kwentaRewards = useAppSelector(selectKwentaRewards) - const isRollbackRequired = useAppSelector(selectStakingRollbackRequired) + const stakedResetTime = useAppSelector(selectStakedResetTime) + const stakingV1 = useAppSelector(selectStakingV1) + const redirectToMigration = useAppSelector(selectRedirectToMigration) useFetchStakeMigrateData() @@ -64,9 +68,18 @@ const StakingPage: StakingComponent = () => { [] ) + const timeLeft = useMemo( + () => + stakedResetTime > new Date().getTime() / 1000 + ? formatTruncatedDuration(stakedResetTime - new Date().getTime() / 1000) + : NO_VALUE, + [stakedResetTime] + ) + const stakingInfo: StakingCards[] = useMemo( () => [ { + key: 'balance', category: t('dashboard.stake.portfolio.balance.title'), onClick: () => setCurrentTab(StakeTab.Staking), card: [ @@ -79,10 +92,18 @@ const StakingPage: StakingComponent = () => { key: 'balance-staked', title: t('dashboard.stake.portfolio.balance.staked'), value: formatNumber(stakedKwentaBalance, { suggestDecimals: true }), + hidden: stakingV1, + }, + { + key: 'balance-escrow', + title: t('dashboard.stake.portfolio.balance.escrow'), + value: formatNumber(escrowedBalance, { suggestDecimals: true }), + hidden: !stakingV1, }, ], }, { + key: 'escrow', category: t('dashboard.stake.portfolio.escrow.title'), onClick: () => setCurrentTab(StakeTab.Escrow), card: [ @@ -97,8 +118,10 @@ const StakingPage: StakingComponent = () => { value: formatNumber(totalVestable, { suggestDecimals: true }), }, ], + hidden: stakingV1, }, { + key: 'rewards', category: t('dashboard.stake.portfolio.rewards.title'), onClick: () => setCurrentTab(StakeTab.Staking), card: [ @@ -115,86 +138,19 @@ const StakingPage: StakingComponent = () => { ], }, { - category: t('dashboard.stake.portfolio.early-vest-rewards.title'), - onClick: () => setCurrentTab(StakeTab.Staking), - card: [ - { - key: 'early-vest-rewards-claimable', - title: t('dashboard.stake.portfolio.early-vest-rewards.claimable'), - value: NO_VALUE, - }, - { - key: 'early-vest-rewards-epoch', - title: t('dashboard.stake.portfolio.early-vest-rewards.epoch'), - value: NO_VALUE, - }, - ], - }, - { + key: 'cooldown', category: t('dashboard.stake.portfolio.cooldown.title'), card: [ { key: 'cooldown-time-left', title: t('dashboard.stake.portfolio.cooldown.time-left'), - value: NO_VALUE, - }, - ], - }, - ], - [ - claimableBalance, - kwentaBalance, - kwentaRewards, - stakedEscrowedKwentaBalance, - stakedKwentaBalance, - t, - totalVestable, - ] - ) - - const rollbackInfo: StakingCards[] = useMemo( - () => [ - { - category: t('dashboard.stake.portfolio.balance.title'), - card: [ - { - key: 'balance-liquid', - title: t('dashboard.stake.portfolio.balance.liquid'), - value: formatNumber(kwentaBalance, { suggestDecimals: true }), - }, - { - key: 'balance-staked', - title: t('dashboard.stake.portfolio.balance.staked-v2'), - value: formatNumber(stakedKwentaBalanceV2, { suggestDecimals: true }), - }, - ], - }, - { - category: t('dashboard.stake.portfolio.rewards.title'), - card: [ - { - key: 'rewards-trading', - title: t('dashboard.stake.portfolio.rewards.trading'), - value: formatNumber(kwentaRewards, { suggestDecimals: true }), - }, - ], - }, - { - category: t('dashboard.stake.portfolio.escrow.title-v2'), - card: [ - { - key: 'escrow-staked', - title: t('dashboard.stake.portfolio.escrow.staked'), - value: formatNumber(stakedEscrowedKwentaBalanceV2, { suggestDecimals: true }), - }, - { - key: 'escrow-vestable', - title: t('dashboard.stake.portfolio.escrow.vestable'), - value: formatNumber(totalVestableV2, { suggestDecimals: true }), + value: timeLeft, }, ], + hidden: stakingV1, }, { + key: 'escrow-v1', category: t('dashboard.stake.portfolio.escrow.title-v1'), card: [ { @@ -208,62 +164,62 @@ const StakingPage: StakingComponent = () => { value: formatNumber(totalVestable, { suggestDecimals: true }), }, ], + hidden: !stakingV1, }, ], [ - t, + claimableBalance, + escrowedBalance, kwentaBalance, - stakedKwentaBalanceV2, kwentaRewards, - stakedEscrowedKwentaBalanceV2, - totalVestableV2, stakedEscrowedKwentaBalance, + stakedKwentaBalance, + stakingV1, + t, + timeLeft, totalVestable, ] ) const { title, cardsInfo, stakingComponent } = useMemo(() => { - if (isRollbackRequired) { - return { - title: t('dashboard.stake.tabs.revert.title'), - cardsInfo: rollbackInfo, - stakingComponent: ( - <> - + return stakingV1 + ? { + title: t('dashboard.stake.portfolio.title-v1'), + cardsInfo: stakingInfo.filter((info) => !info.hidden), + stakingComponent: ( + + - - - ), - } - } else { - return { - title: t('dashboard.stake.portfolio.title'), - cardsInfo: stakingInfo, - stakingComponent: , - } - } - }, [currentTab, handleChangeTab, isRollbackRequired, rollbackInfo, stakingInfo, t]) + + ), + } + : { + title: t('dashboard.stake.portfolio.title'), + cardsInfo: stakingInfo.filter((info) => !info.hidden), + stakingComponent: , + } + }, [currentTab, handleChangeTab, stakingInfo, stakingV1, t]) - return ( + return redirectToMigration ? ( + + ) : ( <> {t('dashboard-stake.page-title')} - + {stakingComponent} ) } -const TableContainer = styled.div` - margin-top: 15px; +const StakingV1Container = styled(FlexDivCol)` + margin-top: 20px; + row-gap: 30px; ${media.lessThan('lg')` - margin-top: 0px; - padding: 15px; + padding: 0 15px; + margin-top: 15px; + row-gap: 25px; `} ` diff --git a/packages/app/src/sections/dashboard/DashboardLayout.tsx b/packages/app/src/sections/dashboard/DashboardLayout.tsx index 56f1f5739a..ff05454eaa 100644 --- a/packages/app/src/sections/dashboard/DashboardLayout.tsx +++ b/packages/app/src/sections/dashboard/DashboardLayout.tsx @@ -9,6 +9,13 @@ import { TabList, TabPanel } from 'components/Tab' import { EXTERNAL_LINKS } from 'constants/links' import ROUTES from 'constants/routes' import AppLayout from 'sections/shared/Layout/AppLayout' +import { useAppSelector } from 'state/hooks' +import { selectStakingMigrationRequired } from 'state/staking/selectors' +import { + selectInMigrationPeriod, + selectIsMigrationPeriodStarted, + selectStartMigration, +} from 'state/stakingMigration/selectors' import { LeftSideContent, PageContent } from 'styles/common' import Links from './Links' @@ -19,6 +26,7 @@ enum Tab { Markets = 'markets', Governance = 'governance', Stake = 'staking', + Migrate = 'migrate', } const Tabs = Object.values(Tab) @@ -26,6 +34,10 @@ const Tabs = Object.values(Tab) const DashboardLayout: FC<{ children?: ReactNode }> = ({ children }) => { const { t } = useTranslation() const router = useRouter() + const stakngMigrationRequired = useAppSelector(selectStakingMigrationRequired) + const startMigration = useAppSelector(selectStartMigration) + const isMigrationPeriodStarted = useAppSelector(selectIsMigrationPeriodStarted) + const inMigrationPeriod = useAppSelector(selectInMigrationPeriod) const tabQuery = useMemo(() => { if (router.pathname) { @@ -64,6 +76,17 @@ const DashboardLayout: FC<{ children?: ReactNode }> = ({ children }) => { label: t('dashboard.tabs.staking'), active: activeTab === Tab.Stake, href: ROUTES.Dashboard.Stake, + hidden: + (stakngMigrationRequired || startMigration) && + isMigrationPeriodStarted && + inMigrationPeriod, + }, + { + name: Tab.Migrate, + label: t('dashboard.tabs.migrate'), + active: activeTab === Tab.Migrate, + href: ROUTES.Dashboard.Migrate, + hidden: !stakngMigrationRequired, }, { name: Tab.Governance, @@ -73,9 +96,18 @@ const DashboardLayout: FC<{ children?: ReactNode }> = ({ children }) => { external: true, }, ], - [t, activeTab] + [ + t, + activeTab, + stakngMigrationRequired, + startMigration, + isMigrationPeriodStarted, + inMigrationPeriod, + ] ) + const visibleTabs = TABS.filter(({ hidden }) => !hidden) + return ( @@ -84,12 +116,12 @@ const DashboardLayout: FC<{ children?: ReactNode }> = ({ children }) => { {t('dashboard.titles.trading')} - {TABS.slice(0, 3).map(({ name, label, active, ...rest }) => ( + {visibleTabs.slice(0, 3).map(({ name, label, active, ...rest }) => ( ))} {t('dashboard.titles.community')} - {TABS.slice(3).map(({ name, label, active, ...rest }) => ( + {visibleTabs.slice(3).map(({ name, label, active, ...rest }) => ( ))} diff --git a/packages/app/src/sections/dashboard/Stake/EscrowTab.tsx b/packages/app/src/sections/dashboard/Stake/EscrowTab.tsx index b8d87f3fea..a830789d4d 100644 --- a/packages/app/src/sections/dashboard/Stake/EscrowTab.tsx +++ b/packages/app/src/sections/dashboard/Stake/EscrowTab.tsx @@ -7,11 +7,9 @@ import { FlexDivCol, FlexDivRow, FlexDivRowCentered } from 'components/layout/fl import { Body, Heading } from 'components/Text' import { useAppSelector } from 'state/hooks' import { - selectAPY, + selectApy, selectStakedEscrowedKwentaBalance, - selectStakedEscrowedKwentaBalanceV2, selectTotalVestable, - selectTotalVestableV2, selectUnstakedEscrowedKwentaBalance, } from 'state/staking/selectors' import media from 'styles/media' @@ -22,65 +20,40 @@ import EscrowInputCard from './InputCards/EscrowInputCard' const EscrowTab = () => { const { t } = useTranslation() - const apy = useAppSelector(selectAPY) + const apy = useAppSelector(selectApy) const stakedEscrowedKwentaBalance = useAppSelector(selectStakedEscrowedKwentaBalance) const unstakedEscrowedKwentaBalance = useAppSelector(selectUnstakedEscrowedKwentaBalance) const totalVestable = useAppSelector(selectTotalVestable) - const stakedEscrowedKwentaBalanceV2 = useAppSelector(selectStakedEscrowedKwentaBalanceV2) - const totalVestableV2 = useAppSelector(selectTotalVestableV2) const stakingOverview = useMemo( () => [ { - category: 'Staking V1', + category: 'Staking', card: [ { - key: 'staking-v1-staked', + key: 'staking-staked', title: t('dashboard.stake.portfolio.escrow.staked'), value: formatNumber(stakedEscrowedKwentaBalance, { suggestDecimals: true }), }, { - key: 'staking-v1-unstaked', + key: 'staking-unstaked', title: t('dashboard.stake.portfolio.escrow.unstaked'), value: formatNumber(unstakedEscrowedKwentaBalance, { suggestDecimals: true }), }, { - key: 'staking-v1-apr', + key: 'staking-apr', title: t('dashboard.stake.portfolio.rewards.apr'), value: formatPercent(apy, { minDecimals: 2 }), }, { - key: 'staking-v1-vestable', + key: 'staking-vestable', title: t('dashboard.stake.portfolio.escrow.vestable'), value: formatNumber(totalVestable, { suggestDecimals: true }), }, ], }, - { - category: 'Staking V2', - card: [ - { - key: 'staking-v2-staked', - title: t('dashboard.stake.portfolio.escrow.staked'), - value: formatNumber(stakedEscrowedKwentaBalanceV2, { suggestDecimals: true }), - }, - { - key: 'staking-v2-vestable', - title: t('dashboard.stake.portfolio.escrow.vestable'), - value: formatNumber(totalVestableV2, { suggestDecimals: true }), - }, - ], - }, ], - [ - apy, - stakedEscrowedKwentaBalance, - stakedEscrowedKwentaBalanceV2, - t, - totalVestable, - totalVestableV2, - unstakedEscrowedKwentaBalance, - ] + [apy, stakedEscrowedKwentaBalance, t, totalVestable, unstakedEscrowedKwentaBalance] ) return ( diff --git a/packages/app/src/sections/dashboard/Stake/EscrowTable.tsx b/packages/app/src/sections/dashboard/Stake/EscrowTable.tsx index 0891ac5ead..50c30b8b74 100644 --- a/packages/app/src/sections/dashboard/Stake/EscrowTable.tsx +++ b/packages/app/src/sections/dashboard/Stake/EscrowTable.tsx @@ -8,6 +8,7 @@ import styled from 'styled-components' import Badge from 'components/Badge' import Button from 'components/Button' import { Checkbox } from 'components/Checkbox' +import { notifyError } from 'components/ErrorNotifier' import { FlexDivCol, FlexDivRow, FlexDivRowCentered } from 'components/layout/flex' import { DesktopLargeOnlyView, DesktopSmallOnlyView } from 'components/Media' import Table, { TableNoResults } from 'components/Table' @@ -17,8 +18,11 @@ import { Body } from 'components/Text' import { STAKING_DISABLED } from 'constants/ui' import { useAppDispatch, useAppSelector } from 'state/hooks' import { vestEscrowedRewards, vestEscrowedRewardsV2 } from 'state/staking/actions' -import { setSelectedEscrowVersion } from 'state/staking/reducer' -import { selectCombinedEscrowData, selectSelectedEscrowVersion } from 'state/staking/selectors' +import { + selectCanVestBeforeMigration, + selectEscrowEntries, + selectStakingV1, +} from 'state/staking/selectors' import media from 'styles/media' import common from 'styles/theme/colors/common' @@ -27,8 +31,9 @@ import VestConfirmationModal from './VestConfirmationModal' const EscrowTable = () => { const { t } = useTranslation() const dispatch = useAppDispatch() - const escrowVersion = useAppSelector(selectSelectedEscrowVersion) - const escrowData = useAppSelector(selectCombinedEscrowData) + const stakingV1 = useAppSelector(selectStakingV1) + const canVestBeforeMigration = useAppSelector(selectCanVestBeforeMigration) + const escrowData = useAppSelector(selectEscrowEntries) const [checkedState, setCheckedState] = useState(escrowData.map((_) => false)) const [checkAllState, setCheckAllState] = useState(false) @@ -42,15 +47,6 @@ const EscrowTable = () => { [checkedState] ) - const handleVersionChange = useCallback( - (version: number) => { - dispatch(setSelectedEscrowVersion(version)) - setCheckedState(escrowData.map((_) => false)) - setCheckAllState(false) - }, - [dispatch, escrowData] - ) - const selectAll = useCallback(() => { if (checkAllState) { setCheckedState(escrowData.map((_) => false)) @@ -61,7 +57,7 @@ const EscrowTable = () => { } }, [checkAllState, escrowData]) - const columnsDeps = useMemo(() => [checkedState], [checkedState]) + const columnsDeps = useMemo(() => [checkedState, escrowData], [checkedState, escrowData]) const { totalVestable, totalFee } = useMemo( () => @@ -88,9 +84,13 @@ const EscrowTable = () => { const handleVest = useCallback(async () => { if (vestEnabled) { - if (escrowVersion === 1) { - await dispatch(vestEscrowedRewards(ids)) - } else if (escrowVersion === 2) { + if (stakingV1) { + if (canVestBeforeMigration) { + await dispatch(vestEscrowedRewards(ids)) + } else { + notifyError('Please complete the migration before vesting.') + } + } else { await dispatch(vestEscrowedRewardsV2(ids)) } setCheckedState(escrowData.map((_) => false)) @@ -98,7 +98,7 @@ const EscrowTable = () => { } setConfirmModalOpen(false) - }, [dispatch, escrowData, escrowVersion, ids, vestEnabled]) + }, [canVestBeforeMigration, dispatch, escrowData, ids, stakingV1, vestEnabled]) const openConfirmModal = useCallback(() => setConfirmModalOpen(true), []) const closeConfirmModal = useCallback(() => setConfirmModalOpen(false), []) @@ -116,25 +116,6 @@ const EscrowTable = () => { {formatNumber(totalFee, { minDecimals: 4 })} KWENTA - - handleVersionChange(1)} - > - {t('dashboard.stake.tabs.escrow.v1')} - - handleVersionChange(2)} - > - {t('dashboard.stake.tabs.escrow.v2')} - - { disabled={!vestEnabled} onClick={openConfirmModal} > - {escrowVersion === 1 - ? t('dashboard.stake.tabs.escrow.vest-v1') - : t('dashboard.stake.tabs.escrow.vest-v2')} + {t('dashboard.stake.tabs.escrow.vest')} ) @@ -201,9 +180,9 @@ const EscrowTable = () => { header: () => {t('dashboard.stake.tabs.escrow.amount')}, cell: (cellProps) => ( - + {formatNumber(cellProps.row.original.amount, { minDecimals: 4 })} - + {cellProps.row.original.version === 1 ? ( V1 @@ -220,7 +199,7 @@ const EscrowTable = () => {
{t('dashboard.stake.tabs.escrow.time-until-vestable')}
), - cell: (cellProps) => {cellProps.row.original.time}, + cell: (cellProps) => {cellProps.row.original.time}, accessorKey: 'timeUntilVestable', size: 80, }, @@ -231,9 +210,9 @@ const EscrowTable = () => { ), cell: (cellProps) => ( - + {formatNumber(cellProps.row.original.vestable, { minDecimals: 4 })} - + ), accessorKey: 'immediatelyVestable', size: 80, @@ -247,7 +226,7 @@ const EscrowTable = () => { cell: (cellProps) => { const fee = wei(cellProps.row.original.fee) return ( - + {`${formatNumber(cellProps.row.original.fee, { minDecimals: 4, })} (${formatPercent( @@ -256,7 +235,7 @@ const EscrowTable = () => { : ZERO_WEI, { minDecimals: 0 } )})`} - + ) }, accessorKey: 'earlyVestFee', @@ -371,6 +350,9 @@ const EscrowTable = () => { const StyledButton = styled(Button)` padding: 10px 20px; + ${media.lessThan('lg')` + width: 100%; + `} ` const Container = styled(FlexDivRow)` align-items: flex-end; @@ -380,34 +362,34 @@ const Container = styled(FlexDivRow)` `} ` -const ButtonsContainer = styled(FlexDivRowCentered)` - column-gap: 20px; - ${media.lessThan('sm')` - column-gap: 10px; - `} -` - -const LabelContainer = styled(FlexDivRow)` +const LabelContainer = styled(FlexDivCol)` + align-items: flex-end; ${media.lessThan('lg')` align-items: flex-start; justify-content: flex-start; flex: 1 `} - ${media.lessThan('sm')` - flex: initial; + ${media.lessThan('md')` + justify-content: space-between; `} ` -const LabelContainers = styled(FlexDivCol)` +const LabelContainers = styled(FlexDivRow)` ${media.lessThan('lg')` justify-content: flex-start; width: 100%; `} + ${media.lessThan('md')` + flex: initial; + `} ` const StatsContainer = styled(FlexDivRowCentered)` ${media.lessThan('lg')` + padding: 15px 15px; + `} + ${media.lessThan('md')` flex-direction: column; row-gap: 25px; padding: 15px 15px; @@ -451,4 +433,8 @@ const TableCell = styled.div<{ $regular?: boolean }>` flex-direction: column; ` +const StyledTableCell = styled(TableCell)` + padding-left: 4px; +` + export default EscrowTable diff --git a/packages/app/src/sections/dashboard/Stake/InputCards/EscrowInputCard.tsx b/packages/app/src/sections/dashboard/Stake/InputCards/EscrowInputCard.tsx index 555c385dc3..49037fedb7 100644 --- a/packages/app/src/sections/dashboard/Stake/InputCards/EscrowInputCard.tsx +++ b/packages/app/src/sections/dashboard/Stake/InputCards/EscrowInputCard.tsx @@ -4,7 +4,13 @@ import { useTranslation } from 'react-i18next' import StakeCard from 'components/StakeCard' import { useAppDispatch, useAppSelector } from 'state/hooks' -import { approveKwentaToken, stakeEscrow, unstakeEscrow } from 'state/staking/actions' +import { + approveKwentaToken, + stakeEscrow, + stakeEscrowV2, + unstakeEscrow, + unstakeEscrowV2, +} from 'state/staking/actions' import { selectCanStakeEscrowedKwenta, selectCanUnstakeEscrowedKwenta, @@ -15,6 +21,7 @@ import { selectIsUnstakedEscrowedKwenta, selectIsUnstakingEscrowedKwenta, selectStakedEscrowedKwentaBalance, + selectStakingV1, selectUnstakedEscrowedKwentaBalance, } from 'state/staking/selectors' @@ -32,23 +39,24 @@ const EscrowInputCard: FC = () => { const isUnstakingEscrowedKwenta = useAppSelector(selectIsUnstakingEscrowedKwenta) const isStakingEscrowedKwenta = useAppSelector(selectIsStakingEscrowedKwenta) const isApprovingKwenta = useAppSelector(selectIsApprovingKwenta) + const stakingV1 = useAppSelector(selectStakingV1) const handleApprove = useCallback(() => { - dispatch(approveKwentaToken('kwenta')) - }, [dispatch]) + dispatch(approveKwentaToken(stakingV1 ? 'kwenta' : 'kwentaStakingV2')) + }, [dispatch, stakingV1]) const handleStakeEscrow = useCallback( (amount: string) => { - dispatch(stakeEscrow(wei(amount).toBN())) + dispatch(stakingV1 ? stakeEscrow(wei(amount).toBN()) : stakeEscrowV2(wei(amount).toBN())) }, - [dispatch] + [dispatch, stakingV1] ) const handleUnstakeEscrow = useCallback( (amount: string) => { - dispatch(unstakeEscrow(wei(amount).toBN())) + dispatch(stakingV1 ? unstakeEscrow(wei(amount).toBN()) : unstakeEscrowV2(wei(amount).toBN())) }, - [dispatch] + [dispatch, stakingV1] ) return ( diff --git a/packages/app/src/sections/dashboard/Stake/InputCards/StakeInputCard.tsx b/packages/app/src/sections/dashboard/Stake/InputCards/StakeInputCard.tsx index 93969543a3..3867b8b82e 100644 --- a/packages/app/src/sections/dashboard/Stake/InputCards/StakeInputCard.tsx +++ b/packages/app/src/sections/dashboard/Stake/InputCards/StakeInputCard.tsx @@ -5,7 +5,13 @@ import { useTranslation } from 'react-i18next' import StakeCard from 'components/StakeCard' import { useAppDispatch, useAppSelector } from 'state/hooks' -import { approveKwentaToken, stakeKwenta, unstakeKwenta } from 'state/staking/actions' +import { + approveKwentaToken, + stakeKwenta, + stakeKwentaV2, + unstakeKwenta, + unstakeKwentaV2, +} from 'state/staking/actions' import { selectCanStakeKwenta, selectCanUnstakeKwenta, @@ -17,6 +23,7 @@ import { selectIsUnstakingKwenta, selectKwentaBalance, selectStakedKwentaBalance, + selectStakingV1, } from 'state/staking/selectors' const StakeInputCard: FC = () => { @@ -33,23 +40,24 @@ const StakeInputCard: FC = () => { const isUnstakingKwenta = useAppSelector(selectIsUnstakingKwenta) const isStakingKwenta = useAppSelector(selectIsStakingKwenta) const isApprovingKwenta = useAppSelector(selectIsApprovingKwenta) + const stakingV1 = useAppSelector(selectStakingV1) const handleApprove = useCallback(() => { - dispatch(approveKwentaToken('kwenta')) - }, [dispatch]) + dispatch(approveKwentaToken(stakingV1 ? 'kwenta' : 'kwentaStakingV2')) + }, [dispatch, stakingV1]) const handleStakeKwenta = useCallback( (amount: string) => { - dispatch(stakeKwenta(wei(amount).toBN())) + dispatch(stakingV1 ? stakeKwenta(wei(amount).toBN()) : stakeKwentaV2(wei(amount).toBN())) }, - [dispatch] + [dispatch, stakingV1] ) const handleUnstakeKwenta = useCallback( (amount: string) => { - dispatch(unstakeKwenta(wei(amount).toBN())) + dispatch(stakingV1 ? unstakeKwenta(wei(amount).toBN()) : unstakeKwentaV2(wei(amount).toBN())) }, - [dispatch] + [dispatch, stakingV1] ) return ( diff --git a/packages/app/src/sections/dashboard/Stake/MigrationSteps.tsx b/packages/app/src/sections/dashboard/Stake/MigrationSteps.tsx index 513385c4d9..7f031d8431 100644 --- a/packages/app/src/sections/dashboard/Stake/MigrationSteps.tsx +++ b/packages/app/src/sections/dashboard/Stake/MigrationSteps.tsx @@ -1,6 +1,7 @@ -import { formatNumber } from '@kwenta/sdk/utils' +import { formatNumber, formatTruncatedDuration } from '@kwenta/sdk/utils' import { wei } from '@synthetixio/wei' -import { useMemo, useCallback, FC, memo } from 'react' +import { useRouter } from 'next/router' +import { useMemo, memo, useCallback } from 'react' import { Trans, useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -8,120 +9,392 @@ import Button from 'components/Button' import { FlexDivCol, FlexDivRowCentered } from 'components/layout/flex' import Spacer from 'components/Spacer' import { Body, Heading } from 'components/Text' +import ROUTES from 'constants/routes' +import useDebouncedMemo from 'hooks/useDebouncedMemo' import { StakingCard } from 'sections/dashboard/Stake/card' import { useAppDispatch, useAppSelector } from 'state/hooks' -import { unstakeKwentaV2, vestEscrowedRewardsV2 } from 'state/staking/actions' import { + claimMultipleAllRewards, + claimStakingRewards, + unstakeKwenta, + vestEscrowedRewards, +} from 'state/staking/actions' +import { + selectClaimableBalanceV1, + selectIsClaimingAllRewards, + selectIsGettingReward, selectIsUnstakingKwenta, selectIsVestingEscrowedRewards, - selectStakedKwentaBalanceV2, - selectTotalVestableV2, - selectVestEscrowV2Entries, + selectKwentaRewards, + selectStakedKwentaBalanceV1, + selectStepApproveActive, + selectStepClaimActive, + selectStepClaimFlowActive, + selectStepClaimTradingActive, + selectStepMigrateActive, + selectStepMigrateFlowActive, + selectStepRegisterActive, + selectStepUnstakeActive, + selectStepVestActive, } from 'state/staking/selectors' +import { + approveEscrowMigrator, + migrateEntries, + registerEntries, +} from 'state/stakingMigration/actions' +import { + selectInMigrationPeriod, + selectIsApprovingEscrowMigrator, + selectIsMigratingEntries, + selectIsMigrationPeriodStarted, + selectIsRegisteringEntries, + selectMigrationSecondsLeft, + selectNumberOfUnmigratedRegisteredEntries, + selectNumberOfUnregisteredEntries, + selectNumberOfUnvestedRegisteredEntries, + selectUnmigratedRegisteredEntryIDs, + selectUnregisteredVestingEntryIDs, + selectUnvestedRegisteredEntryIDs, +} from 'state/stakingMigration/selectors' import media from 'styles/media' -const MigrationSteps: FC = memo(() => { +import { StakingHeading } from './StakingHeading' + +const REGISTER_BATCH_SIZE = 500 +const VEST_BATCH_SIZE = 2000 +const MIGRATE_BATCH_SIZE = 175 + +const MigrationSteps = memo(() => { const { t } = useTranslation() + const router = useRouter() const dispatch = useAppDispatch() - const stakedKwentaBalanceV2 = useAppSelector(selectStakedKwentaBalanceV2) - const totalVestableV2 = useAppSelector(selectTotalVestableV2) - const escrowV2Entries = useAppSelector(selectVestEscrowV2Entries) - const isUnstakingKwenta = useAppSelector(selectIsUnstakingKwenta) - const isVestingEscrowedRewards = useAppSelector(selectIsVestingEscrowedRewards) - - const handleUnstakeKwenta = useCallback( - () => dispatch(unstakeKwentaV2(wei(stakedKwentaBalanceV2).toBN())), - [dispatch, stakedKwentaBalanceV2] + const claimableBalance = useAppSelector(selectClaimableBalanceV1) + const kwentaTradingRewards = useAppSelector(selectKwentaRewards) + const stakedKwentaBalance = useAppSelector(selectStakedKwentaBalanceV1) + const unregisteredVestingEntryIDs = useAppSelector(selectUnregisteredVestingEntryIDs) + const unvestedRegisteredEntryIDs = useAppSelector(selectUnvestedRegisteredEntryIDs) + const unmigratedRegisteredEntryIDs = useAppSelector(selectUnmigratedRegisteredEntryIDs) + const numberOfUnregisteredVestingEntries = useAppSelector(selectNumberOfUnregisteredEntries) + const numberOfUnvestedRegisteredVestingEntries = useAppSelector( + selectNumberOfUnvestedRegisteredEntries + ) + const numberOfUnmigratedRegisteredVestingEntries = useAppSelector( + selectNumberOfUnmigratedRegisteredEntries ) - const handleVest = useCallback( - () => dispatch(vestEscrowedRewardsV2(escrowV2Entries)), - [dispatch, escrowV2Entries] + const isMigrationPeriodStarted = useAppSelector(selectIsMigrationPeriodStarted) + const migrationSecondsLeft = useAppSelector(selectMigrationSecondsLeft) + const migrationTimeCountdown = useMemo( + () => formatTruncatedDuration(migrationSecondsLeft), + [migrationSecondsLeft] ) + const inMigrationPeriod = useAppSelector(selectInMigrationPeriod) + + const stepClaimFlowActive = useAppSelector(selectStepClaimFlowActive) + const stepClaimActive = useAppSelector(selectStepClaimActive) + const stepClaimTradingActive = useAppSelector(selectStepClaimTradingActive) + const stepMigrateFlowActive = useAppSelector(selectStepMigrateFlowActive) + const stepUnstakeActive = useAppSelector(selectStepUnstakeActive) + const stepRegisterActive = useAppSelector(selectStepRegisterActive) + const stepVestActive = useAppSelector(selectStepVestActive) + const stepApproveActive = useAppSelector(selectStepApproveActive) + const stepMigrateActive = useAppSelector(selectStepMigrateActive) + + const isGettingReward = useAppSelector(selectIsGettingReward) + const isClaimingAllRewards = useAppSelector(selectIsClaimingAllRewards) + const isRegisteringEntries = useAppSelector(selectIsRegisteringEntries) + const isVestingEscrowedEntries = useAppSelector(selectIsVestingEscrowedRewards) + const isApprovingEscrowMigrator = useAppSelector(selectIsApprovingEscrowMigrator) + const isMigratingEntries = useAppSelector(selectIsMigratingEntries) + const isUnstakingKwenta = useAppSelector(selectIsUnstakingKwenta) + + const handleGetRewards = useCallback(() => { + dispatch(claimStakingRewards()) + }, [dispatch]) + + const handleClaimAll = useCallback(() => { + dispatch(claimMultipleAllRewards()) + }, [dispatch]) + + const handleRegisterEntryIDs = useCallback(() => { + dispatch(registerEntries(unregisteredVestingEntryIDs.slice(0, REGISTER_BATCH_SIZE))) + }, [dispatch, unregisteredVestingEntryIDs]) + + const handleVestEntryIDs = useCallback(() => { + dispatch(vestEscrowedRewards(unvestedRegisteredEntryIDs.slice(0, VEST_BATCH_SIZE))) + }, [dispatch, unvestedRegisteredEntryIDs]) - const migrationSteps = useMemo( + const handleApproveEscrowMigrator = useCallback(() => { + dispatch(approveEscrowMigrator()) + }, [dispatch]) + + const handleMigrate = useCallback(() => { + dispatch(migrateEntries(unmigratedRegisteredEntryIDs.slice(0, MIGRATE_BATCH_SIZE))) + }, [dispatch, unmigratedRegisteredEntryIDs]) + + const handleUnstakeV1 = useCallback(() => { + dispatch(unstakeKwenta(wei(stakedKwentaBalance).toBN())) + }, [dispatch, stakedKwentaBalance]) + + const handelGoToStaking = useCallback(() => { + router.push(ROUTES.Dashboard.Stake) + }, [router]) + + const migrationSteps = useDebouncedMemo( () => [ { - key: 'step-1', - copy: t('dashboard.stake.tabs.revert.step-1-copy'), - label: t('dashboard.stake.tabs.revert.staked'), - value: formatNumber(stakedKwentaBalanceV2, { suggestDecimals: true }), - buttonLabel: t('dashboard.stake.tabs.revert.unstake'), - onClick: handleUnstakeKwenta, - active: stakedKwentaBalanceV2.gt(0), - loading: isUnstakingKwenta, - visible: true, + key: 'claim', + title: t('dashboard.stake.tabs.migrate.claim.title'), + copy: t('dashboard.stake.tabs.migrate.claim.copy'), + substeps: [ + { + key: 'claim-v1', + title: t('dashboard.stake.tabs.migrate.claim.title'), + copy: t('dashboard.stake.tabs.migrate.claim.copy'), + label: t('dashboard.stake.tabs.migrate.claim.rewards'), + value: formatNumber(claimableBalance, { suggestDecimals: true }), + buttonLabel: t('dashboard.stake.tabs.migrate.claim.button'), + onClick: handleGetRewards, + active: stepClaimActive, + loading: isGettingReward, + }, + { + key: 'claim-trading', + title: t('dashboard.stake.tabs.migrate.claim.trading.title'), + copy: t('dashboard.stake.tabs.migrate.claim.trading.copy'), + label: t('dashboard.stake.tabs.migrate.claim.rewards'), + value: formatNumber(kwentaTradingRewards, { suggestDecimals: true }), + buttonLabel: t('dashboard.stake.tabs.migrate.claim.trading.button'), + onClick: handleClaimAll, + active: stepClaimTradingActive, + loading: isClaimingAllRewards, + }, + ], + visible: stepClaimFlowActive, }, { - key: 'step-2', - copy: t('dashboard.stake.tabs.revert.step-2-copy'), - label: t('dashboard.stake.tabs.revert.reclaimable'), - value: formatNumber(totalVestableV2, { suggestDecimals: true }), - buttonLabel: t('dashboard.stake.tabs.revert.reclaim'), - onClick: handleVest, - active: stakedKwentaBalanceV2.eq(0) && totalVestableV2.gt(0), - loading: isVestingEscrowedRewards, - visible: totalVestableV2.gt(0), + key: 'migrate-flow', + title: t('dashboard.stake.tabs.migrate.migrate.title'), + copy: + isMigrationPeriodStarted && inMigrationPeriod ? ( + ]} + values={{ time: migrationTimeCountdown }} + /> + ) : ( + t('dashboard.stake.tabs.migrate.migrate.copy') + ), + substeps: [ + { + key: 'register', + title: t('dashboard.stake.tabs.migrate.migrate.register.title'), + copy: t('dashboard.stake.tabs.migrate.migrate.register.copy'), + label: t('dashboard.stake.tabs.migrate.migrate.remaining'), + value: numberOfUnregisteredVestingEntries, + buttonLabel: t('dashboard.stake.tabs.migrate.migrate.register.button'), + onClick: handleRegisterEntryIDs, + active: stepRegisterActive, + loading: isRegisteringEntries, + }, + { + key: 'vest', + title: t('dashboard.stake.tabs.migrate.migrate.vest.title'), + copy: t('dashboard.stake.tabs.migrate.migrate.vest.copy'), + label: t('dashboard.stake.tabs.migrate.migrate.remaining'), + value: numberOfUnvestedRegisteredVestingEntries, + buttonLabel: t('dashboard.stake.tabs.migrate.migrate.vest.button'), + onClick: handleVestEntryIDs, + active: stepVestActive, + loading: isVestingEscrowedEntries, + }, + { + key: 'approve', + title: t('dashboard.stake.tabs.migrate.migrate.approve.title'), + copy: t('dashboard.stake.tabs.migrate.migrate.approve.copy'), + buttonLabel: t('dashboard.stake.tabs.migrate.migrate.approve.button'), + onClick: handleApproveEscrowMigrator, + active: stepApproveActive, + loading: isApprovingEscrowMigrator, + }, + { + key: 'migrate', + title: t('dashboard.stake.tabs.migrate.migrate.migrate.title'), + copy: t('dashboard.stake.tabs.migrate.migrate.migrate.copy'), + label: t('dashboard.stake.tabs.migrate.migrate.remaining'), + value: numberOfUnmigratedRegisteredVestingEntries, + buttonLabel: t('dashboard.stake.tabs.migrate.migrate.migrate.button'), + onClick: handleMigrate, + active: stepMigrateActive, + loading: isMigratingEntries, + }, + ], + visible: stepMigrateFlowActive, + }, + { + key: 'unstake', + title: t('dashboard.stake.tabs.migrate.unstake.title'), + copy: isMigrationPeriodStarted + ? inMigrationPeriod + ? t('dashboard.stake.tabs.migrate.unstake.copy') + : t('dashboard.stake.tabs.migrate.migrate.closed') + : t('dashboard.stake.tabs.migrate.unstake.copy'), + substeps: [ + { + key: 'unstake-v1', + title: t('dashboard.stake.tabs.migrate.unstake.unstake-v1.title'), + copy: t('dashboard.stake.tabs.migrate.unstake.unstake-v1.copy'), + label: t('dashboard.stake.tabs.migrate.unstake.unstake-v1.kwenta-token'), + value: formatNumber(stakedKwentaBalance, { suggestDecimals: true }), + buttonLabel: t('dashboard.stake.tabs.migrate.unstake.unstake-v1.button'), + onClick: handleUnstakeV1, + active: stepUnstakeActive, + loading: isUnstakingKwenta, + }, + ], + visible: stepUnstakeActive, }, ], [ - handleUnstakeKwenta, - handleVest, + claimableBalance, + handleApproveEscrowMigrator, + handleClaimAll, + handleGetRewards, + handleMigrate, + handleRegisterEntryIDs, + handleUnstakeV1, + handleVestEntryIDs, + inMigrationPeriod, + isApprovingEscrowMigrator, + isClaimingAllRewards, + isGettingReward, + isMigratingEntries, + isMigrationPeriodStarted, + isRegisteringEntries, isUnstakingKwenta, - isVestingEscrowedRewards, - stakedKwentaBalanceV2, + isVestingEscrowedEntries, + kwentaTradingRewards, + migrationTimeCountdown, + numberOfUnmigratedRegisteredVestingEntries, + numberOfUnregisteredVestingEntries, + numberOfUnvestedRegisteredVestingEntries, + stakedKwentaBalance, + stepApproveActive, + stepClaimActive, + stepClaimFlowActive, + stepClaimTradingActive, + stepMigrateActive, + stepMigrateFlowActive, + stepRegisterActive, + stepUnstakeActive, + stepVestActive, t, - totalVestableV2, - ] + ], + 1500 + ) + + const migrationCompeleted = useMemo( + () => migrationSteps.filter(({ visible }) => visible).length === 0, + [migrationSteps] ) return ( - - {migrationSteps - .filter(({ visible }) => visible) - .map(({ key, copy, label, value, buttonLabel, active, onClick, loading }, i) => ( - - - ]} - /> - - - {copy} - - - - - - {label} - - - {value} - - - - - - ))} - + + + {migrationCompeleted ? ( + + + {t('dashboard.stake.tabs.migrate.migrate.completed')} + + + ]} + /> + + + ) : ( + + {migrationSteps.map(({ key, title, copy, visible, substeps }) => ( + + {title} + {visible && {copy}} + + + {visible && + substeps.map( + ({ key, title, copy, label, value, buttonLabel, onClick, active, loading }) => ( + + + {title} + {copy} + + + {label && value && active && ( + + {label} + + {value} + + + )} + + + + ) + )} + + + ))} + + )} + ) }) +const Emphasis = styled.b` + color: ${(props) => props.theme.colors.common.primaryYellow}; +` + +const SubstepsContainer = styled(FlexDivRowCentered)` + ${media.lessThan('md')` + flex-direction: column; + row-gap: 15px; + `} +` +const CardsContainer = styled(FlexDivCol)<{ $active: boolean }>` + flex-shrink: 1; + flex-grow: 5; + padding: 20px; + opacity: ${(props) => (props.$active ? '1' : '0.3')}; + background: ${(props) => props.theme.colors.selectedTheme.newTheme.containers.cards.background}; + border-radius: 20px; + border: 1px solid ${(props) => props.theme.colors.selectedTheme.newTheme.border.color}; + justify-content: flex-start; + column-gap: 50px; + row-gap: 25px; + ${media.lessThan('mdUp')` + column-gap: 25px; + width: 100%; + `} +` + const StepsContainer = styled(FlexDivRowCentered)` margin: 30px 0 15px; - ${media.lessThan('lg')` + ${media.lessThan('mdUp')` flex-direction: column; row-gap: 25px; margin: 0; @@ -129,12 +402,15 @@ const StepsContainer = styled(FlexDivRowCentered)` `} ` -const StyledStakingCard = styled(StakingCard)<{ $active: boolean }>` +const StyledStakingCard = styled(StakingCard)<{ $active?: boolean }>` + min-width: 120px; + cursor: pointer; width: 100%; + height: 270px; column-gap: 10px; opacity: ${(props) => (props.$active ? '1' : '0.3')}; + flex: ${(props) => (props.$active ? '10' : '1')}; padding: 25px; - height: 150px; border: 1px solid ${(props) => props.theme.colors.selectedTheme.newTheme.pill.yellow.outline.border}; ` @@ -143,4 +419,20 @@ const StyledHeading = styled(Heading)` font-weight: 400; ` +const MigrationContainer = styled.div` + ${media.lessThan('mdUp')` + padding: 15px; + `} + ${media.greaterThan('lg')` + margin-top: 20px; + `} +` + +const FullWidthStyledStakingCard = styled(StyledStakingCard)<{ $active: boolean }>` + display: ${(props) => (props.$active ? 'flex' : 'none')}; + flex-direction: column; + justify-content: center; + align-items: center; +` + export default MigrationSteps diff --git a/packages/app/src/sections/dashboard/Stake/StakingHeading.tsx b/packages/app/src/sections/dashboard/Stake/StakingHeading.tsx index 61e448854b..c22381bca2 100644 --- a/packages/app/src/sections/dashboard/Stake/StakingHeading.tsx +++ b/packages/app/src/sections/dashboard/Stake/StakingHeading.tsx @@ -4,14 +4,15 @@ import styled from 'styled-components' import Button from 'components/Button' import { FlexDivCol, FlexDivRowCentered } from 'components/layout/flex' -import { Heading } from 'components/Text' +import { Body, Heading } from 'components/Text' import { EXTERNAL_LINKS } from 'constants/links' interface StakingHeadingProps { title: string + copy?: string } -export const StakingHeading: FC = memo(({ title }) => { +export const StakingHeading: FC = memo(({ title, copy }) => { const { t } = useTranslation() return ( @@ -19,6 +20,7 @@ export const StakingHeading: FC = memo(({ title }) => { {title} + {copy && {copy}} = memo(({ title }) => { const TitleContainer = styled(FlexDivRowCentered)` margin-bottom: 30px; + column-gap: 10%; ` const StyledButton = styled(Button)` diff --git a/packages/app/src/sections/dashboard/Stake/StakingPortfolio.tsx b/packages/app/src/sections/dashboard/Stake/StakingPortfolio.tsx index 85dc5a521c..cfd72b3336 100644 --- a/packages/app/src/sections/dashboard/Stake/StakingPortfolio.tsx +++ b/packages/app/src/sections/dashboard/Stake/StakingPortfolio.tsx @@ -5,7 +5,6 @@ import { FlexDivCol, FlexDivRow, FlexDivRowCentered } from 'components/layout/fl import { Body } from 'components/Text' import media from 'styles/media' -import MigrationSteps from './MigrationSteps' import { StakingHeading } from './StakingHeading' import { StakingCards } from './types' @@ -17,39 +16,35 @@ export enum StakeTab { type StakingPortfolioProps = { title: string cardsInfo: StakingCards[] - isRollbackRequired?: boolean } -const StakingPortfolio: FC = memo( - ({ title, cardsInfo, isRollbackRequired = false }) => { - return ( - - - {isRollbackRequired && } - - {cardsInfo.map(({ category, card, onClick, icon }, i) => ( - - - {category} - {icon} - - - {card.map(({ key, title, value, onClick }) => ( - - {title} - - {value} - - - ))} - - - ))} - - - ) - } -) +const StakingPortfolio: FC = memo(({ title, cardsInfo }) => { + return ( + + + + {cardsInfo.map(({ key, category, card, onClick, icon }) => ( + + + {category} + {icon} + + + {card.map(({ key, title, value, onClick }) => ( + + {title} + + {value} + + + ))} + + + ))} + + + ) +}) const LabelContainer = styled(Body)` display: flex; @@ -70,12 +65,10 @@ const StyledFlexDivCol = styled(FlexDivCol)` ` const StakingPortfolioContainer = styled.div` - ${media.lessThan('mdUp')` - padding: 15px; - `} - ${media.greaterThan('lg')` - margin-top: 20px; + ${media.lessThan('lg')` + padding: 0px 15px; `} + margin-top: 20px; ` const CardsContainer = styled(FlexDivRowCentered)` diff --git a/packages/app/src/sections/dashboard/Stake/StakingTab.tsx b/packages/app/src/sections/dashboard/Stake/StakingTab.tsx index 8136a9defc..a881e53963 100644 --- a/packages/app/src/sections/dashboard/Stake/StakingTab.tsx +++ b/packages/app/src/sections/dashboard/Stake/StakingTab.tsx @@ -3,19 +3,16 @@ import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' -import HelpIcon from 'assets/svg/app/question-mark.svg' import Button from 'components/Button' import { FlexDivCol, FlexDivRow, FlexDivRowCentered } from 'components/layout/flex' import { SplitContainer } from 'components/layout/grid' import { Body, Heading } from 'components/Text' -import Tooltip from 'components/Tooltip/Tooltip' -import { NO_VALUE } from 'constants/placeholder' import { STAKING_DISABLED } from 'constants/ui' import { StakingCard } from 'sections/dashboard/Stake/card' import { useAppDispatch, useAppSelector } from 'state/hooks' import { claimStakingRewards } from 'state/staking/actions' import { - selectAPY, + selectApy, selectClaimableBalance, selectIsGettingReward, selectStakedKwentaBalance, @@ -32,7 +29,7 @@ const StakingTab = () => { const claimableBalance = useAppSelector(selectClaimableBalance) const stakedKwentaBalance = useAppSelector(selectStakedKwentaBalance) const isClaimingReward = useAppSelector(selectIsGettingReward) - const apy = useAppSelector(selectAPY) + const apy = useAppSelector(selectApy) const handleGetReward = useCallback(() => { dispatch(claimStakingRewards()) @@ -41,6 +38,7 @@ const StakingTab = () => { const stakingAndRewardsInfo: StakingCards[] = useMemo( () => [ { + key: 'staking', category: t('dashboard.stake.tabs.staking.title'), card: [ { @@ -57,6 +55,7 @@ const StakingTab = () => { flex: 1, }, { + key: 'rewards', category: t('dashboard.stake.portfolio.rewards.title'), card: [ { @@ -67,33 +66,6 @@ const StakingTab = () => { ], flex: 0.5, }, - { - category: t('dashboard.stake.portfolio.early-vest-rewards.title'), - icon: ( - - - - - - ), - card: [ - { - key: 'early-vest-rewards-claimable', - title: t('dashboard.stake.portfolio.early-vest-rewards.claimable'), - value: NO_VALUE, - }, - { - key: 'early-vest-rewards-epoch', - title: t('dashboard.stake.portfolio.early-vest-rewards.epoch'), - value: NO_VALUE, - }, - ], - flex: 1, - }, ], [apy, claimableBalance, stakedKwentaBalance, t] ) @@ -106,23 +78,25 @@ const StakingTab = () => { {t('dashboard.stake.tabs.staking.staking-rewards.title')} - {stakingAndRewardsInfo.map(({ category, card, flex, icon }, i) => ( - - - {category} {icon} - - - {card.map(({ key, title, value }) => ( - - {title} - - {value} - - - ))} - - - ))} + {stakingAndRewardsInfo + .filter((info) => !info.hidden) + .map(({ key, category, card, flex, icon }) => ( + + + {category} {icon} + + + {card.map(({ key, title, value }) => ( + + {title} + + {value} + + + ))} + + + ))}