diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/modules/components/Royalty.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/modules/components/Royalty.tsx new file mode 100644 index 00000000000..853e3642879 --- /dev/null +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/modules/components/Royalty.tsx @@ -0,0 +1,250 @@ +"use client"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Skeleton } from "@/components/ui/skeleton"; +import { useMutation } from "@tanstack/react-query"; +import { useCallback } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { sendAndConfirmTransaction } from "thirdweb"; +import { RoyaltyERC721 } from "thirdweb/modules"; +import { useReadContract } from "thirdweb/react"; +import { ModuleCardUI, type ModuleCardUIProps } from "./module-card"; +import type { ModuleInstanceProps } from "./module-instance"; + +export type RoyaltyInfoFormValues = { + tokenId: number; + recipient: string; + bps: number; +}; + +export type RoyaltyModuleFormValues = Partial & { + transferValidator?: string; +}; + +function RoyaltyModule(props: ModuleInstanceProps) { + const { contract, ownerAccount } = props; + + const defaultRoyaltyInfoQuery = useReadContract( + RoyaltyERC721.getDefaultRoyaltyInfo, + { + contract: contract, + }, + ); + const transferValidatorQuery = useReadContract( + RoyaltyERC721.getTransferValidator, + { + contract: contract, + }, + ); + + const setRoyaltyInfoForToken = useCallback( + async (values: RoyaltyInfoFormValues) => { + if (!ownerAccount) { + throw new Error("Not an owner account"); + } + + const setRoyaltyForTokenTx = RoyaltyERC721.setRoyaltyInfoForToken({ + contract: contract, + recipient: values.recipient, + bps: values.bps, + tokenId: BigInt(values.tokenId), + }); + + await sendAndConfirmTransaction({ + account: ownerAccount, + transaction: setRoyaltyForTokenTx, + }); + }, + [contract, ownerAccount], + ); + + const update = useCallback( + async (values: RoyaltyModuleFormValues) => { + if (!ownerAccount) { + throw new Error("Not an owner account"); + } + const [defaultRoyaltyRecipient, defaultRoyaltyBps] = + defaultRoyaltyInfoQuery.data || []; + + if ( + values.recipient && + values.bps && + (values.recipient !== defaultRoyaltyRecipient || + values.bps !== defaultRoyaltyBps) + ) { + const setSaleConfigTx = RoyaltyERC721.setDefaultRoyaltyInfo({ + contract: contract, + royaltyRecipient: values.recipient, + royaltyBps: values.bps, + }); + + await sendAndConfirmTransaction({ + account: ownerAccount, + transaction: setSaleConfigTx, + }); + } + + if ( + values.transferValidator && + values.transferValidator !== transferValidatorQuery.data + ) { + const setTransferValidatorTx = RoyaltyERC721.setTransferValidator({ + contract: contract, + validator: values.transferValidator, + }); + + await sendAndConfirmTransaction({ + account: ownerAccount, + transaction: setTransferValidatorTx, + }); + } + }, + [ + contract, + ownerAccount, + transferValidatorQuery.data, + defaultRoyaltyInfoQuery.data, + ], + ); + + return ( + + ); +} + +export function RoyaltyModuleUI( + props: Omit & { + isPending: boolean; + isOwnerAccount: boolean; + transferValidator: string | undefined; + defaultRoyaltyInfo: [string, number] | undefined; + update: (values: RoyaltyModuleFormValues) => Promise; + setRoyaltyInfoForToken: (values: RoyaltyInfoFormValues) => Promise; + }, +) { + const [defaultRoyaltyRecipient, defaultRoyaltyBps] = + props.defaultRoyaltyInfo || []; + const form = useForm({ + values: { + transferValidator: props.transferValidator, + recipient: defaultRoyaltyRecipient || "", + bps: defaultRoyaltyBps || 0, + }, + reValidateMode: "onChange", + }); + + const setRoyaltyInfoForTokenMutation = useMutation({ + mutationFn: props.setRoyaltyInfoForToken, + }); + + const updateMutation = useMutation({ + mutationFn: props.update, + }); + + const _setRoyaltyInfoForToken = async () => { + const promise = setRoyaltyInfoForTokenMutation.mutateAsync( + form.getValues(), + ); + toast.promise(promise, { + success: "Successfully set royalty info for token", + error: "Failed to set royalty info for token", + }); + }; + + const onSubmit = async () => { + const promise = updateMutation.mutateAsync(form.getValues()); + toast.promise(promise, { + success: "Successfully updated royalty info or transfer validator", + error: "Failed to update royalty info or transfer validator", + }); + }; + + return ( +
+ + + {props.isPending && } + + {!props.isPending && ( +
+
+ ( + + Default Royalty Recipient + + + + + )} + /> + + ( + + Default Royalty BPS + + + + + )} + /> +
+ + ( + + Primary Sale Recipient + + + + + )} + /> +
+ )} +
+
+ + ); +} + +export default RoyaltyModule;