diff --git a/dapp/src/components/TransactionModal/FeesTooltip/FeesTooltipItem.tsx b/dapp/src/components/TransactionModal/FeesTooltip/FeesTooltipItem.tsx index efa995f0d..3662ec8a6 100644 --- a/dapp/src/components/TransactionModal/FeesTooltip/FeesTooltipItem.tsx +++ b/dapp/src/components/TransactionModal/FeesTooltip/FeesTooltipItem.tsx @@ -19,8 +19,7 @@ export function FeesTooltipItem({ label, amount, ...props }: FeesItemType) { size="sm" amount={amount} color="gold.300" - balanceFontWeight="semibold" - symbolFontWeight="semibold" + fontWeight="semibold" desiredDecimals={DESIRED_DECIMALS_FOR_FEE} {...props} /> diff --git a/dapp/src/components/shared/ActivitiesList/ActivitiesList.tsx b/dapp/src/components/shared/ActivitiesList/ActivitiesList.tsx index dfb744afe..aae2c762b 100644 --- a/dapp/src/components/shared/ActivitiesList/ActivitiesList.tsx +++ b/dapp/src/components/shared/ActivitiesList/ActivitiesList.tsx @@ -3,8 +3,8 @@ import { List, ListItem, ListProps } from "@chakra-ui/react" import { AnimatePresence, Variants, motion } from "framer-motion" import { useAppDispatch, + useLatestCompletedActivities, useIsFetchedWalletData, - useLatestActivities, } from "#/hooks" import { deleteLatestActivity } from "#/store/wallet" import ActivitiesListItem from "./ActivitiesListItem" @@ -19,19 +19,19 @@ const listItemVariants: Variants = { function ActivitiesList(props: ListProps) { const dispatch = useAppDispatch() - const latestActivities = useLatestActivities() + const activities = useLatestCompletedActivities() const isFetchedWalletData = useIsFetchedWalletData() const handleItemDismiss = (activityId: string) => { dispatch(deleteLatestActivity(activityId)) } - if (!isFetchedWalletData || latestActivities.length === 0) return null + if (!isFetchedWalletData || activities.length === 0) return null return ( - {latestActivities.map(({ id, amount, status, type, txHash }) => ( + {activities.map(({ id, amount, status, type, txHash }) => ( {isCompleted - ? `${type === "withdraw" ? "Unstaking" : "Staking"} completed` - : `${type === "withdraw" ? "Unstaking" : "Staking"}...`} + ? `${convertActivityTypeToLabel(type)} completed` + : `${convertActivityTypeToLabel(type)}...`} diff --git a/dapp/src/components/shared/CurrencyBalance/index.tsx b/dapp/src/components/shared/CurrencyBalance/index.tsx index 0be487b6a..3ff7973e7 100644 --- a/dapp/src/components/shared/CurrencyBalance/index.tsx +++ b/dapp/src/components/shared/CurrencyBalance/index.tsx @@ -24,9 +24,10 @@ export type CurrencyBalanceProps = { | "greater-balance-xxl" | "unstyled" > - balanceFontWeight?: string - symbolFontWeight?: string symbolPosition?: "prefix" | "suffix" + withDots?: boolean + balanceTextProps?: TextProps + symbolTextProps?: TextProps } & TextProps export function CurrencyBalance({ @@ -36,10 +37,11 @@ export function CurrencyBalance({ desiredDecimals: customDesiredDecimals, size, variant, - balanceFontWeight = "bold", - symbolFontWeight = "bold", symbolPosition = "suffix", + withDots = false, as, + balanceTextProps, + symbolTextProps, ...textProps }: CurrencyBalanceProps) { const styles = useMultiStyleConfig("CurrencyBalance", { @@ -67,18 +69,14 @@ export function CurrencyBalance({ {balance} + {withDots && ".."} - + {symbol} diff --git a/dapp/src/components/shared/Pagination/Pagination.tsx b/dapp/src/components/shared/Pagination/Pagination.tsx index 969d2eda5..493e8b9b8 100644 --- a/dapp/src/components/shared/Pagination/Pagination.tsx +++ b/dapp/src/components/shared/Pagination/Pagination.tsx @@ -33,7 +33,7 @@ function Pagination(props: PaginationProps) { return ( - + {children} diff --git a/dapp/src/components/shared/Pagination/PaginationFooter.tsx b/dapp/src/components/shared/Pagination/PaginationFooter.tsx new file mode 100644 index 000000000..580023482 --- /dev/null +++ b/dapp/src/components/shared/Pagination/PaginationFooter.tsx @@ -0,0 +1,28 @@ +import React from "react" +import { HStack, StackProps } from "@chakra-ui/react" + +const TOP_SPACE = 6 + +type PaginationFooterProps = StackProps & { containerPadding: number } + +function PaginationFooter(props: PaginationFooterProps) { + const { children, containerPadding, ...restProps } = props + + return ( + + {children} + + ) +} + +export default PaginationFooter diff --git a/dapp/src/components/shared/Pagination/index.ts b/dapp/src/components/shared/Pagination/index.ts index 84337122b..ea1f4777a 100644 --- a/dapp/src/components/shared/Pagination/index.ts +++ b/dapp/src/components/shared/Pagination/index.ts @@ -2,3 +2,4 @@ export { default as Pagination } from "./Pagination" export { default as PaginationStatus } from "./PaginationStatus" export { default as PaginationPage } from "./PaginationPage" export { default as PaginationButton } from "./PaginationButton" +export { default as PaginationFooter } from "./PaginationFooter" diff --git a/dapp/src/hooks/store/index.ts b/dapp/src/hooks/store/index.ts index 1e6b74506..aafdf9918 100644 --- a/dapp/src/hooks/store/index.ts +++ b/dapp/src/hooks/store/index.ts @@ -6,9 +6,9 @@ export * from "./useActionFlowStatus" export * from "./useActionFlowActiveStep" export * from "./useActionFlowTokenAmount" export * from "./useActionFlowTxHash" -export * from "./useCompletedActivities" -export * from "./useLatestActivities" export * from "./useAllActivitiesCount" export * from "./useActionFlowPause" export * from "./useIsSignedMessage" export { default as useHasFetchedActivities } from "./useHasFetchedActivities" +export { default as useActivities } from "./useActivities" +export { default as useLatestCompletedActivities } from "./useLatestCompletedActivities" diff --git a/dapp/src/hooks/store/useActivities.ts b/dapp/src/hooks/store/useActivities.ts new file mode 100644 index 000000000..b9f022b29 --- /dev/null +++ b/dapp/src/hooks/store/useActivities.ts @@ -0,0 +1,6 @@ +import { selectActivities } from "#/store/wallet" +import { useAppSelector } from "./useAppSelector" + +export default function useActivities() { + return useAppSelector(selectActivities) +} diff --git a/dapp/src/hooks/store/useCompletedActivities.ts b/dapp/src/hooks/store/useCompletedActivities.ts deleted file mode 100644 index 604309422..000000000 --- a/dapp/src/hooks/store/useCompletedActivities.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { selectCompletedActivities } from "#/store/wallet" -import { useAppSelector } from "./useAppSelector" - -export function useCompletedActivities() { - return useAppSelector(selectCompletedActivities) -} diff --git a/dapp/src/hooks/store/useLatestActivities.ts b/dapp/src/hooks/store/useLatestActivities.ts deleted file mode 100644 index f9dcbf7a3..000000000 --- a/dapp/src/hooks/store/useLatestActivities.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { selectLatestActivities } from "#/store/wallet" -import { useAppSelector } from "./useAppSelector" - -export function useLatestActivities() { - return useAppSelector(selectLatestActivities) -} diff --git a/dapp/src/hooks/store/useLatestCompletedActivities.ts b/dapp/src/hooks/store/useLatestCompletedActivities.ts new file mode 100644 index 000000000..1fc1178f8 --- /dev/null +++ b/dapp/src/hooks/store/useLatestCompletedActivities.ts @@ -0,0 +1,6 @@ +import { selectLatestCompletedActivities } from "#/store/wallet" +import { useAppSelector } from "./useAppSelector" + +export default function useLatestCompletedActivities() { + return useAppSelector(selectLatestCompletedActivities) +} diff --git a/dapp/src/pages/DashboardPage/DashboardCard.tsx b/dapp/src/pages/DashboardPage/DashboardCard.tsx index a4102e8ed..2c676c020 100644 --- a/dapp/src/pages/DashboardPage/DashboardCard.tsx +++ b/dapp/src/pages/DashboardPage/DashboardCard.tsx @@ -1,117 +1,15 @@ import React from "react" -import { CurrencyBalanceWithConversion } from "#/components/shared/CurrencyBalanceWithConversion" -import { TextMd } from "#/components/shared/Typography" -import { useBitcoinPosition, useTransactionModal } from "#/hooks" -import { ACTION_FLOW_TYPES } from "#/types" -import { - Button, - ButtonProps, - Card, - CardBody, - CardHeader, - CardProps, - HStack, - // Tag, - VStack, -} from "@chakra-ui/react" +import { Card, CardBody, CardProps, VStack } from "@chakra-ui/react" import { ActivitiesList } from "#/components/shared/ActivitiesList" -import ArrivingSoonTooltip from "#/components/ArrivingSoonTooltip" -import { featureFlags } from "#/constants" -import UserDataSkeleton from "#/components/shared/UserDataSkeleton" import TransactionHistory from "./TransactionHistory" +import PositionDetails from "./PositionDetails" -const isWithdrawalFlowEnabled = featureFlags.WITHDRAWALS_ENABLED - -const buttonStyles: ButtonProps = { - size: "lg", - flex: 1, - maxW: "12.5rem", // 200px - fontWeight: "bold", - lineHeight: 6, - px: 7, - h: "auto", -} - -type DashboardCardProps = CardProps - -export default function DashboardCard(props: DashboardCardProps) { - const { data } = useBitcoinPosition() - const bitcoinAmount = data?.estimatedBitcoinBalance ?? 0n - - const openDepositModal = useTransactionModal(ACTION_FLOW_TYPES.STAKE) - const openWithdrawModal = useTransactionModal(ACTION_FLOW_TYPES.UNSTAKE) - +export default function DashboardCard(props: CardProps) { return ( - - - - My position - {/* TODO: Uncomment when position will be implemented */} - {/* {positionPercentage && ( - - Top {positionPercentage}% - - )} */} - - - - - - - - - - - - - - - - - - - - - - + + + diff --git a/dapp/src/pages/DashboardPage/PositionDetails.tsx b/dapp/src/pages/DashboardPage/PositionDetails.tsx new file mode 100644 index 000000000..a0e8305da --- /dev/null +++ b/dapp/src/pages/DashboardPage/PositionDetails.tsx @@ -0,0 +1,103 @@ +import React from "react" +import { CurrencyBalanceWithConversion } from "#/components/shared/CurrencyBalanceWithConversion" +import { useBitcoinPosition, useTransactionModal } from "#/hooks" +import { ACTION_FLOW_TYPES } from "#/types" +import { + Button, + ButtonProps, + Flex, + HStack, + // Tag, + VStack, +} from "@chakra-ui/react" +import ArrivingSoonTooltip from "#/components/ArrivingSoonTooltip" +import UserDataSkeleton from "#/components/shared/UserDataSkeleton" +import { featureFlags } from "#/constants" +import { TextMd } from "#/components/shared/Typography" + +const isWithdrawalFlowEnabled = featureFlags.WITHDRAWALS_ENABLED + +const buttonStyles: ButtonProps = { + size: "lg", + flex: 1, + maxW: "12.5rem", // 200px + fontWeight: "bold", + lineHeight: 6, + px: 7, + h: "auto", +} + +export default function PositionDetails() { + const { data } = useBitcoinPosition() + const bitcoinAmount = data?.estimatedBitcoinBalance ?? 0n + + const openDepositModal = useTransactionModal(ACTION_FLOW_TYPES.STAKE) + const openWithdrawModal = useTransactionModal(ACTION_FLOW_TYPES.UNSTAKE) + + return ( + + + + My position + {/* TODO: Uncomment when position will be implemented */} + {/* {positionPercentage && ( + + Top {positionPercentage}% + + )} */} + + + + + + + + + + + + + + + + + + + + ) +} diff --git a/dapp/src/pages/DashboardPage/TransactionHistory/EstimatedDuration.tsx b/dapp/src/pages/DashboardPage/TransactionHistory/EstimatedDuration.tsx new file mode 100644 index 000000000..e13eaedf1 --- /dev/null +++ b/dapp/src/pages/DashboardPage/TransactionHistory/EstimatedDuration.tsx @@ -0,0 +1,40 @@ +import React from "react" +import { HStack, Box, Tag, TagLabel } from "@chakra-ui/react" +import Spinner from "#/components/shared/Spinner" +import { + convertActivityTypeToLabel, + getEstimatedDuration, + isActivityCompleted, +} from "#/utils" +import { Activity } from "#/types" + +export default function EstimatedDuration({ + activity, +}: { + activity: Activity +}) { + if (isActivityCompleted(activity)) return null + + return ( + + + + {`${convertActivityTypeToLabel(activity.type)} transaction pending...`} + + + + Est. duration + + {getEstimatedDuration(activity.amount, activity.type)} + + + + + ) +} diff --git a/dapp/src/pages/DashboardPage/TransactionHistory/TransactionTable.tsx b/dapp/src/pages/DashboardPage/TransactionHistory/TransactionTable.tsx index d19d9113d..7c2547ea9 100644 --- a/dapp/src/pages/DashboardPage/TransactionHistory/TransactionTable.tsx +++ b/dapp/src/pages/DashboardPage/TransactionHistory/TransactionTable.tsx @@ -1,42 +1,52 @@ import React from "react" -import { HStack, Card, CardBody, Box, VisuallyHidden } from "@chakra-ui/react" +import { HStack, Card, CardBody, Box, Flex, Icon } from "@chakra-ui/react" import { Pagination, PaginationButton, + PaginationFooter, PaginationPage, PaginationStatus, } from "#/components/shared/Pagination" import { TextSm } from "#/components/shared/Typography" import { CurrencyBalance } from "#/components/shared/CurrencyBalance" -import { displayBlockTimestamp } from "#/utils" +import { displayBlockTimestamp, getActivityTimestamp } from "#/utils" import { Activity } from "#/types" import BlockExplorerLink from "#/components/shared/BlockExplorerLink" import { IconArrowUpRight } from "@tabler/icons-react" -import { useCompletedActivities } from "#/hooks" +import { useActivities } from "#/hooks" +import { semanticTokens } from "#/theme/utils" +import EstimatedDuration from "./EstimatedDuration" + +const BLOCK_EXPLORER_CELL_MIN_WIDTH = 16 export default function TransactionTable() { - const completedActivities = useCompletedActivities() + const activities = useActivities() return ( - + {(pageData: Activity[]) => - pageData.map( - ({ id, initializedAt, finalizedAt, type, txHash, amount }) => ( - - + pageData.map((activity) => ( + + + - {displayBlockTimestamp(finalizedAt ?? initializedAt)} + {displayBlockTimestamp(getActivityTimestamp(activity))} - + - {type} + {activity.type} @@ -44,45 +54,54 @@ export default function TransactionTable() { color="grey.700" size="sm" fontWeight="bold" - amount={amount} + amount={activity.amount} currency="bitcoin" + withDots /> - {txHash && ( + {activity.txHash ? ( - - View transaction details + + Details + + + ) : ( + )} - - - ), - ) + + + + + )) } - @@ -90,7 +109,7 @@ export default function TransactionTable() { - + ) } diff --git a/dapp/src/pages/DashboardPage/TransactionHistory/index.tsx b/dapp/src/pages/DashboardPage/TransactionHistory/index.tsx index b54500462..2f0b668e1 100644 --- a/dapp/src/pages/DashboardPage/TransactionHistory/index.tsx +++ b/dapp/src/pages/DashboardPage/TransactionHistory/index.tsx @@ -1,13 +1,13 @@ import React from "react" import { StackProps, VStack, Image } from "@chakra-ui/react" import { TextMd } from "#/components/shared/Typography" -import { useCompletedActivities, useIsFetchedWalletData } from "#/hooks" +import { useAllActivitiesCount, useIsFetchedWalletData } from "#/hooks" import UserDataSkeleton from "#/components/shared/UserDataSkeleton" import emptyStateIllustration from "#/assets/images/empty-state.svg" import TransactionTable from "./TransactionTable" function TransactionHistoryContent() { - const completedActivities = useCompletedActivities() + const activitiesCount = useAllActivitiesCount() const isFetchedWalletData = useIsFetchedWalletData() if (!isFetchedWalletData) @@ -19,7 +19,7 @@ function TransactionHistoryContent() { ) - if (completedActivities.length === 0) + if (activitiesCount === 0) return ( @@ -8,18 +8,9 @@ export const selectEstimatedBtcBalance = (state: RootState): bigint => export const selectSharesBalance = (state: RootState): bigint => state.wallet.sharesBalance -export const selectLatestActivities = createSelector( - (state: RootState) => state.wallet.latestActivities, - (latestActivities) => - sortActivitiesByTimestamp(Object.values(latestActivities)), -) - -export const selectCompletedActivities = createSelector( +export const selectActivities = createSelector( (state: RootState) => state.wallet.activities, - (activities) => - sortActivitiesByTimestamp( - activities.filter((activity) => isActivityCompleted(activity)), - ), + (activities) => sortActivitiesByTimestamp(activities), ) export const selectAllActivitiesCount = createSelector( @@ -27,6 +18,17 @@ export const selectAllActivitiesCount = createSelector( (activities) => activities.length, ) +export const selectLatestActivities = createSelector( + (state: RootState) => state.wallet.latestActivities, + (latestActivities) => + sortActivitiesByTimestamp(Object.values(latestActivities)), +) + +export const selectLatestCompletedActivities = createSelector( + selectLatestActivities, + (latestActivities) => filterCompletedActivities(latestActivities), +) + export const selectIsSignedMessage = (state: RootState): boolean => state.wallet.isSignedMessage diff --git a/dapp/src/store/wallet/walletSlice.ts b/dapp/src/store/wallet/walletSlice.ts index 4d2958ab3..ba89834f0 100644 --- a/dapp/src/store/wallet/walletSlice.ts +++ b/dapp/src/store/wallet/walletSlice.ts @@ -67,7 +67,7 @@ export const walletSlice = createSlice({ .filter( (activity) => activity.id.includes(latestActivity.id) && - activity.status === "completed", + isActivityCompleted(activity), ) .sort((first, second) => { // The withdraw id is: `-` @@ -108,6 +108,7 @@ export const walletSlice = createSlice({ ...state.latestActivities, [activity.id]: activity, } + state.activities = [...state.activities, activity] }, }, }) diff --git a/dapp/src/theme/CurrencyBalance.ts b/dapp/src/theme/CurrencyBalance.ts index dda72ff61..8f4586daf 100644 --- a/dapp/src/theme/CurrencyBalance.ts +++ b/dapp/src/theme/CurrencyBalance.ts @@ -16,6 +16,7 @@ const baseStyleBalance = defineStyle(({ symbolPosition }) => ({ })) const baseStyleSymbol = defineStyle({ + fontWeight: "bold", fontSize: "md", lineHeight: "md", }) diff --git a/dapp/src/theme/Tag.ts b/dapp/src/theme/Tag.ts index 1ff607434..1c1169fe4 100644 --- a/dapp/src/theme/Tag.ts +++ b/dapp/src/theme/Tag.ts @@ -6,7 +6,8 @@ const multiStyleConfig = createMultiStyleConfigHelpers(parts.keys) const baseStyleContainer = defineStyle({ borderRadius: "full", w: "fit-content", - bg: "gold.100", + color: "grey.700", + bg: "gold.200", paddingX: 4, paddingY: 2.5, shadow: "none", diff --git a/dapp/src/theme/utils/semanticTokens.ts b/dapp/src/theme/utils/semanticTokens.ts index 699328ebe..da48ade6e 100644 --- a/dapp/src/theme/utils/semanticTokens.ts +++ b/dapp/src/theme/utils/semanticTokens.ts @@ -2,6 +2,7 @@ export const semanticTokens = { space: { header_height: 24, modal_shift: "9.75rem", // 156px + dashboard_card_padding: 5, }, sizes: { sidebar_width: 80, diff --git a/dapp/src/types/activity.ts b/dapp/src/types/activity.ts index 2cec46c5c..7d92168f1 100644 --- a/dapp/src/types/activity.ts +++ b/dapp/src/types/activity.ts @@ -16,6 +16,8 @@ type ConditionalActivityData = txHash?: string } +export type ActivityType = ConditionalActivityData["type"] + export type Activity = CommonActivityData & ConditionalActivityData export type ActivitiesByIds = { diff --git a/dapp/src/utils/activities.ts b/dapp/src/utils/activities.ts index c97b1467c..44812948e 100644 --- a/dapp/src/utils/activities.ts +++ b/dapp/src/utils/activities.ts @@ -1,9 +1,41 @@ -import { Activity } from "#/types" +import { Activity, ActivityType } from "#/types" + +const MIN_LIMIT_VALUE_DURATION = BigInt(String(1e7)) // 0.1 BTC +const MAX_LIMIT_VALUE_DURATION = BigInt(String(1e8)) // 1 BTC export const isActivityCompleted = (activity: Activity): boolean => activity.status === "completed" +export const getActivityTimestamp = (activity: Activity): number => + activity?.finalizedAt ?? activity.initializedAt + export const sortActivitiesByTimestamp = (activities: Activity[]): Activity[] => - activities.sort( - (activity1, activity2) => activity2.initializedAt - activity1.initializedAt, + [...activities].sort( + (activity1, activity2) => + getActivityTimestamp(activity2) - getActivityTimestamp(activity1), ) + +export const filterCompletedActivities = (activities: Activity[]): Activity[] => + activities.filter((activity) => isActivityCompleted(activity)) + +export const isWithdrawType = (type: ActivityType) => type === "withdraw" + +export function getEstimatedDuration( + amount: bigint, + type: ActivityType, +): string { + if (isWithdrawType(type)) return "6 hours" + + if (amount < MIN_LIMIT_VALUE_DURATION) return "1 hour" + + if (amount >= MIN_LIMIT_VALUE_DURATION && amount < MAX_LIMIT_VALUE_DURATION) + return "2 hours" + + return "3 hours" +} + +export function convertActivityTypeToLabel(type: ActivityType): string { + if (isWithdrawType(type)) return "Unstaking" + + return "Staking" +}