Skip to content

Commit

Permalink
Migrate Deployed contracts page to shadcn/tailwind (#4426)
Browse files Browse the repository at this point in the history
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
The focus of this PR is to update UI components, add a new import contract modal, and improve the network select dropdown.

### Detailed summary
- Updated table UI styles
- Added `ShowMoreButton` component with `Button` and `ChevronDownIcon`
- Improved `NetworkSelectDropdown` with `Select` components
- Added `ImportModal` with form validation and submission logic

> The following files were skipped due to too many changes: `apps/dashboard/src/components/contract-components/import-contract/modal.tsx`, `apps/dashboard/src/components/contract-components/tables/deployed-contracts.tsx`

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

<!-- end pr-codex -->
  • Loading branch information
MananTank committed Sep 5, 2024
1 parent 9b083ff commit dbebfb4
Show file tree
Hide file tree
Showing 6 changed files with 282 additions and 378 deletions.
2 changes: 1 addition & 1 deletion apps/dashboard/src/@/components/ui/popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const PopoverContent = React.forwardRef<
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
"z-50 w-72 rounded-md border border-border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className,
)}
{...props}
Expand Down
2 changes: 1 addition & 1 deletion apps/dashboard/src/@/components/ui/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const TableFooter = React.forwardRef<
<tfoot
ref={ref}
className={cn(
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
"border-t border-border bg-muted/50 font-medium [&>tr]:last:border-b-0",
className,
)}
{...props}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,37 @@
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
useAddContractMutation,
useAllContractList,
} from "@3rdweb-sdk/react/hooks/useRegistry";
import { Flex, FormControl } from "@chakra-ui/react";
import { zodResolver } from "@hookform/resolvers/zod";
import { NetworkSelectorButton } from "components/selects/NetworkSelectorButton";
import { SolidityInput } from "contract-ui/components/solidity-inputs";
import { useChainSlug } from "hooks/chains/chainSlug";
import { PlusIcon } from "lucide-react";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { FiFilePlus } from "react-icons/fi";
import { getAddress } from "thirdweb";
import { toast } from "sonner";
import { getAddress, isAddress } from "thirdweb";
import { useActiveAccount, useActiveWalletChain } from "thirdweb/react";
import { Button, FormErrorMessage } from "tw-components";

import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { z } from "zod";

type ImportModalProps = {
isOpen: boolean;
Expand All @@ -37,9 +48,14 @@ export const ImportModal: React.FC<ImportModalProps> = (props) => {
}
}}
>
<DialogContent dialogOverlayClassName="z-[9000]" className="z-[9001]">
<DialogContent
dialogOverlayClassName="z-[9000] rounded-lg"
className="z-[9001]"
>
<DialogHeader>
<DialogTitle>Import Contract</DialogTitle>
<DialogTitle className="text-2xl font-semibold tracking-tight">
Import Contract
</DialogTitle>
<DialogDescription>
Import an already deployed contract into thirdweb by entering a
contract address below.
Expand All @@ -52,133 +68,157 @@ export const ImportModal: React.FC<ImportModalProps> = (props) => {
);
};

const importFormSchema = z.object({
contractAddress: z.string().refine(
(v) => {
try {
return isAddress(v);
} catch {
return false;
}
},
{
message: "Invalid contract address",
},
),
});

function ImportForm() {
const router = useRouter();
const chainId = useActiveWalletChain()?.id;
const chainSlug = useChainSlug(chainId || 1);
const [isRedirecting, setIsRedirecting] = useState(false);

const form = useForm({
defaultValues: {
contractAddress: "",
},
resolver: zodResolver(importFormSchema),
});
const addToDashboard = useAddContractMutation();
const address = useActiveAccount()?.address;
const registry = useAllContractList(address);

return (
<form
onSubmit={form.handleSubmit(async (data) => {
if (!chainId) {
throw new Error("No chain ID");
}
let contractAddress: string;

try {
contractAddress = getAddress(data.contractAddress);
} catch {
form.setError("contractAddress", {
message: "Invalid contract address",
});
return;
}
const isLoading =
form.formState.isSubmitting ||
addToDashboard.isLoading ||
addToDashboard.isSuccess ||
isRedirecting;

try {
const res = await fetch(
`https://contract.thirdweb.com/metadata/${chainId}/${contractAddress}`,
);
const json = await res.json();

if (json.error) {
throw new Error(json.message);
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(async (data) => {
if (!chainId) {
throw new Error("No chain ID");
}
let contractAddress: string;

const hasUnknownContractName =
!!json.settings?.compilationTarget?.UnknownContract;

const hasPartialAbi = json.metadata?.isPartialAbi;

if (hasUnknownContractName || hasPartialAbi) {
try {
contractAddress = getAddress(data.contractAddress);
} catch {
form.setError("contractAddress", {
message:
"This contract cannot be imported since it's not verified on any block explorers.",
message: "Invalid contract address",
});
return;
}

const isInRegistry =
registry.isFetched &&
registry.data?.find(
(c) =>
contractAddress &&
// compare address...
c.address.toLowerCase() === contractAddress.toLowerCase() &&
// ... and chainId
c.chainId === chainId,
) &&
registry.isSuccess;

if (isInRegistry) {
router.push(`/${chainSlug}/${contractAddress}`);
setIsRedirecting(true);
return;
}

addToDashboard.mutate(
{
contractAddress,
chainId,
},
{
onSuccess: () => {
router.push(`/${chainSlug}/${contractAddress}`);
try {
const res = await fetch(
`https://contract.thirdweb.com/metadata/${chainId}/${contractAddress}`,
);
const json = await res.json();

if (json.error) {
throw new Error(json.message);
}

const hasUnknownContractName =
!!json.settings?.compilationTarget?.UnknownContract;

const hasPartialAbi = json.metadata?.isPartialAbi;

if (hasUnknownContractName || hasPartialAbi) {
form.setError("contractAddress", {
message:
"This contract cannot be imported since it's not verified on any block explorers.",
});
return;
}

const isInRegistry =
registry.isFetched &&
registry.data?.find(
(c) =>
contractAddress &&
// compare address...
c.address.toLowerCase() === contractAddress.toLowerCase() &&
// ... and chainId
c.chainId === chainId,
) &&
registry.isSuccess;

if (isInRegistry) {
router.push(`/${chainSlug}/${contractAddress}`);
setIsRedirecting(true);
return;
}

addToDashboard.mutate(
{
contractAddress,
chainId,
},
onError: (err) => {
console.error(err);
{
onSuccess: () => {
router.push(`/${chainSlug}/${contractAddress}`);
},
onError: (err) => {
console.error(err);
},
},
},
);
} catch (err) {
console.error(err);
}
})}
>
<Flex gap={6} direction="column">
<Flex gap={3} direction="column">
<FormControl isInvalid={!!form.formState.errors.contractAddress}>
<SolidityInput
solidityType="address"
formContext={form}
placeholder="Contract address"
{...form.register("contractAddress")}
/>
<FormErrorMessage>
{form.formState.errors.contractAddress?.message}
</FormErrorMessage>
</FormControl>

<NetworkSelectorButton />
</Flex>
<Button
ml="auto"
leftIcon={<FiFilePlus />}
type="submit"
variant="inverted"
isLoading={
form.formState.isSubmitting ||
addToDashboard.isLoading ||
addToDashboard.isSuccess ||
isRedirecting
);
} catch (err) {
toast.error("Failed to import contract");
console.error(err);
}
loadingText={
addToDashboard.isSuccess || isRedirecting
? "Redirecting"
: "Importing contract"
}
>
Import
</Button>
</Flex>
</form>
})}
>
<FormField
control={form.control}
name="contractAddress"
render={({ field }) => (
<FormItem>
<FormLabel>Contract Address</FormLabel>
<FormControl>
<Input placeholder="0x..." {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<div className="h-3" />
<div>
<Label className="mb-3 inline-block">Network</Label>
<NetworkSelectorButton />
</div>

<div className="h-8" />

<div className="flex justify-end">
<Button type="submit" className="gap-2">
{isLoading ? (
<Spinner className="size-4" />
) : (
<PlusIcon className="size-4" />
)}

{isLoading
? addToDashboard.isSuccess || isRedirecting
? "Redirecting"
: "Importing contract"
: "Import Contract"}
</Button>
</div>
</form>
</Form>
);
}
Loading

0 comments on commit dbebfb4

Please sign in to comment.