import React, { useCallback, useEffect, useState } from "react"; import { Plus, Trash, X } from "phosphor-react"; import { Button, Box, Text } from "."; import { Stack, Flex, Select } from "."; import { Dialog, DialogContent, DialogTitle, DialogDescription, DialogClose, DialogTrigger, } from "./Dialog"; import { Input, Label } from "./Input"; import { Controller, SubmitHandler, useFieldArray, useForm, } from "react-hook-form"; import { deployHook } from "../state/actions"; import { useSnapshot } from "valtio"; import state, { IFile, SelectOption } from "../state"; import toast from "react-hot-toast"; import { prepareDeployHookTx, sha256 } from "../state/actions/deployHook"; import estimateFee from "../utils/estimateFee"; import { getParameters, getInvokeOptions, transactionOptions, SetHookData, } from "../utils/setHook"; import { capitalize } from "../utils/helpers"; export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo( ({ accountAddress }) => { const snap = useSnapshot(state); const [estimateLoading, setEstimateLoading] = useState(false); const [isSetHookDialogOpen, setIsSetHookDialogOpen] = useState(false); const compiledFiles = snap.files.filter(file => file.compiledContent); const activeFile = compiledFiles[snap.activeWat] as IFile | undefined; const accountOptions: SelectOption[] = snap.accounts.map(acc => ({ label: acc.name, value: acc.address, })); const [selectedAccount, setSelectedAccount] = useState( accountOptions.find(acc => acc.value === accountAddress) ); const account = snap.accounts.find( acc => acc.address === selectedAccount?.value ); const getHookNamespace = useCallback( () => (activeFile && snap.deployValues[activeFile.name]?.HookNamespace) || activeFile?.name.split(".")[0] || "", [activeFile, snap.deployValues] ); const getDefaultValues = useCallback((): Partial => { const content = activeFile?.compiledValueSnapshot; return ( (activeFile && snap.deployValues[activeFile.name]) || { HookNamespace: getHookNamespace(), Invoke: getInvokeOptions(content), HookParameters: getParameters(content), } ); }, [activeFile, getHookNamespace, snap.deployValues]); const { register, handleSubmit, control, watch, setValue, getValues, reset, formState: { errors }, } = useForm({ defaultValues: getDefaultValues(), }); const { fields, append, remove } = useFieldArray({ control, name: "HookParameters", // unique name for your Field Array }); const watchedFee = watch("Fee"); // Reset form if activeFile changes useEffect(() => { if (!activeFile) return; const defaultValues = getDefaultValues(); reset(defaultValues); }, [activeFile, getDefaultValues, reset]); useEffect(() => { if ( watchedFee && (watchedFee.includes(".") || watchedFee.includes(",")) ) { setValue("Fee", watchedFee.replaceAll(".", "").replaceAll(",", "")); } }, [watchedFee, setValue]); // const { // fields: grantFields, // append: grantAppend, // remove: grantRemove, // } = useFieldArray({ // control, // name: "HookGrants", // unique name for your Field Array // }); const [hashedNamespace, setHashedNamespace] = useState(""); const namespace = watch("HookNamespace", getHookNamespace()); const calculateHashedValue = useCallback(async () => { const hashedVal = await sha256(namespace); setHashedNamespace(hashedVal.toUpperCase()); }, [namespace]); useEffect(() => { calculateHashedValue(); }, [namespace, calculateHashedValue]); const calculateFee = useCallback(async () => { if (!account) return; const formValues = getValues(); const tx = await prepareDeployHookTx(account, formValues); if (!tx) { return; } const res = await estimateFee(tx, account); if (res && res.base_fee) { setValue("Fee", Math.round(Number(res.base_fee || "")).toString()); } }, [account, getValues, setValue]); const tooLargeFile = () => { return Boolean( activeFile?.compiledContent?.byteLength && activeFile?.compiledContent?.byteLength >= 64000 ); }; const onSubmit: SubmitHandler = async data => { const currAccount = state.accounts.find( acc => acc.address === account?.address ); if (!account) return; if (currAccount) currAccount.isLoading = true; data.HookParameters.forEach(param => { delete param.$metaData; return param; }); const res = await deployHook(account, data); if (currAccount) currAccount.isLoading = false; if (res && res.engine_result === "tesSUCCESS") { toast.success("Transaction succeeded!"); return setIsSetHookDialogOpen(false); } toast.error(`Transaction failed! (${res?.engine_result_message})`); }; const onOpenChange = useCallback((open: boolean) => { setIsSetHookDialogOpen(open); if (open) calculateFee(); }, [calculateFee]); return (
Deploy configuration )} /> {errors.HookNamespace?.type === "required" && ( Namespace is required )} {fields.map((field, index) => ( {errors.HookParameters?.[index]?.HookParameter ?.HookParameterValue?.type === "required" && ( This field is required )} ))} { if (e.key === "." || e.key === ",") { e.preventDefault(); } }} step="1" defaultValue={10000} css={{ "-moz-appearance": "textfield", "&::-webkit-outer-spin-button": { "-webkit-appearance": "none", margin: 0, }, "&::-webkit-inner-spin-button ": { "-webkit-appearance": "none", margin: 0, }, }} /> {errors.Fee?.type === "required" && ( Fee is required )} {/* {grantFields.map((field, index) => ( ))} */} {/* */} {/* */}
); } ); SetHookDialog.displayName = "SetHookDialog"; export default SetHookDialog;