Files
js/src/contract-ui/tabs/edit-extensions/components/InstalledExtensionsTable.tsx
T
kumaryash90 2f439ba4b3 Modular contracts Edit Extensions UI (#2225)
<!-- start pr-codex -->

## PR-Codex overview
This PR adds support for Modular Core and Modular Extension features, updates contract extension settings UI, and enhances extension management in the contract editor.

### Detailed summary
- Added support for `ModularCore` and `ModularExtension` features
- Updated contract extension settings UI
- Improved extension management in the contract editor

> The following files were skipped due to too many changes: `src/contract-ui/tabs/edit-extensions/components/InstalledExtensionsTable.tsx`, `src/contract-ui/tabs/edit-extensions/components/ExtensionForm.tsx`, `pnpm-lock.yaml`

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2024-06-14 21:37:25 +00:00

347 lines
8.7 KiB
TypeScript

import {
Flex,
Table,
TableContainer,
Tbody,
Td,
Th,
Thead,
Tr,
Tooltip,
Skeleton,
Alert,
AlertIcon,
useToast,
Box,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
useDisclosure,
} from "@chakra-ui/react";
import { FaRegTrashAlt } from "react-icons/fa";
import { Button, Text, Heading } from "tw-components";
import { uninstallExtensionByProxy } from "thirdweb/extensions/modular";
import {
sendTransaction,
waitForReceipt,
type ContractOptions,
} from "thirdweb";
import { AddressCopyButton } from "tw-components/AddressCopyButton";
import { useExtensionContractInfo } from "./extensionContractInfo";
import type { Account } from "thirdweb/wallets";
import { TransactionButton } from "components/buttons/TransactionButton";
import { useMutation } from "@tanstack/react-query";
export const InstalledExtensionsTable = (props: {
contract: ContractOptions;
installedExtensions: {
data?: string[];
isLoading: boolean;
};
refetchExtensions: () => void;
ownerAccount?: Account;
}) => {
const { installedExtensions, ownerAccount } = props;
if (
!installedExtensions.isLoading &&
installedExtensions.data?.length === 0
) {
return (
<>
<Heading as="h2" size="title.md">
Installed Extensions
</Heading>
<Alert
status="error"
fontSize={"small"}
borderRadius="md"
p={3}
bg="alertBg"
>
<AlertIcon />
<Flex direction="column" gap={0.5} ml={1}>
<Text color="heading" size="body.lg" fontWeight={500}>
No extensions installed
</Text>
</Flex>
</Alert>
</>
);
}
return (
<>
<Heading as="h2" size="title.md">
Installed Extensions
</Heading>
<TableContainer
rounded={"lg"}
overflowX={{ base: "auto", md: "initial" }}
>
<Table position="relative">
<Thead>
<TableHeading> Extension Name </TableHeading>
<TableHeading> Description </TableHeading>
<TableHeading> Publisher Address </TableHeading>
<TableHeading> Extension Address </TableHeading>
<TableHeading> Version </TableHeading>
{ownerAccount && <TableHeading> Remove </TableHeading>}
</Thead>
<Tbody>
{installedExtensions.isLoading ? (
<>
<SkeletonRow ownerAccount={ownerAccount} />
<SkeletonRow ownerAccount={ownerAccount} />
<SkeletonRow ownerAccount={ownerAccount} />
</>
) : (
<>
{installedExtensions.data?.map((e, i) => (
<ExtensionRow
key={i}
extensionAddress={e}
contract={props.contract}
onRemoveExtension={props.refetchExtensions}
ownerAccount={ownerAccount}
/>
))}
</>
)}
</Tbody>
</Table>
</TableContainer>
</>
);
};
function SkeletonRow(props: { ownerAccount?: Account }) {
return (
<Tr borderBottomWidth={1} _last={{ borderBottomWidth: 0 }}>
<TableData>
<Skeleton height="24px" rounded="xl" />
</TableData>
<TableData>
<Skeleton height="24px" rounded="xl" />
</TableData>
<TableData>
<Skeleton height="24px" rounded="xl" />
</TableData>
<TableData>
<Skeleton height="24px" rounded="xl" />
</TableData>
{/* Version */}
<TableData>
<Skeleton height="24px" rounded="xl" />
</TableData>
{/* Remove */}
{props.ownerAccount && (
<TableData>
<Skeleton height="24px" rounded="xl" />
</TableData>
)}
</Tr>
);
}
function ExtensionRow(props: {
extensionAddress: string;
contract: ContractOptions;
onRemoveExtension: () => void;
ownerAccount?: Account;
}) {
const { contract, extensionAddress, ownerAccount } = props;
const toast = useToast();
const { isOpen, onOpen, onClose } = useDisclosure();
const contractInfo = useExtensionContractInfo(props.extensionAddress);
const uninstallMutation = useMutation({
mutationFn: async (account: Account) => {
const uninstallTransaction = uninstallExtensionByProxy({
contract,
chain: contract.chain,
client: contract.client,
extensionProxyAddress: extensionAddress,
extensionData: "0x",
});
const txResult = await sendTransaction({
transaction: uninstallTransaction,
account,
});
await waitForReceipt(txResult);
},
onSuccess() {
toast({
variant: "solid",
position: "bottom",
title: "Extension Removed successfully",
status: "success",
duration: 5000,
isClosable: true,
});
props.onRemoveExtension();
},
onError(error) {
toast({
variant: "solid",
position: "bottom",
title: "Failed to remove extension",
status: "error",
duration: 5000,
isClosable: true,
});
console.error("Error during uninstallation:", error);
},
});
const handleRemove = async () => {
if (!ownerAccount) {
return;
}
onClose();
uninstallMutation.mutate(ownerAccount);
};
if (!contractInfo) {
return <SkeletonRow ownerAccount={ownerAccount} />;
}
return (
<Tr borderBottomWidth={1} _last={{ borderBottomWidth: 0 }}>
<TableData>
<Text color="heading">{contractInfo.name}</Text>
</TableData>
<TableData>
<Text>{contractInfo.description || "..."}</Text>
</TableData>
<TableData>
<AddressCopyButton size="xs" address={contractInfo.publisher} />
</TableData>
<TableData>
<AddressCopyButton size="xs" address={extensionAddress} />
</TableData>
{/* Version */}
<TableData>
<Text>{contractInfo.version}</Text>
</TableData>
{/* Remove */}
{ownerAccount && (
<TableData>
<Flex>
<Tooltip
label="Remove"
bg="backgroundHighlight"
border="1px solid"
borderColor="borderColor"
color="accent.500"
rounded="xl"
placement="top"
fontSize="small"
px={3}
py={2}
>
<Button
isLoading={uninstallMutation.isLoading}
onClick={onOpen}
color="red.500"
variant="outline"
_hover={{
bg: "accent.100",
}}
rounded={"xl"}
p={2}
>
<FaRegTrashAlt
style={{
width: "16px",
height: "16px",
}}
/>
</Button>
</Tooltip>
</Flex>
</TableData>
)}
<Modal isOpen={isOpen} onClose={onClose} isCentered>
<ModalOverlay />
<ModalContent>
<form
onSubmit={(e) => {
e.preventDefault();
handleRemove();
}}
>
<ModalHeader fontSize={"larger"} pb={1}>
Uninstall Extension
</ModalHeader>
<ModalCloseButton />
<ModalBody>
Are you sure you want to uninstall{" "}
<Box as="span" fontWeight={500} color="heading">
{contractInfo.name}
</Box>{" "}
?
</ModalBody>
<ModalFooter pt={10} gap={3}>
<Button
type="button"
mr={3}
m={0}
onClick={onClose}
variant="outline"
>
Cancel
</Button>
<TransactionButton
transactionCount={1}
isLoading={uninstallMutation.isLoading}
type="submit"
colorScheme="red"
alignSelf="flex-end"
mr={3}
m={0}
>
Uninstall
</TransactionButton>
</ModalFooter>
</form>
</ModalContent>
</Modal>
</Tr>
);
}
function TableData(props: { children: React.ReactNode }) {
return (
<Td borderBottomWidth="inherit" borderBottomColor="borderColor" py={5}>
{props.children}
</Td>
);
}
function TableHeading(props: { children: React.ReactNode }) {
return (
<Th border="none" color="faded">
{props.children}
</Th>
);
}