Skip to content

Commit

Permalink
ui preview modal (#392)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremy-babylonlabs authored Nov 27, 2024
1 parent aecb432 commit 629ce21
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 108 deletions.
236 changes: 138 additions & 98 deletions src/app/components/Modals/PreviewModal.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
import { IoMdClose } from "react-icons/io";
import { twJoin } from "tailwind-merge";
import {
Avatar,
Button,
Dialog,
DialogBody,
DialogFooter,
DialogHeader,
Heading,
Loader,
MobileDialog,
Text,
} from "@babylonlabs-io/bbn-core-ui";

import { useParams } from "@/app/hooks/api/useParams";
import { useIsMobileView } from "@/app/hooks/useBreakpoint";
import { getNetworkConfig } from "@/config/network.config";
import { blocksToDisplayTime } from "@/utils/blocksToDisplayTime";
import { satoshiToBtc } from "@/utils/btcConversions";
import { maxDecimals } from "@/utils/maxDecimals";

import { LoadingView } from "../Loading/Loading";

import { GeneralModal } from "./GeneralModal";

interface PreviewModalProps {
open: boolean;
isOpen: boolean;
onClose: () => void;
onSign: () => void;
finalityProvider: string | undefined;
finalityProviderAvatar: string | undefined;
stakingAmountSat: number;
stakingTimeBlocks: number;
stakingFeeSat: number;
Expand All @@ -24,114 +32,146 @@ interface PreviewModalProps {
awaitingWalletResponse: boolean;
}

export const PreviewModal: React.FC<PreviewModalProps> = ({
open,
export const PreviewModal = ({
isOpen,
onClose,
finalityProvider,
finalityProviderAvatar,
stakingAmountSat,
stakingTimeBlocks,
onSign,
stakingFeeSat,
feeRate,
unbondingFeeSat,
awaitingWalletResponse,
}) => {
const cardStyles =
"card border bg-base-300 p-4 text-sm dark:border-0 dark:bg-base-200";

}: PreviewModalProps) => {
const isMobileView = useIsMobileView();
const { coinName } = getNetworkConfig();

const { data: params } = useParams();
const confirmationDepth =
params?.btcEpochCheckParams?.latestParam?.btcConfirmationDepth || 10;

const DialogComponent = isMobileView ? MobileDialog : Dialog;

const FinalityProviderValue = isMobileView ? (
<span className="flex gap-2">
{finalityProviderAvatar && (
<Avatar
size="small"
url={finalityProviderAvatar}
variant="circular"
alt={finalityProvider || "-"}
/>
)}
<Text variant="body1">{finalityProvider || "-"}</Text>
</span>
) : (
<Text variant="body1">{finalityProvider || "-"}</Text>
);

const previewFields = [
{
key: "Finality Provider",
value: FinalityProviderValue,
},
{
key: "Stake Amount",
value: (
<Text variant="body1">
{maxDecimals(satoshiToBtc(stakingAmountSat), 8)} {coinName}
</Text>
),
},
{
key: "Fee rate",
value: <Text variant="body1">{feeRate} sat/vB</Text>,
},
{
key: "Transaction fee",
value: (
<Text variant="body1">
{maxDecimals(satoshiToBtc(stakingFeeSat), 8)} {coinName}
</Text>
),
},
{
key: "Term",
value: (
<Text variant="body1">{blocksToDisplayTime(stakingTimeBlocks)}</Text>
),
},
{
key: "On Demand Unbonding",
value: <Text variant="body1">Enabled (7 days unbonding time)</Text>,
},
{
key: "Unbonding fee",
value: (
<Text variant="body1">
{maxDecimals(satoshiToBtc(unbondingFeeSat), 8)} {coinName}
</Text>
),
},
];

return (
<GeneralModal
open={open}
onClose={onClose}
closeOnOverlayClick={!awaitingWalletResponse}
closeOnEsc={false}
>
<div className="mb-4 flex items-center justify-between">
<h3 className="font-bold">Preview</h3>
{!awaitingWalletResponse && (
<button
className="btn btn-circle btn-ghost btn-sm"
onClick={() => onClose()}
>
<IoMdClose size={24} />
</button>
)}
</div>
<div className="flex flex-col gap-4 text-sm">
<div className="flex flex-col gap-4 md:flex-row">
<div className={twJoin(cardStyles, "flex-1")}>
<p className="text-xs dark:text-neutral-content">
Finality Provider
</p>
<p>{finalityProvider || "-"}</p>
</div>
<div className={twJoin(cardStyles, "flex-1")}>
<p className="text-xs dark:text-neutral-content">Stake Amount</p>
<p>{`${maxDecimals(satoshiToBtc(stakingAmountSat), 8)} ${coinName}`}</p>
</div>
</div>
<div className="flex flex-col gap-4 md:flex-row">
<div className={twJoin(cardStyles, "flex-1")}>
<p className="text-xs dark:text-neutral-content">Fee rate</p>
<p>{feeRate} sat/vB</p>
</div>
<div className={twJoin(cardStyles, "flex-1")}>
<p className="text-xs dark:text-neutral-content">Transaction fee</p>
<p>{`${maxDecimals(satoshiToBtc(stakingFeeSat), 8)} ${coinName}`}</p>
</div>
<DialogComponent open={isOpen} onClose={onClose}>
<DialogHeader
title="Preview"
onClose={onClose}
className="text-primary-dark"
/>
<DialogBody className="flex flex-col pb-8 pt-4 text-primary-dark gap-4">
<div className="flex flex-col">
{previewFields.map((field, index) => (
<>
<div key={field.key} className="flex justify-between">
<Text variant="body1">{field.key}</Text>
<div className="text-right">{field.value}</div>
</div>
{index < previewFields.length - 1 && (
<div className="divider mx-0 my-2" />
)}
</>
))}
</div>
<div className="flex flex-col gap-4 md:flex-row">
<div className={twJoin(cardStyles, "basis-1/5")}>
<p className="text-xs dark:text-neutral-content">Term</p>
<p>{blocksToDisplayTime(stakingTimeBlocks)}</p>
</div>
<div className={twJoin(cardStyles, "basis-2/5")}>
<p className="text-xs dark:text-neutral-content">Slashing ratio</p>
<p>12.3%</p>
</div>
<div className={twJoin(cardStyles, "basis-2/5")}>
<p className="text-xs dark:text-neutral-content">Unbonding fee</p>
<p>{`${maxDecimals(satoshiToBtc(unbondingFeeSat), 8)} ${coinName}`}</p>
</div>
<div className="flex flex-col gap-2">
<Heading variant="h6">Attention!</Heading>
<Text variant="body2">
1. No third party possesses your staked {coinName}. You are the only
one who can unbond and withdraw your stake.
</Text>
<Text variant="body2">
2. Your stake will initially have the status of &quot;Pending&quot;
until it receives {confirmationDepth} Bitcoin confirmations.
&quot;Pending&quot; stake is only accessible through the device it
was created.
</Text>
</div>
<h4 className="text-center text-base">Attention!</h4>
<p className="dark:text-neutral-content">
1. No third party possesses your staked {coinName}. You are the only
one who can unbond and withdraw your stake.
</p>
<p className="dark:text-neutral-content">
2. Your stake will initially have the status of &quot;Pending&quot;
until it receives {confirmationDepth} Bitcoin confirmations.
&quot;Pending&quot; stake is only accessible through the device it was
created.
</p>
{awaitingWalletResponse ? (
<LoadingView
text="Awaiting wallet signature and broadcast"
noBorder
/>
) : (
<div className="flex gap-4">
<button
className="btn btn-outline flex-1"
onClick={() => {
onClose();
}}
>
Cancel
</button>
<button className="btn-primary btn flex-1" onClick={onSign}>
Proceed To Signing
</button>
</div>
)}
</div>
</GeneralModal>
</DialogBody>
<DialogFooter className="flex gap-4">
<Button
variant="outlined"
color="primary"
onClick={onClose}
className="flex-1 text-xs sm:text-base"
>
Cancel
</Button>
<Button
variant="contained"
onClick={onSign}
className="flex-1 text-xs sm:text-base"
disabled={awaitingWalletResponse}
>
{awaitingWalletResponse ? (
<Loader size={16} className="text-white" />
) : (
"Proceed to Signing"
)}
</Button>
</DialogFooter>
</DialogComponent>
);
};
7 changes: 4 additions & 3 deletions src/app/components/Modals/UnbondModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
DialogBody,
DialogFooter,
DialogHeader,
Loader,
MobileDialog,
Text,
} from "@babylonlabs-io/bbn-core-ui";
Expand Down Expand Up @@ -53,7 +54,7 @@ export const UnbondModal = ({
onClick={onClose}
className="flex-1"
>
Done
Cancel
</Button>
<Button
variant="contained"
Expand All @@ -62,9 +63,9 @@ export const UnbondModal = ({
disabled={awaitingWalletResponse}
>
{awaitingWalletResponse ? (
<span className="loading loading-spinner loading-xs text-white" />
<Loader size={16} className="text-white" />
) : (
"Cancel"
"Proceed"
)}
</Button>
</DialogFooter>
Expand Down
8 changes: 4 additions & 4 deletions src/app/components/Modals/WithdrawModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import {
DialogBody,
DialogFooter,
DialogHeader,
Loader,
MobileDialog,
Text,
} from "@babylonlabs-io/bbn-core-ui";
import { useMediaQuery } from "usehooks-ts";

import { useIsMobileView } from "@/app/hooks/useBreakpoint";
import { getNetworkConfig } from "@/config/network.config";
import { screenBreakPoints } from "@/config/screen-breakpoints";

export const MODE_TRANSITION = "transition";
export const MODE_WITHDRAW = "withdraw";
Expand Down Expand Up @@ -39,7 +39,7 @@ export const WithdrawModal = ({
</>
);

const isMobileView = useMediaQuery(`(max-width: ${screenBreakPoints.md})`);
const isMobileView = useIsMobileView();

const DialogComponent = isMobileView ? MobileDialog : Dialog;

Expand All @@ -64,7 +64,7 @@ export const WithdrawModal = ({
</Button>
<Button variant="contained" onClick={onProceed} className="flex-1">
{awaitingWalletResponse ? (
<span className="loading loading-spinner loading-xs text-white" />
<Loader size={16} className="text-white" />
) : (
"Proceed"
)}
Expand Down
3 changes: 2 additions & 1 deletion src/app/components/Staking/Staking.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -493,10 +493,11 @@ export const Staking = () => {
</span>
{previewReady && (
<PreviewModal
open={previewModalOpen}
isOpen={previewModalOpen}
onClose={handlePreviewModalClose}
onSign={handleDelegationEoiCreation}
finalityProvider={finalityProvider?.description.moniker}
finalityProviderAvatar={finalityProvider?.description.identity}
stakingAmountSat={stakingAmountSat}
stakingTimeBlocks={stakingTimeBlocksWithFixed}
stakingFeeSat={stakingFeeSat}
Expand Down
4 changes: 2 additions & 2 deletions src/app/hooks/useBreakpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ type BreakpointKey = keyof typeof screenBreakPoints;
/**
* Custom hook to check if the current viewport matches a specific breakpoint
* @param breakpoint - The breakpoint to check against ("sm" | "md" | "lg" | "xl" | "2xl")
* @returns boolean indicating if the viewport width is greater than or equal to the specified breakpoint
* @returns boolean indicating if the viewport width is less than or equal to the specified breakpoint
*/
export const useBreakpoint = (breakpoint: BreakpointKey): boolean => {
const matches = useMediaQuery(
`(min-width: ${screenBreakPoints[breakpoint]})`,
`(max-width: ${screenBreakPoints[breakpoint]})`,
);
return matches;
};
Expand Down

0 comments on commit 629ce21

Please sign in to comment.