Skip to content

Commit

Permalink
Merge pull request #4 from Se7en-Seas/boring-queue
Browse files Browse the repository at this point in the history
Boring Queue Integration
  • Loading branch information
philipjames44 authored Oct 25, 2024
2 parents 523c8ab + fd2ade9 commit cfe7f8d
Show file tree
Hide file tree
Showing 13 changed files with 1,412 additions and 111 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,4 @@ dist
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
.vscode
296 changes: 189 additions & 107 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "boring-vault-ui",
"version": "1.6.3",
"version": "1.7.0",
"description": "A reusable package to quickly integrate boring vaults onto a UI.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
1 change: 1 addition & 0 deletions src/abis/v1/BoringQueueABI.tsx

Large diffs are not rendered by default.

271 changes: 271 additions & 0 deletions src/components/v1/BoringQueueButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
// src/components/v1/BoringQueueButton.tsx

import React, { useEffect } from "react";
import {
Button,
Modal,
ModalOverlay,
ModalContent,
ModalBody,
ModalCloseButton,
useDisclosure,
ButtonProps,
ModalProps,
ModalOverlayProps,
ModalContentProps,
ModalBodyProps,
ModalCloseButtonProps,
Text,
HStack,
VStack,
Box,
Image,
Select,
InputGroup,
Input,
InputRightElement,
FormControl,
Flex,
FormHelperText,
FormLabel,
InputProps,
ButtonGroup,
ModalHeader,
ModalFooter,
Avatar,
useToast,
} from "@chakra-ui/react";
import { useBoringVaultV1 } from "../../contexts/v1/BoringVaultContextV1";
import { Token } from "../../types";
import { Contract, formatUnits } from "ethers";
import { erc20Abi } from "viem";
import { useEthersSigner } from "../../hooks/ethers";
import { useAccount } from "wagmi";

interface BoringQueueButtonProps {
buttonText: string;
popupText: string;
title?: string; // Optional title
bottomText?: string; // Optional bottom text (e.g. disclaimer, etc.)
buttonProps?: ButtonProps;
modalProps?: ModalProps;
modalOverlayProps?: ModalOverlayProps;
modalContentProps?: ModalContentProps;
modalBodyProps?: ModalBodyProps;
modalCloseButtonProps?: ModalCloseButtonProps;
inputProps?: any;
}

const BoringQueueButton: React.FC<BoringQueueButtonProps> = ({
buttonText,
buttonProps,
modalProps,
modalOverlayProps,
modalContentProps,
modalBodyProps,
modalCloseButtonProps,
inputProps,
title,
bottomText,
...withdrawButtonProps
}) => {
const { isOpen, onOpen, onClose } = useDisclosure();
const {
withdrawTokens,
ethersProvider,
withdrawStatus,
queueBoringWithdraw,
fetchUserShares,
} = useBoringVaultV1();

const [selectedToken, setSelectedToken] = React.useState<Token>(
withdrawTokens[0]
);
const [balance, setBalance] = React.useState(0.0);
const [withdrawAmount, setWithdrawAmount] = React.useState("");
const [discountPercent, setDiscountPercent] = React.useState("");
const [daysValid, setDaysValid] = React.useState("");
const signer = useEthersSigner();

useEffect(() => {
async function fetchBalance() {
if (!signer || !ethersProvider) return;

try {
const shareBalance = await fetchUserShares(
await signer.getAddress(),
);
setBalance(shareBalance);
} catch (error) {
console.error("Failed to fetch share balance:", error);
setBalance(0); // Optionally reset balance on error
}
}

fetchBalance();
}, [signer, ethersProvider]);

const handleSelectChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
const newTokenAddress = event.target.value;
console.log("New token address:", newTokenAddress);
console.log("Withdraw tokens:", withdrawTokens);
const newSelectedToken = withdrawTokens.find(
(token) => token.address === newTokenAddress
);
setSelectedToken(newSelectedToken || withdrawTokens[0]);
};

// TODO: Allow people to pass in a toast to allow for custom toast branding
const toast = useToast();
useEffect(() => {
if (withdrawStatus.loading) {
toast({
title: "Processing withdraw...",
status: "info",
duration: 5000,
isClosable: true,
});
} else if (withdrawStatus.success) {
toast({
title: "Intent successful",
// Add link to etherscan
description: `Transaction hash: ${withdrawStatus.tx_hash}`,
status: "success",
duration: 5000,
isClosable: true,
});
} else if (withdrawStatus.error) {
toast({
title: "Failed to initiate withdraw",
description: withdrawStatus.error,
status: "error",
duration: 5000,
isClosable: true,
});
}
}, [withdrawStatus, toast]);

return (
<>
<Button onClick={onOpen} isDisabled={!useAccount().isConnected} {...buttonProps}>
{buttonText}
</Button>
<Modal isOpen={isOpen} onClose={onClose} isCentered {...modalProps}>
<ModalOverlay {...modalOverlayProps} />
<ModalContent {...modalContentProps}>
{title && <ModalHeader>{title}</ModalHeader>}
<ModalCloseButton {...modalCloseButtonProps} />
<ModalBody {...modalBodyProps}>
<Flex
alignItems="center"
justifyContent="space-between"
width={"100%"}
>
<VStack spacing="2" width={"100%"} align={"right"}>
<HStack spacing="2">
<Text whiteSpace="nowrap" fontWeight={"bold"}>
Asset Out:{" "}
</Text>
<Select
onChange={handleSelectChange}
value={selectedToken.address}
icon={
<Avatar
size="lg"
src={selectedToken.image}
onChange={handleSelectChange}
/>
}
>
{withdrawTokens.map((token) => (
<>
<Avatar size="l" src={token.image} />
<option key={token.address} value={token.address}>
{token.displayName}
</option>
</>
))}
</Select>
</HStack>
<FormControl>
<HStack spacing="2">
<Text whiteSpace="nowrap" fontWeight={"bold"}>
Shares to Redeem:{" "}
</Text>
{/* TODO: Sterilize input to only allow positive numbers */}
<Input
placeholder="0.00"
onChange={(e) => setWithdrawAmount(e.target.value)}
{...inputProps}
/>
</HStack>
<FormHelperText textAlign="right">
Share Balance: {balance} <Button size="xs">MAX</Button>
</FormHelperText>
</FormControl>
<FormControl>
<VStack spacing="10">
<Text fontWeight={"bold"} height={"20px"} width={"100%"}>
Discount Percent (if share value is 1, a discount of 1%
means you'll accept a share price of 0.99):{" "}
</Text>
{/* TODO: Sterilize input to only allow positive numbers */}
<Input
placeholder="0.00"
onChange={(e) => setDiscountPercent(e.target.value)}
{...inputProps}
/>
</VStack>
</FormControl>
<FormControl>
<HStack spacing="2">
<Text whiteSpace="nowrap" fontWeight={"bold"}>
Days Valid (until order expires if unfulfilled):{" "}
</Text>
{/* TODO: Sterilize input to only allow positive numbers */}
<Input
placeholder="0"
onChange={(e) => setDaysValid(e.target.value)}
{...inputProps}
/>
</HStack>
</FormControl>
</VStack>
</Flex>
<Flex justifyContent="space-between" mt={2}>
{/* Example static value, replace with actual conversion */}
<Button
mt={4}
onClick={() =>
queueBoringWithdraw(
signer!,
withdrawAmount,
selectedToken,
discountPercent,
daysValid
)
}
isDisabled={
!withdrawAmount ||
!discountPercent ||
!daysValid ||
parseFloat(withdrawAmount) > balance
}
{...withdrawButtonProps}
>
Initiate Withdraw
</Button>
</Flex>
</ModalBody>
{bottomText && (
<ModalFooter justifyContent="center">
<Text fontSize="sm">{bottomText}</Text>
</ModalFooter>
)}
</ModalContent>
</Modal>
</>
);
};

export default BoringQueueButton;
32 changes: 32 additions & 0 deletions src/components/v1/BoringQueueCancelButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// src/components/v1/BoringQueueCancelButton.tsx

import React, { useEffect, useState } from "react";
import { Box, Text, VStack, Button } from "@chakra-ui/react";
import { useBoringVaultV1 } from "../../contexts/v1/BoringVaultContextV1";
import { Token } from "../../types";
import { useEthersSigner } from "../../hooks/ethers";

interface BoringQueueCancelButtonProps {
token: Token;
}

const BoringQueueCancelButton: React.FC<BoringQueueCancelButtonProps> = ({
token,
}) => {
const { boringQueueCancel } = useBoringVaultV1();
const signer = useEthersSigner();

return (
<Button
mt={4}
onClick={() => boringQueueCancel(signer!, token)}
isDisabled={!signer}
outline={"1px solid black"}
colorScheme={"blue"}
>
Cancel
</Button>
);
};

export default BoringQueueCancelButton;
78 changes: 78 additions & 0 deletions src/components/v1/BoringQueuePendingStatuses.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React, { useEffect, useState } from "react";
import { Box, HStack, Text, VStack } from "@chakra-ui/react";
import { useBoringVaultV1 } from "../../contexts/v1/BoringVaultContextV1";
import { useEthersSigner } from "../../hooks/ethers";
import { BoringQueueStatus, WithdrawQueueStatus } from "../../types";
import BoringQueueCancelButton from "./BoringQueueCancelButton";

interface PendingBoringQueueStatusesProps {
title?: string; // Optional title
}

// TODO Abstract away style into props above same as DepositButton
const PendingBoringQueueStatuses: React.FC<
PendingBoringQueueStatusesProps
> = ({ title, ...pendingBoringQueueProps }) => {
const { ethersProvider, boringQueueStatuses } = useBoringVaultV1();
const [statuses, setStatuses] = useState<any[]>([]); // State to store fetched statuses
const signer = useEthersSigner();

useEffect(() => {
const fetchStatuses = async () => {
if (!signer) return;

const fetchedStatuses: BoringQueueStatus[] =
await boringQueueStatuses(signer!);
setStatuses(fetchedStatuses);
};

fetchStatuses();
console.log("boringQueueStatuses", boringQueueStatuses);
}, [boringQueueStatuses, signer]);

return (
<Box outline={"5px solid black"} borderRadius={"1em"} padding={"1em"}>
{title && (
<Text fontSize={"md"} fontWeight={"bold"}>
{title}
</Text>
)}
<VStack>
{statuses.map((withdrawStatus: BoringQueueStatus, index) => {
return (
<Box
key={index}
padding={"1em"}
outline={"1px solid black"}
borderRadius={"1em"}
>
<HStack key={index} alignItems={"flex-start"}>
<VStack alignItems={"flex-start"}>
<Text>
<strong>Shares Withdrawing:</strong>{" "}
{withdrawStatus.sharesWithdrawing}
</Text>
<Text>
<strong>Tokens Out:</strong>{" "}
{withdrawStatus.tokenOut.displayName}
</Text>
<Text>
<strong>Seconds To Deadline (unix seconds):</strong>{" "}
{withdrawStatus.secondsToDeadline}
</Text>
</VStack>
<VStack paddingLeft="1em">
<BoringQueueCancelButton
token={withdrawStatus.tokenOut}
/>
</VStack>
</HStack>
</Box>
);
})}
</VStack>
</Box>
);
};

export default PendingBoringQueueStatuses;
Loading

0 comments on commit cfe7f8d

Please sign in to comment.