From 61c2207e95d2765de086d92bfe7715419173ba2f Mon Sep 17 00:00:00 2001 From: Vihang Patil Date: Mon, 14 Oct 2024 23:55:58 +0200 Subject: [PATCH] feat(vault): staking --- apps/vault/src/assets/chevron-right-icon.svg | 3 + apps/vault/src/assets/home-icon.svg | 4 + apps/vault/src/assets/vault-logo.svg | 10 +- apps/vault/src/components/Breadcrumbs.tsx | 56 ++++++ apps/vault/src/components/CopyText.tsx | 40 ++++ apps/vault/src/components/NavigationBar.tsx | 43 ++++ apps/vault/src/components/VaultHeader.tsx | 8 +- apps/vault/src/components/index.ts | 6 + .../staking/VaultStakingAssetsTable.tsx | 118 +++++++++++ .../VaultStakingPositionStatusLabel.tsx | 43 ++++ .../positions/VaultStakingPositionsTable.tsx | 100 ++++++++++ .../components/vaultAssets/BalanceCard.tsx | 2 +- .../src/components/vaultAssets/DonutChart.tsx | 2 +- .../vaultAssets/VaultAssetsTable.tsx | 6 +- apps/vault/src/layouts/MainLayout.tsx | 3 +- apps/vault/src/pages/index.tsx | 10 +- apps/vault/src/pages/staking/index.tsx | 76 +++++++ .../positions/[stakingPositionId]/index.tsx | 186 ++++++++++++++++++ .../src/pages/staking/positions/index.tsx | 94 +++++++++ apps/vault/src/services/vault.ts | 93 ++++++++- apps/vault/src/types/domain.ts | 49 +++++ .../vaultAssets => utils}/formatters.ts | 8 + packages/tailwind-config/tailwind.config.js | 2 +- packages/ui/src/Header.tsx | 8 +- 24 files changed, 939 insertions(+), 31 deletions(-) create mode 100644 apps/vault/src/assets/chevron-right-icon.svg create mode 100644 apps/vault/src/assets/home-icon.svg create mode 100644 apps/vault/src/components/Breadcrumbs.tsx create mode 100644 apps/vault/src/components/CopyText.tsx create mode 100644 apps/vault/src/components/NavigationBar.tsx create mode 100644 apps/vault/src/components/staking/VaultStakingAssetsTable.tsx create mode 100644 apps/vault/src/components/staking/VaultStakingPositionStatusLabel.tsx create mode 100644 apps/vault/src/components/staking/positions/VaultStakingPositionsTable.tsx create mode 100644 apps/vault/src/pages/staking/index.tsx create mode 100644 apps/vault/src/pages/staking/positions/[stakingPositionId]/index.tsx create mode 100644 apps/vault/src/pages/staking/positions/index.tsx rename apps/vault/src/{components/vaultAssets => utils}/formatters.ts (72%) diff --git a/apps/vault/src/assets/chevron-right-icon.svg b/apps/vault/src/assets/chevron-right-icon.svg new file mode 100644 index 00000000..2ec4d700 --- /dev/null +++ b/apps/vault/src/assets/chevron-right-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/vault/src/assets/home-icon.svg b/apps/vault/src/assets/home-icon.svg new file mode 100644 index 00000000..8abd0a72 --- /dev/null +++ b/apps/vault/src/assets/home-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/vault/src/assets/vault-logo.svg b/apps/vault/src/assets/vault-logo.svg index c0a1b5aa..9ee7475d 100644 --- a/apps/vault/src/assets/vault-logo.svg +++ b/apps/vault/src/assets/vault-logo.svg @@ -1,9 +1,9 @@ - - + + + fill="#a54242"/> - + fill="#a54242"/> + diff --git a/apps/vault/src/components/Breadcrumbs.tsx b/apps/vault/src/components/Breadcrumbs.tsx new file mode 100644 index 00000000..03ca17c3 --- /dev/null +++ b/apps/vault/src/components/Breadcrumbs.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import HomeIcon from '../assets/home-icon.svg'; +import ChevronRightIcon from '../assets/chevron-right-icon.svg'; +import Image from 'next/image'; +import Link from 'next/link'; + +interface BreadcrumbsProps { + pages: Page[]; +} + +interface Page { + name: string; + href: string; + current?: boolean; +} + +const Breadcrumbs: React.FC = ({ pages }) => { + return ( + + ); +}; + +export default Breadcrumbs; diff --git a/apps/vault/src/components/CopyText.tsx b/apps/vault/src/components/CopyText.tsx new file mode 100644 index 00000000..89872441 --- /dev/null +++ b/apps/vault/src/components/CopyText.tsx @@ -0,0 +1,40 @@ +import Image from 'next/image'; +import CopyIcon from '../assets/copy-icon.png'; +import React, { useState } from 'react'; +import { InfoAlert } from '@/components'; + +interface CopyTextProps { + text: string; + alertMessage?: string; +} + +const CopyText: React.FC = ({ text, alertMessage }) => { + const [alertText, setAlertText] = useState(null); + return ( + <> + setAlertText(null)} + alertText={alertText || ''} + className={'my-3'} + /> +
+
+ {text} + {'Copy'} + navigator.clipboard + .writeText(text || '') + .then(() => setAlertText(alertMessage ?? 'Copied')) + } + /> +
+
+ + ); +}; + +export default CopyText; diff --git a/apps/vault/src/components/NavigationBar.tsx b/apps/vault/src/components/NavigationBar.tsx new file mode 100644 index 00000000..5db5980f --- /dev/null +++ b/apps/vault/src/components/NavigationBar.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { useRouter } from 'next/router'; + +const tabs = [ + { name: 'Home', href: '/' }, + { name: 'Staking', href: '/staking' }, + { name: 'Staking positions', href: '/staking/positions' }, +]; + +function classNames(...classes: string[]) { + return classes.filter(Boolean).join(' '); +} + +const NavigationBar: React.FC = () => { + const router = useRouter(); + const currentTab = tabs.find((tab) => tab.href === router.pathname); + const tabName = currentTab ? currentTab.name : undefined; + return ( +
+
+ +
+
+ ); +}; + +export default NavigationBar; diff --git a/apps/vault/src/components/VaultHeader.tsx b/apps/vault/src/components/VaultHeader.tsx index ebeb8d26..48fec128 100644 --- a/apps/vault/src/components/VaultHeader.tsx +++ b/apps/vault/src/components/VaultHeader.tsx @@ -6,9 +6,6 @@ import config from '@/firebase/config'; import { Header } from 'ui'; import vaultLogo from '../assets/vault-logo.svg'; import Link from 'next/link'; -import { useRouter } from 'next/router'; - -interface VaultHeaderProps {} // TODO: upgrade the secondary header here with the ui one const AuthHeader = dynamic( @@ -26,10 +23,7 @@ const AuthHeader = dynamic( } ); -const VaultHeader: React.FC = ({}) => { - const router = useRouter(); - const isLanding = - router.pathname.startsWith('/products/') || router.pathname === '/'; +const VaultHeader: React.FC = () => { return ( <> = ({ + vaultStakingAssets, +}) => { + return ( +
+ + {vaultStakingAssets.map(({ id, available, pending, staked }, index) => ( +
+
+ {id} +
+ + {available ? ( +
+ + Available: + + {available} +
+ + + +
+ ) : ( +
+ )} + {pending ? ( +
+ + Pending: + + {pending} +
+ + + +
+ ) : ( +
+ )} + + {staked ? ( +
+ + Staked: + + {staked} +
+ + + + + +
+ ) : ( +
+ )} +
+ ))} +
+ ); +}; + +export default VaultStakingAssetsTable; diff --git a/apps/vault/src/components/staking/VaultStakingPositionStatusLabel.tsx b/apps/vault/src/components/staking/VaultStakingPositionStatusLabel.tsx new file mode 100644 index 00000000..124e02eb --- /dev/null +++ b/apps/vault/src/components/staking/VaultStakingPositionStatusLabel.tsx @@ -0,0 +1,43 @@ +import { VaultStakingPositionStatus } from '@/types'; +import React from 'react'; + +interface VaultStakingPositionStatusLabelProps { + status: VaultStakingPositionStatus; +} + +const VaultStakingPositionStatusLabel: React.FC< + VaultStakingPositionStatusLabelProps +> = ({ status }) => { + let statusColor = 'gray'; + switch (status) { + case 'error': + case 'failed': + statusColor = 'red'; + break; + case 'creating': + case 'activating': + case 'pending': + case 'deactivating': + case 'withdrawing': + statusColor = 'orange'; + break; + case 'active': + case 'withdrawn': + statusColor = 'green'; + break; + case 'canceled': + case 'deactivated': + statusColor = 'gray'; + break; + } + return ( + + {status} + + ); +}; + +export default VaultStakingPositionStatusLabel; diff --git a/apps/vault/src/components/staking/positions/VaultStakingPositionsTable.tsx b/apps/vault/src/components/staking/positions/VaultStakingPositionsTable.tsx new file mode 100644 index 00000000..cdca1076 --- /dev/null +++ b/apps/vault/src/components/staking/positions/VaultStakingPositionsTable.tsx @@ -0,0 +1,100 @@ +import * as React from 'react'; +import { VaultStakingPosition } from '@/types'; +import Link from 'next/link'; +import { VaultStakingPositionStatusLabel } from '@/components'; +import { BasicButton } from 'ui'; +import { formatDateTime, formatNumber } from '@/utils/formatters'; + +interface VaultStakingPositionsTableProps { + vaultStakingPositions: VaultStakingPosition[]; +} + +const VaultStakingPositionsTable: React.FC = ({ + vaultStakingPositions, +}) => { + return ( +
+ + {vaultStakingPositions.map( + ( + { + id, + chainDescriptor, + amount, + rewardsAmount, + dateCreated, + status, + inProgress, + }, + index + ) => ( +
+
ID
+
+ + {id} + +
+ +
+ Chain descriptor +
+
{chainDescriptor}
+ +
Amount
+
{amount}
+ +
+ Rewards Amount +
+
{rewardsAmount}
+ +
+ Date Created +
+
+ {formatDateTime(new Date(dateCreated))} +
+ +
Status
+
+ +
+ +
+ In Progress +
+
+ {inProgress ? 'Yes' : 'No'} +
+
+ ) + )} +
+ ); +}; + +export default VaultStakingPositionsTable; diff --git a/apps/vault/src/components/vaultAssets/BalanceCard.tsx b/apps/vault/src/components/vaultAssets/BalanceCard.tsx index 071efa35..1276409c 100644 --- a/apps/vault/src/components/vaultAssets/BalanceCard.tsx +++ b/apps/vault/src/components/vaultAssets/BalanceCard.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Amount } from '@/types'; -import { formatCurrency } from './formatters'; +import { formatCurrency } from '@/utils/formatters'; interface BalanceCardProps { amount: Amount; diff --git a/apps/vault/src/components/vaultAssets/DonutChart.tsx b/apps/vault/src/components/vaultAssets/DonutChart.tsx index 142846af..96264ef7 100644 --- a/apps/vault/src/components/vaultAssets/DonutChart.tsx +++ b/apps/vault/src/components/vaultAssets/DonutChart.tsx @@ -13,7 +13,7 @@ import { TooltipItem, } from 'chart.js'; import { Amount } from '@/types'; -import { formatCurrency, formatPercent } from './formatters'; +import { formatCurrency, formatPercent } from '@/utils/formatters'; interface DonutChartProps { currency: string; diff --git a/apps/vault/src/components/vaultAssets/VaultAssetsTable.tsx b/apps/vault/src/components/vaultAssets/VaultAssetsTable.tsx index 0a3cfcdb..5b8b6b35 100644 --- a/apps/vault/src/components/vaultAssets/VaultAssetsTable.tsx +++ b/apps/vault/src/components/vaultAssets/VaultAssetsTable.tsx @@ -2,7 +2,11 @@ import * as React from 'react'; import { Amount, VaultAsset } from '@/types'; -import { formatCurrency, formatNumber, formatPercent } from './formatters'; +import { + formatCurrency, + formatNumber, + formatPercent, +} from '@/utils/formatters'; import Image from 'next/image'; import QrCodeIcon from '../../assets/qrcode-icon.png'; diff --git a/apps/vault/src/layouts/MainLayout.tsx b/apps/vault/src/layouts/MainLayout.tsx index 7a8883ef..4ad7788e 100644 --- a/apps/vault/src/layouts/MainLayout.tsx +++ b/apps/vault/src/layouts/MainLayout.tsx @@ -7,6 +7,7 @@ import dynamic from 'next/dynamic'; import Link from 'next/link'; import { PlatformFooter } from 'platform-js'; import vaultLogo from '../assets/vault-logo.svg'; +import { NavigationBar } from '@/components'; const AuthHeader = dynamic( async () => await (await import('platform-js')).AuthHeader, @@ -49,7 +50,7 @@ const MainLayout: React.FC = ({ children }) => { } > - {null} +
{children}
diff --git a/apps/vault/src/pages/index.tsx b/apps/vault/src/pages/index.tsx index da1e9c4a..d849cf2e 100644 --- a/apps/vault/src/pages/index.tsx +++ b/apps/vault/src/pages/index.tsx @@ -1,4 +1,4 @@ -import { createContext, ReactElement, useEffect, useState } from 'react'; +import React, { ReactElement, useEffect, useState } from 'react'; import Head from 'next/head'; import { getTitle, NextPageWithLayout, useAppState } from 'platform-js'; import { @@ -12,10 +12,10 @@ import { Amount, VaultAsset, VaultAssetAddress } from '@/types'; import { AddressModal, BalanceCard, + Breadcrumbs, CurrencyDropdown, DonutChart, VaultAssetsTable, - InfoAlert, } from '@/components'; /** @@ -23,7 +23,7 @@ import { * @returns */ -const Home: NextPageWithLayout = () => { +const HomePage: NextPageWithLayout = () => { const router = useRouter(); const state = useAppState(config); const [isLoading, setIsLoading] = useState(true); @@ -146,8 +146,8 @@ const Home: NextPageWithLayout = () => { ); }; -Home.getLayout = (page: ReactElement) => { +HomePage.getLayout = (page: ReactElement) => { return {page}; }; -export default Home; +export default HomePage; diff --git a/apps/vault/src/pages/staking/index.tsx b/apps/vault/src/pages/staking/index.tsx new file mode 100644 index 00000000..9eb28e0f --- /dev/null +++ b/apps/vault/src/pages/staking/index.tsx @@ -0,0 +1,76 @@ +import Head from 'next/head'; +import { getTitle, NextPageWithLayout, useAppState } from 'platform-js'; +import React, { ReactElement, useEffect, useState } from 'react'; +import { PrivateMainLayout } from '@/layouts'; +import { Breadcrumbs, StakingAssetsTable } from '@/components'; +import { VaultStakingAsset } from '@/types'; +import { useRouter } from 'next/router'; +import config from '@/firebase/config'; +import { useLazyGetVaultStakingAssetsQuery } from '@/services'; + +const StakingAssetsPage: NextPageWithLayout = () => { + const router = useRouter(); + const state = useAppState(config); + const [isLoading, setIsLoading] = useState(true); + const [vaultStakingAssets, setVaultStakingAssets] = useState< + VaultStakingAsset[] + >([]); + const [getVaultStakingAssetsQuery] = useLazyGetVaultStakingAssetsQuery(); + useEffect(() => { + const getVaultStakingAssets = async () => { + try { + setVaultStakingAssets(await getVaultStakingAssetsQuery().unwrap()); + setIsLoading(false); + } catch (error) { + console.log(error); + window.location.assign( + `https://${process.env.NEXT_PUBLIC_WEB_DOMAIN}/custody/not-registered` + ); + } + }; + if (state === 'REGISTERED') { + getVaultStakingAssets().then(); + } + }, [state, getVaultStakingAssetsQuery, router]); + let content: ReactElement; + if (isLoading) { + content = ( +

+ Loading... +

+ ); + } else if (vaultStakingAssets.length == 0) { + content = ( +

+ No stakable assets (ETH, SOL) found. +

+ ); + } else { + content = ; + } + return ( + <> + + {getTitle('Vault', 'Staking')} + + +
{content}
+ + ); +}; + +StakingAssetsPage.getLayout = (page: ReactElement) => { + return {page}; +}; + +export default StakingAssetsPage; diff --git a/apps/vault/src/pages/staking/positions/[stakingPositionId]/index.tsx b/apps/vault/src/pages/staking/positions/[stakingPositionId]/index.tsx new file mode 100644 index 00000000..fe64f3f8 --- /dev/null +++ b/apps/vault/src/pages/staking/positions/[stakingPositionId]/index.tsx @@ -0,0 +1,186 @@ +import Head from 'next/head'; +import { getTitle, NextPageWithLayout, useAppState } from 'platform-js'; +import React, { ReactElement, useEffect, useState } from 'react'; +import { MainLayout } from '@/layouts'; +import { + Breadcrumbs, + CopyText, + VaultStakingPositionStatusLabel, +} from '@/components'; +import { VaultStakingPosition } from '@/types'; +import { useRouter } from 'next/router'; +import config from '@/firebase/config'; +import { useLazyGetVaultStakingPositionQuery } from '@/services'; +import { useParams } from 'next/navigation'; +import { formatDateTime } from '@/utils/formatters'; + +const StakingPositionPage: NextPageWithLayout = () => { + const params = useParams<{ stakingPositionId: string }>(); + const router = useRouter(); + const state = useAppState(config); + const [isLoading, setIsLoading] = useState(true); + const [vaultStakingPosition, setVaultStakingPosition] = + useState(null); + const [getVaultStakingPositionQuery] = useLazyGetVaultStakingPositionQuery(); + useEffect(() => { + const getVaultStakingPosition = async () => { + try { + setVaultStakingPosition( + await getVaultStakingPositionQuery(params.stakingPositionId).unwrap() + ); + setIsLoading(false); + } catch (error) { + console.log(error); + window.location.assign( + `https://${process.env.NEXT_PUBLIC_WEB_DOMAIN}/custody/not-registered` + ); + } + }; + if (state === 'REGISTERED') { + getVaultStakingPosition().then(); + } + }, [state, params, getVaultStakingPositionQuery, router]); + + let content: ReactElement; + if (isLoading) { + content = ( +

+ Loading... +

+ ); + } else if (vaultStakingPosition) { + content = ( +
+
+

+ Vault Staking Position +

+

+ +

+
+
+
+
+
+ Chain +
+
+ {vaultStakingPosition.chainDescriptor} +
+
+
+
+ Amount +
+
+ {vaultStakingPosition.amount} +
+
+
+
+ Rewards Amount +
+
+ {vaultStakingPosition.rewardsAmount} +
+
+
+
+ Created on +
+
+ {formatDateTime(new Date(vaultStakingPosition.dateCreated))} +
+
+
+
+ Status +
+
+ +
+
+
+
+ In Progress +
+
+ {vaultStakingPosition.inProgress ? 'Yes' : 'No'} +
+
+
+
+ Provider +
+
+ {vaultStakingPosition.providerName} +
+
+ {vaultStakingPosition.inProgressTxId ? ( +
+
+ In progress transaction ID +
+
+ {vaultStakingPosition.inProgressTxId} +
+
+ ) : ( + <> + )} +
+
+
+ ); + } else { + content = ( +

+ Staking Position Not Found +

+ ); + } + + return ( + <> + + {getTitle('Vault', 'Staking', 'Positions')} + + + + {content} + + ); +}; +StakingPositionPage.getLayout = (page: ReactElement) => { + return {page}; +}; + +export default StakingPositionPage; diff --git a/apps/vault/src/pages/staking/positions/index.tsx b/apps/vault/src/pages/staking/positions/index.tsx new file mode 100644 index 00000000..6c86a271 --- /dev/null +++ b/apps/vault/src/pages/staking/positions/index.tsx @@ -0,0 +1,94 @@ +import Head from 'next/head'; +import { getTitle, NextPageWithLayout, useAppState } from 'platform-js'; +import React, { ReactElement, useEffect, useState } from 'react'; +import { PrivateMainLayout } from '@/layouts'; +import { + Breadcrumbs, + StakingAssetsTable, + VaultStakingPositionsTable, +} from '@/components'; +import { VaultStakingPosition } from '@/types'; +import { useRouter } from 'next/router'; +import config from '@/firebase/config'; +import { useLazyGetVaultStakingPositionsQuery } from '@/services'; + +const StakingPositionsPage: NextPageWithLayout = () => { + const router = useRouter(); + const state = useAppState(config); + const [isLoading, setIsLoading] = useState(true); + const [vaultStakingPositions, setVaultStakingPositions] = useState< + VaultStakingPosition[] + >([]); + const [getVaultStakingPositionsQuery] = + useLazyGetVaultStakingPositionsQuery(); + useEffect(() => { + const getVaultStakingPositions = async () => { + try { + setVaultStakingPositions( + await getVaultStakingPositionsQuery().unwrap() + ); + setIsLoading(false); + } catch (error) { + console.log(error); + window.location.assign( + `https://${process.env.NEXT_PUBLIC_WEB_DOMAIN}/custody/not-registered` + ); + } + }; + if (state === 'REGISTERED') { + getVaultStakingPositions().then(); + } + }, [state, getVaultStakingPositionsQuery, router]); + let content: ReactElement; + if (isLoading) { + content = ( +

+ Loading... +

+ ); + } else if (vaultStakingPositions.length == 0) { + content = ( +

+ No stake positions found. +

+ ); + } else { + content = ( + + ); + } + return ( + <> + + {getTitle('Vault', 'Staking', 'Positions')} + + +
{content}
+ + ); +}; + +StakingPositionsPage.getLayout = (page: ReactElement) => { + return {page}; +}; + +export default StakingPositionsPage; diff --git a/apps/vault/src/services/vault.ts b/apps/vault/src/services/vault.ts index 8e47a457..0806872e 100644 --- a/apps/vault/src/services/vault.ts +++ b/apps/vault/src/services/vault.ts @@ -1,6 +1,11 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; import { getIdToken } from 'core'; -import { VaultAsset, VaultAssetAddress } from '@/types'; +import { + VaultAsset, + VaultAssetAddress, + VaultStakingAsset, + VaultStakingPosition, +} from '@/types'; export const vaultApi = createApi({ baseQuery: fetchBaseQuery({ @@ -15,15 +20,81 @@ export const vaultApi = createApi({ }, }), - tagTypes: ['VaultAssets', 'VaultAssetsAddress'], + tagTypes: [ + 'VaultAssets', + 'VaultAssetsAddress', + 'VaultStakingAssets', + 'VaultStakingPositions', + ], + endpoints: (builder) => ({ getVaultAssets: builder.query({ query: (currency) => `assets?currency=${currency}`, - providesTags: ['VaultAssets'], + providesTags: (vaultAssets) => + vaultAssets + ? [ + ...vaultAssets.map(({ id }) => ({ + type: 'VaultAssets' as const, + id, + })), + { type: 'VaultAssets', id: 'LIST' }, + ] + : [{ type: 'VaultAssets', id: 'LIST' }], }), + getVaultAssetAddresses: builder.query({ query: (assetId) => `assets/${assetId}/addresses`, - providesTags: ['VaultAssetsAddress'], + providesTags: (vaultAssetsAddresses) => + vaultAssetsAddresses + ? [ + ...vaultAssetsAddresses.map(({ assetId }) => ({ + type: 'VaultAssetsAddress' as const, + id: assetId, + })), + { type: 'VaultAssetsAddress', id: 'LIST' }, + ] + : [{ type: 'VaultAssetsAddress', id: 'LIST' }], + }), + + getVaultStakingAssets: builder.query({ + query: () => 'staking/assets', + providesTags: (vaultStakingAssets) => + vaultStakingAssets + ? [ + ...vaultStakingAssets.map(({ id }) => ({ + type: 'VaultStakingAssets' as const, + id, + })), + { type: 'VaultStakingAssets', id: 'LIST' }, + ] + : [{ type: 'VaultStakingAssets', id: 'LIST' }], + }), + + getVaultStakingPositions: builder.query({ + query: () => 'staking/positions', + providesTags: (vaultStakingPositions) => + vaultStakingPositions + ? [ + ...vaultStakingPositions.map(({ id }) => ({ + type: 'VaultStakingPositions' as const, + id, + })), + { type: 'VaultStakingPositions', id: 'LIST' }, + ] + : [{ type: 'VaultStakingPositions', id: 'LIST' }], + }), + + getVaultStakingPosition: builder.query({ + query: (stakingPositionId) => `staking/positions/${stakingPositionId}`, + providesTags: (vaultStakingPosition) => + vaultStakingPosition + ? [ + { + type: 'VaultStakingPositions' as const, + id: vaultStakingPosition.id, + }, + ] + : [], }), }), }); @@ -34,7 +105,19 @@ export const { useLazyGetVaultAssetsQuery, useGetVaultAssetAddressesQuery, useLazyGetVaultAssetAddressesQuery, + useGetVaultStakingAssetsQuery, + useLazyGetVaultStakingAssetsQuery, + useGetVaultStakingPositionsQuery, + useLazyGetVaultStakingPositionsQuery, + useGetVaultStakingPositionQuery, + useLazyGetVaultStakingPositionQuery, } = vaultApi; // export endpoints for use in SSR -export const { getVaultAssets, getVaultAssetAddresses } = vaultApi.endpoints; +export const { + getVaultAssets, + getVaultAssetAddresses, + getVaultStakingAssets, + getVaultStakingPositions, + getVaultStakingPosition, +} = vaultApi.endpoints; diff --git a/apps/vault/src/types/domain.ts b/apps/vault/src/types/domain.ts index b8880c6e..6e5b19b3 100644 --- a/apps/vault/src/types/domain.ts +++ b/apps/vault/src/types/domain.ts @@ -18,3 +18,52 @@ export interface VaultAssetAddress { legacyAddress?: string; tag?: string; } + +export interface VaultStakingAsset { + id: string; + available?: number; + pending?: number; + staked?: number; +} + +export type VaultStakingPositionStatus = + | 'error' + | 'failed' + | 'creating' + | 'canceled' + | 'pending' + | 'activating' + | 'active' + | 'deactivating' + | 'deactivated' + | 'withdrawing' + | 'withdrawn' + | string; + +export interface VaultStakingPosition { + id: string; + vaultAccountId: string; + validatorName: string; + providerName: string; + chainDescriptor: string; + amount: string; + rewardsAmount: string; + dateCreated: string; + status: VaultStakingPositionStatus; + relatedTransactions: Array; + validatorAddress: string; + providerId: string; + availableActions: Array; + inProgress: boolean; + inProgressTxId?: string; + blockchainPositionInfo: BlockchainPositionInfo; +} + +export interface RelatedTransaction { + txId: string; + completed: boolean; +} + +export interface BlockchainPositionInfo { + stakeAccountAddress?: string; +} diff --git a/apps/vault/src/components/vaultAssets/formatters.ts b/apps/vault/src/utils/formatters.ts similarity index 72% rename from apps/vault/src/components/vaultAssets/formatters.ts rename to apps/vault/src/utils/formatters.ts index f22fd4b0..5f68acda 100644 --- a/apps/vault/src/components/vaultAssets/formatters.ts +++ b/apps/vault/src/utils/formatters.ts @@ -22,3 +22,11 @@ export const formatPercent = (value: number) => { }); return numberFormatter.format(value / 100.0); }; +export const formatDateTime = (date: Date) => { + // const dateTimeFormatter = new Intl.DateTimeFormat([], { + // dateStyle: 'short', + // timeStyle: 'medium', + // }); + // return dateTimeFormatter.format(date); + return date.toISOString().replace(/T/, ' ').replace('Z', ' UTC'); +}; diff --git a/packages/tailwind-config/tailwind.config.js b/packages/tailwind-config/tailwind.config.js index ed2f558f..cc77ab21 100644 --- a/packages/tailwind-config/tailwind.config.js +++ b/packages/tailwind-config/tailwind.config.js @@ -144,7 +144,7 @@ module.exports = { colors: { brand: { light: { - primary: '#090A0B', + primary: '#a54242', secondary: '#2D3339', tertiary: '#6C757D', }, diff --git a/packages/ui/src/Header.tsx b/packages/ui/src/Header.tsx index 4c367bcb..c3b295c8 100644 --- a/packages/ui/src/Header.tsx +++ b/packages/ui/src/Header.tsx @@ -18,11 +18,11 @@ export const Header: React.FC = ({ transparent ? '' : 'ui-bg-bg-light-primary' }`} > -
+
{logo}
{children}
@@ -41,8 +41,8 @@ export const SecondaryHeader: React.FC = ({ children, }) => { return ( -