Skip to content

Commit cf36a33

Browse files
committed
[Dashboard] Update erc1155 airdrop form (#5127)
- [x] Remove tw-component's Drawer - [x] Consolidate some shared code - [x] Remove an useEffect <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on refactoring the airdrop functionality in a dashboard application, improving the handling of token and NFT airdrops by consolidating interfaces and components. ### Detailed summary - Renamed `ERC20AirdropAddressInput` to `AirdropAddressInput`. - Updated `AirdropUploadERC20` to `AirdropUpload`. - Adjusted state management for airdrop data. - Simplified CSV data processing. - Enhanced error handling during transaction processes. - Replaced modal with `Sheet` component for airdrop interface. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 2fdb69d commit cf36a33

File tree

5 files changed

+164
-646
lines changed

5 files changed

+164
-646
lines changed

apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/airdrop-tab.tsx

Lines changed: 101 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,28 @@
11
"use client";
22

3-
import { Flex, useDisclosure } from "@chakra-ui/react";
3+
import {
4+
Sheet,
5+
SheetContent,
6+
SheetHeader,
7+
SheetTitle,
8+
SheetTrigger,
9+
} from "@/components/ui/sheet";
10+
import { Flex } from "@chakra-ui/react";
411
import { TransactionButton } from "components/buttons/TransactionButton";
512
import { useTrack } from "hooks/analytics/useTrack";
6-
import { useTxNotifications } from "hooks/useTxNotifications";
713
import { UploadIcon } from "lucide-react";
14+
import { useState } from "react";
815
import { useForm } from "react-hook-form";
16+
import { toast } from "sonner";
917
import type { ThirdwebContract } from "thirdweb";
1018
import { multicall } from "thirdweb/extensions/common";
1119
import { balanceOf, encodeSafeTransferFrom } from "thirdweb/extensions/erc1155";
1220
import { useActiveAccount, useSendAndConfirmTransaction } from "thirdweb/react";
1321
import { Button, Text } from "tw-components";
14-
import { type AirdropAddressInput, AirdropUpload } from "./airdrop-upload";
22+
import {
23+
type AirdropAddressInput,
24+
AirdropUpload,
25+
} from "../../../tokens/components/airdrop-upload";
1526

1627
interface AirdropTabProps {
1728
contract: ThirdwebContract;
@@ -22,109 +33,113 @@ interface AirdropTabProps {
2233
* This component must only take in ERC1155 contracts
2334
*/
2435
const AirdropTab: React.FC<AirdropTabProps> = ({ contract, tokenId }) => {
25-
const account = useActiveAccount();
26-
2736
const address = useActiveAccount()?.address;
2837
const { handleSubmit, setValue, watch, reset, formState } = useForm<{
2938
addresses: AirdropAddressInput[];
3039
}>({
3140
defaultValues: { addresses: [] },
3241
});
3342
const trackEvent = useTrack();
34-
35-
const { isOpen, onOpen, onClose } = useDisclosure();
36-
37-
const { mutate, isPending } = useSendAndConfirmTransaction();
38-
39-
const { onSuccess, onError } = useTxNotifications(
40-
"Airdrop successful",
41-
"Error transferring",
42-
contract,
43-
);
44-
43+
const sendAndConfirmTx = useSendAndConfirmTransaction();
4544
const addresses = watch("addresses");
45+
const [open, setOpen] = useState(false);
4646

4747
return (
4848
<div className="flex w-full flex-col gap-2">
4949
<form
5050
onSubmit={handleSubmit(async (_data) => {
51-
trackEvent({
52-
category: "nft",
53-
action: "airdrop",
54-
label: "attempt",
55-
contract_address: contract.address,
56-
token_id: tokenId,
57-
});
58-
const totalOwned = await balanceOf({
59-
contract,
60-
tokenId: BigInt(tokenId),
61-
owner: account?.address ?? "",
62-
});
63-
// todo: make a batch-transfer extension for erc1155?
64-
const totalToAirdrop = _data.addresses.reduce((prev, curr) => {
65-
return BigInt(prev) + BigInt(curr?.quantity || 1);
66-
}, 0n);
67-
if (totalOwned < totalToAirdrop) {
68-
return onError(
69-
new Error(
51+
try {
52+
trackEvent({
53+
category: "nft",
54+
action: "airdrop",
55+
label: "attempt",
56+
contract_address: contract.address,
57+
token_id: tokenId,
58+
});
59+
const totalOwned = await balanceOf({
60+
contract,
61+
tokenId: BigInt(tokenId),
62+
owner: address ?? "",
63+
});
64+
// todo: make a batch-transfer extension for erc1155?
65+
const totalToAirdrop = _data.addresses.reduce((prev, curr) => {
66+
return BigInt(prev) + BigInt(curr?.quantity || 1);
67+
}, 0n);
68+
if (totalOwned < totalToAirdrop) {
69+
return toast.error(
7070
`The caller owns ${totalOwned.toString()} NFTs, but wants to airdrop ${totalToAirdrop.toString()} NFTs.`,
71-
),
71+
);
72+
}
73+
const data = _data.addresses.map(({ address: to, quantity }) =>
74+
encodeSafeTransferFrom({
75+
from: address ?? "",
76+
to,
77+
value: BigInt(quantity),
78+
data: "0x",
79+
tokenId: BigInt(tokenId),
80+
}),
7281
);
82+
const transaction = multicall({ contract, data });
83+
const promise = sendAndConfirmTx.mutateAsync(transaction, {
84+
onSuccess: () => {
85+
trackEvent({
86+
category: "nft",
87+
action: "airdrop",
88+
label: "success",
89+
contract_address: contract.address,
90+
token_id: tokenId,
91+
});
92+
reset();
93+
},
94+
onError: (error) => {
95+
trackEvent({
96+
category: "nft",
97+
action: "airdrop",
98+
label: "success",
99+
contract_address: contract.address,
100+
token_id: tokenId,
101+
error,
102+
});
103+
},
104+
});
105+
toast.promise(promise, {
106+
loading: "Airdropping NFTs",
107+
success: "Airdropped successfully",
108+
error: "Failed to airdrop",
109+
});
110+
} catch (err) {
111+
console.error(err);
112+
toast.error("Failed to airdrop NFTs");
73113
}
74-
const data = _data.addresses.map(({ address: to, quantity }) =>
75-
encodeSafeTransferFrom({
76-
from: account?.address ?? "",
77-
to,
78-
value: BigInt(quantity),
79-
data: "0x",
80-
tokenId: BigInt(tokenId),
81-
}),
82-
);
83-
const transaction = multicall({ contract, data });
84-
mutate(transaction, {
85-
onSuccess: () => {
86-
trackEvent({
87-
category: "nft",
88-
action: "airdrop",
89-
label: "success",
90-
contract_address: contract.address,
91-
token_id: tokenId,
92-
});
93-
onSuccess();
94-
reset();
95-
},
96-
onError: (error) => {
97-
trackEvent({
98-
category: "nft",
99-
action: "airdrop",
100-
label: "success",
101-
contract_address: contract.address,
102-
token_id: tokenId,
103-
error,
104-
});
105-
onError(error);
106-
},
107-
});
108114
})}
109115
>
110116
<div className="flex flex-col gap-2">
111117
<div className="mb-3 flex w-full flex-col gap-6 md:flex-row">
112-
<AirdropUpload
113-
isOpen={isOpen}
114-
onClose={onClose}
115-
setAirdrop={(value) =>
116-
setValue("addresses", value, { shouldDirty: true })
117-
}
118-
/>
119118
<Flex direction={{ base: "column", md: "row" }} gap={4}>
120-
<Button
121-
colorScheme="primary"
122-
borderRadius="md"
123-
onClick={onOpen}
124-
rightIcon={<UploadIcon className="size-5" />}
125-
>
126-
Upload addresses
127-
</Button>
119+
<Sheet open={open} onOpenChange={setOpen}>
120+
<SheetTrigger asChild>
121+
<Button
122+
colorScheme="primary"
123+
borderRadius="md"
124+
rightIcon={<UploadIcon className="size-5" />}
125+
>
126+
Upload addresses
127+
</Button>
128+
</SheetTrigger>
129+
<SheetContent className="w-full overflow-y-auto sm:min-w-[540px] lg:min-w-[700px]">
130+
<SheetHeader>
131+
<SheetTitle className="mb-5 text-left">
132+
Aidrop NFTs
133+
</SheetTitle>
134+
</SheetHeader>
135+
<AirdropUpload
136+
onClose={() => setOpen(false)}
137+
setAirdrop={(value) =>
138+
setValue("addresses", value, { shouldDirty: true })
139+
}
140+
/>
141+
</SheetContent>
142+
</Sheet>
128143

129144
<Flex
130145
gap={2}
@@ -149,7 +164,7 @@ const AirdropTab: React.FC<AirdropTabProps> = ({ contract, tokenId }) => {
149164
<TransactionButton
150165
txChainID={contract.chain.id}
151166
transactionCount={1}
152-
isLoading={isPending}
167+
isLoading={sendAndConfirmTx.isPending}
153168
type="submit"
154169
colorScheme="primary"
155170
disabled={!!address && addresses.length === 0}

0 commit comments

Comments
 (0)