From 4435577362edd7b931f1d8278371fbfe9ecb544b Mon Sep 17 00:00:00 2001 From: poomthiti Date: Tue, 3 Oct 2023 12:12:14 +0700 Subject: [PATCH 01/17] fix: rewrite publish status resolver into an effect --- CHANGELOG.md | 1 + src/lib/pages/publish-module/completed.tsx | 7 ++- .../components/ModulePublishCard.tsx | 4 +- .../components/UploadModuleCard.tsx | 20 ++++--- src/lib/pages/publish-module/formConstants.ts | 9 +-- src/lib/pages/publish-module/index.tsx | 4 +- src/lib/pages/publish-module/publish.tsx | 58 ++++++++++--------- src/lib/pages/publish-module/utils.ts | 22 ++++--- src/lib/services/moduleService.ts | 16 +---- 9 files changed, 70 insertions(+), 71 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 283dd5997..43e48707c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -118,6 +118,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Bug fixes +- [#555](https://github.com/alleslabs/celatone-frontend/pull/555) Rewrite publish status resolver into an effect - [#554](https://github.com/alleslabs/celatone-frontend/pull/554) Fix and improve ui alignment, cta, and publish error - [#547](https://github.com/alleslabs/celatone-frontend/pull/547) Fix form null and sort modules when submit an address - [#543](https://github.com/alleslabs/celatone-frontend/pull/543) Fix Initia testnet chain config diff --git a/src/lib/pages/publish-module/completed.tsx b/src/lib/pages/publish-module/completed.tsx index 88df71b42..752951529 100644 --- a/src/lib/pages/publish-module/completed.tsx +++ b/src/lib/pages/publish-module/completed.tsx @@ -49,8 +49,11 @@ export const PublishCompleted = ({ Published {modules.length} {plur("module", modules.length)} - {modules.map((module) => ( - + {modules.map((module, idx) => ( + ))} {status === "error" && ( { category: "Quick Actions", slug: "quick-actions", submenu: [ - // TODO change path to /account/0x1 { name: "0x1 Page", - slug: "/deploy", + slug: "/accounts/0x1", icon: "home" as IconKeys, }, { diff --git a/src/lib/pages/deploy-script/components/Footer.tsx b/src/lib/pages/deploy-script/components/Footer.tsx new file mode 100644 index 000000000..ac6635659 --- /dev/null +++ b/src/lib/pages/deploy-script/components/Footer.tsx @@ -0,0 +1,41 @@ +import { Button, Flex, Spinner } from "@chakra-ui/react"; +import { useRouter } from "next/router"; + +interface FooterProps { + disabled: boolean; + isLoading: boolean; + executeScript: () => void; +} + +export const Footer = ({ + isLoading = false, + disabled, + executeScript, +}: FooterProps) => { + const router = useRouter(); + return ( + + + + + + + ); +}; diff --git a/src/lib/pages/deploy-script/components/ScriptInput.tsx b/src/lib/pages/deploy-script/components/ScriptInput.tsx new file mode 100644 index 000000000..c93d7e37e --- /dev/null +++ b/src/lib/pages/deploy-script/components/ScriptInput.tsx @@ -0,0 +1,50 @@ +import { Flex, chakra } from "@chakra-ui/react"; + +import { AbiForm } from "lib/components/abi"; +import type { AbiFormData, ExposedFunction, Option } from "lib/types"; + +const MessageContainer = chakra(Flex, { + baseStyle: { + w: "full", + bg: "gray.900", + p: "24px 8px", + borderRadius: "8px", + fontSize: "14px", + color: "gray.400", + justifyContent: "center", + }, +}); + +interface ScriptInputProps { + fn: Option; + initialData: AbiFormData; + propsOnChange?: (data: AbiFormData) => void; + propsOnErrors?: (errors: [string, string][]) => void; +} + +export const ScriptInput = ({ + fn, + initialData, + propsOnChange, + propsOnErrors, +}: ScriptInputProps) => { + if (!fn) + return ( + + Your script input will display here after uploading .mv file. + + ); + + return fn.generic_type_params.length || fn.params.length ? ( + + ) : ( + + Your uploaded script file does not require any input. + + ); +}; diff --git a/src/lib/pages/deploy-script/components/UploadScriptCard.tsx b/src/lib/pages/deploy-script/components/UploadScriptCard.tsx new file mode 100644 index 000000000..5b23c3bea --- /dev/null +++ b/src/lib/pages/deploy-script/components/UploadScriptCard.tsx @@ -0,0 +1,112 @@ +import { Flex, Text } from "@chakra-ui/react"; +import { useCallback, useState } from "react"; + +import type { FileState } from ".."; +import { ComponentLoader } from "lib/components/ComponentLoader"; +import { DropZone } from "lib/components/dropzone"; +import { UploadCard } from "lib/components/upload/UploadCard"; +import { useDecodeScript } from "lib/services/moduleService"; +import type { ExposedFunction, Option } from "lib/types"; + +const DEFAULT_TEMP_FILE = { + file: undefined, + base64: "", +}; + +interface UploadScriptCardProps { + fileState: FileState; + removeFile: () => void; + setFile: ( + file: Option, + base64File: string, + decodeRes: Option, + decodeError: string + ) => void; +} + +export const UploadScriptCard = ({ + fileState: { file, decodeRes, decodeError }, + removeFile, + setFile, +}: UploadScriptCardProps) => { + const [tempFile, setTempFile] = useState<{ + file: Option; + base64: string; + }>(DEFAULT_TEMP_FILE); + + const { isFetching } = useDecodeScript({ + base64EncodedFile: tempFile.base64, + options: { + enabled: Boolean(tempFile.base64), + retry: 0, + refetchOnWindowFocus: false, + onSuccess: (data) => { + setFile(tempFile.file, tempFile.base64, data, ""); + setTempFile(DEFAULT_TEMP_FILE); + }, + onError: () => { + setFile( + tempFile.file, + tempFile.base64, + undefined, + "Failed to decode .mv script file" + ); + setTempFile(DEFAULT_TEMP_FILE); + }, + }, + }); + + const handleFileDrop = useCallback(async (target: File) => { + const reader = new FileReader(); + + reader.onload = () => { + const dataUrl = reader.result as string; + // strip "data:application/octet-stream;base64,oRzrCw..." + const base64String = dataUrl.replace(/^data:.*;base64,/, ""); + setTempFile({ file: target, base64: base64String }); + }; + reader.readAsDataURL(target); + }, []); + + return ( + + + + {file ? ( + + ) : ( + + )} + + + + + Function name + + + {decodeRes?.name ?? "-"} + + + + ); +}; diff --git a/src/lib/pages/deploy-script/components/index.ts b/src/lib/pages/deploy-script/components/index.ts new file mode 100644 index 000000000..a825d06ee --- /dev/null +++ b/src/lib/pages/deploy-script/components/index.ts @@ -0,0 +1,3 @@ +export * from "./Footer"; +export * from "./ScriptInput"; +export * from "./UploadScriptCard"; diff --git a/src/lib/pages/deploy-script/index.tsx b/src/lib/pages/deploy-script/index.tsx index 09fb47f3a..746917818 100644 --- a/src/lib/pages/deploy-script/index.tsx +++ b/src/lib/pages/deploy-script/index.tsx @@ -1,14 +1,207 @@ -import { Flex, Text } from "@chakra-ui/react"; +import { Flex, Heading, Text } from "@chakra-ui/react"; +import type { StdFee } from "@cosmjs/stargate"; +import { useCallback, useEffect, useMemo, useState } from "react"; + +import { + useCurrentChain, + useFabricateFee, + useSimulateFeeQuery, +} from "lib/app-provider"; +import { useDeployScriptTx } from "lib/app-provider/tx/script"; +import { ConnectWalletAlert } from "lib/components/ConnectWalletAlert"; +import { ErrorMessageRender } from "lib/components/ErrorMessageRender"; +import { EstimatedFeeRender } from "lib/components/EstimatedFeeRender"; +import WasmPageContainer from "lib/components/WasmPageContainer"; +import { useTxBroadcast } from "lib/providers/tx-broadcast"; +import type { + ExposedFunction, + HumanAddr, + Option, + AbiFormData, +} from "lib/types"; +import { composeScriptMsg, getAbiInitialData } from "lib/utils"; + +import { Footer } from "./components/Footer"; +import { ScriptInput } from "./components/ScriptInput"; +import { UploadScriptCard } from "./components/UploadScriptCard"; + +export interface FileState { + file: Option; + base64File: string; + decodeRes: Option; + decodeError: string; +} + +const DEFAULT_FILE_STATE: FileState = { + file: undefined, + base64File: "", + decodeRes: undefined, + decodeError: "", +}; export const DeployScript = () => { + const { address } = useCurrentChain(); + const fabricateFee = useFabricateFee(); + const deployScriptTx = useDeployScriptTx(); + const { broadcast } = useTxBroadcast(); + + // File State + const [fileState, setFileState] = useState(DEFAULT_FILE_STATE); + // TX State + const [estimatedFee, setEstimatedFee] = useState(); + const [simulateError, setSimulateError] = useState(""); + const [processing, setProcessing] = useState(false); + // Form State + const [inputData, setInputData] = useState({ + typeArgs: {}, + args: {}, + }); + const [abiErrors, setAbiErrors] = useState<[string, string][]>([]); + + const enableDeploy = useMemo( + () => + Boolean( + address && + fileState.base64File && + fileState.decodeRes && + !abiErrors.length && + Object.values(inputData.typeArgs).every((val) => val) + ), + [ + address, + fileState.base64File, + fileState.decodeRes, + abiErrors, + inputData.typeArgs, + ] + ); + + const resetState = useCallback(() => { + setFileState(DEFAULT_FILE_STATE); + setEstimatedFee(undefined); + setSimulateError(""); + setInputData({ typeArgs: {}, args: {} }); + setAbiErrors([]); + }, []); + + const { isFetching: isSimulating } = useSimulateFeeQuery({ + enabled: enableDeploy, + messages: composeScriptMsg( + address as HumanAddr, + fileState.base64File, + fileState.decodeRes, + inputData + ), + onSuccess: (gasRes) => { + if (gasRes) { + setEstimatedFee(fabricateFee(gasRes)); + setSimulateError(""); + } else setEstimatedFee(undefined); + }, + onError: (e) => { + setSimulateError(e.message); + setEstimatedFee(undefined); + }, + }); + + const proceed = useCallback(async () => { + const stream = await deployScriptTx({ + onTxSucceed: () => setProcessing(false), + onTxFailed: () => setProcessing(false), + estimatedFee, + messages: composeScriptMsg( + address as HumanAddr, + fileState.base64File, + fileState.decodeRes, + inputData + ), + }); + if (stream) { + setProcessing(true); + broadcast(stream); + } + }, [ + address, + broadcast, + deployScriptTx, + estimatedFee, + fileState.base64File, + fileState.decodeRes, + inputData, + ]); + + useEffect(() => { + const script = fileState.decodeRes; + if (script) { + setInputData({ + typeArgs: getAbiInitialData(script.generic_type_params.length), + args: getAbiInitialData(script.params.length), + }); + } + }, [fileState.decodeRes]); + return ( - - - DeployScript: Lorem ipsum dolor sit amet consectetur adipisicing elit. - Aliquam voluptas hic aperiam ad sint unde sapiente cupiditate! Quo et - cupiditate iure, sequi deserunt tempora corrupti eum. Error facere - placeat repellendus? - - + <> + + + Script + + + Upload .mv files to deploy one-time use Script which execute messages. + + + + Upload .mv file + + , + base64File: string, + decodeRes: Option, + decodeError: string + ) => setFileState({ file, base64File, decodeRes, decodeError })} + /> + + Script input + + + +

Transaction Fee:

+ +
+ {simulateError && ( + + )} +
+