diff --git a/app/deployments/page.tsx b/app/deployments/page.tsx index a62aaa9..9c0aa8e 100644 --- a/app/deployments/page.tsx +++ b/app/deployments/page.tsx @@ -26,7 +26,7 @@ export default function Page() { - Date and time + Date and time Contract Address diff --git a/app/workspace/[id]/page.tsx b/app/workspace/[id]/page.tsx index cbbbced..55877fa 100644 --- a/app/workspace/[id]/page.tsx +++ b/app/workspace/[id]/page.tsx @@ -1,6 +1,8 @@ "use client"; +import { BuildDeployPanel } from "@/components/build-deploy-panel"; import TopBottom from "@/components/top-bottom"; +import { Button } from "@/components/ui/button"; import { ResizablePanelGroup, ResizablePanel, @@ -14,20 +16,16 @@ import { TerminalContextProvider } from "react-terminal"; export default function Page() { return ( - - } bottom={

Chat component

} /> -
- - - } - bottom={ - - - - } - /> - + + + + + + + + } bottom={} /> + +
); } diff --git a/components/build-deploy-panel.tsx b/components/build-deploy-panel.tsx new file mode 100644 index 0000000..5e2752c --- /dev/null +++ b/components/build-deploy-panel.tsx @@ -0,0 +1,57 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "./ui/button"; +import { useCliCommands } from "./workspace/use-cli-commands"; +import useSWR from "swr"; +import { useParams } from "next/navigation"; +import { db } from "@/data/db"; + +export function BuildDeployPanel() { + const commands = useCliCommands(); + const [isBuilding, setIsBuilding] = useState(false); + const [isDeploying, setIsDeploying] = useState(false); + + const { id } = useParams<{ id: string }>(); + const { data: isDeployable } = useSWR( + id ? `deployable-${id}` : undefined, + async () => { + const ws = await db.workspaces.get(id); + return typeof ws?.dll === "string"; + } + ); + + return ( +
+ + +
+ ); +} diff --git a/components/workspace/cli.tsx b/components/workspace/cli.tsx index ea9b93f..a128812 100644 --- a/components/workspace/cli.tsx +++ b/components/workspace/cli.tsx @@ -1,116 +1,11 @@ "use client"; -import { build } from "@/data/build"; -import { db } from "@/data/db"; -import { useWallet } from "@/data/wallet"; import { useTheme } from "next-themes"; -import { useParams } from "next/navigation"; import { ReactTerminal } from "react-terminal"; -import { z } from "zod"; +import { useCliCommands } from "./use-cli-commands"; export default function Cli() { - const { id } = useParams(); - const wallet = useWallet(); - const commands = { - help: () => ( -
-

These are the available commands:

-
    -
  1. clear - clears the terminal
  2. -
  3. build - builds the current workspace
  4. -
  5. deploy - deploys the built smart contract
  6. -
  7. - check txID - checks the result of transaction -
  8. -
-
- ), - build: async () => { - if (typeof id !== "string") throw new Error("id is not string"); - const start = `/workspace/${id}/`; - const files = await db.files - .filter((file) => file.path.startsWith(start)) - .toArray(); - const params = files - .map((file) => ({ - path: decodeURIComponent(file.path.replace(start, "")), - contents: file.contents, - })) - .filter((i) => i.path.startsWith("src")); - - const str = await build(params); - await db.workspaces.update(id, { dll: str }); - - return "Build successful."; - }, - deploy: async () => { - if (typeof id !== "string") return "Workspace id not found."; - const { dll } = (await db.workspaces.get(id)) || {}; - if (!dll) return "Contract not built. Please build first."; - if (!wallet) return "Wallet not ready."; - try { - await wallet.faucet(); - } catch (err) {} - const { TransactionId } = await wallet.deploy(dll); - try { - const result = await wallet.getTxResult(TransactionId); - return `TransactionId: ${TransactionId}, Status: ${result.Status}`; - } catch (err) { - return JSON.stringify(err, undefined, 2); - } - }, - check: async (id: string) => { - if (!id) return `Please enter the Transaction ID.`; - if (!wallet) return "Wallet not ready."; - try { - const result = await wallet.getTxResult(id); - const logs = await wallet.getLogs(id); - const { data } = z.object({ proposalId: z.string() }).safeParse(logs); - if (!data?.proposalId) return "Missing proposalId."; - const proposalInfo = await wallet.getProposalInfo(data?.proposalId); - const releasedTxId = proposalInfo?.data.proposal.releasedTxId; - const releasedTxLogs = releasedTxId - ? await wallet.getLogs(releasedTxId) - : undefined; - const { data: contractAddressData } = z - .object({ address: z.string() }) - .safeParse(releasedTxLogs); - - return ( - <> -
- - - - - - - - - - - - - - - - - - - - -
TransactionId:{id}
Status:{result.Status}
ProposalId:{data?.proposalId}
Proposal Status:{proposalInfo?.data.proposal.status}
Contract Address: - {proposalInfo?.data.proposal.status === "released" - ? contractAddressData?.address - : "-"} -
- - ); - } catch (err) { - return JSON.stringify(err, undefined, 2); - } - }, - }; + const commands = useCliCommands(); const { theme, systemTheme } = useTheme(); diff --git a/components/workspace/use-cli-commands.tsx b/components/workspace/use-cli-commands.tsx new file mode 100644 index 0000000..8f303ea --- /dev/null +++ b/components/workspace/use-cli-commands.tsx @@ -0,0 +1,187 @@ +import { build } from "@/data/build"; +import { db } from "@/data/db"; +import { useWallet } from "@/data/wallet"; +import { Loader2 } from "lucide-react"; +import Link from "next/link"; +import { useParams } from "next/navigation"; +import { useContext } from "react"; +import { TerminalContext } from "react-terminal"; +import { z } from "zod"; + +export function useCliCommands() { + const terminalContext = useContext(TerminalContext); + + const { id } = useParams<{ id: string }>(); + const wallet = useWallet(); + const commands = { + help: () => { + terminalContext.setBufferedContent( +
+

These are the available commands:

+
    +
  1. clear - clears the terminal
  2. +
  3. build - builds the current workspace
  4. +
  5. deploy - deploys the built smart contract
  6. +
  7. + check txID - checks the result of transaction +
  8. +
+
+ ); + }, + build: async () => { + if (typeof id !== "string") throw new Error("id is not string"); + await db.workspaces.update(id, { dll: undefined }); + const start = `/workspace/${id}/`; + terminalContext.setBufferedContent( + <> +

Loading files...

+ + ); + const files = ( + await db.files.filter((file) => file.path.startsWith(start)).toArray() + ) + .map((file) => ({ + path: decodeURIComponent(file.path.replace(start, "")), + contents: file.contents, + })) + .filter((i) => i.path.startsWith("src")); + + terminalContext.setBufferedContent( + <> +

Loaded files: {files.map((i) => i.path).join(", ")}

+

+ Building... +

+ + ); + + try { + const str = await build(files); + if (typeof str === "string") { + await db.workspaces.update(id, { dll: str }); + terminalContext.setBufferedContent( + <> +

Build successful.

+ + ); + return; + } else { + terminalContext.setBufferedContent( + <> + {terminalContext.bufferedContent} +

Build failed.

+ + ); + return; + } + } catch (err) { + if (err instanceof Error) + terminalContext.setBufferedContent(<>{err.message}); + return; + } + }, + deploy: async () => { + if (typeof id !== "string") { + terminalContext.setBufferedContent( + <> +

Workspace {id} not found.

+ + ); + return; + } + const { dll } = (await db.workspaces.get(id)) || {}; + if (!dll) { + terminalContext.setBufferedContent( + <> +

Contract not built.

+ + ); + return; + } + if (!wallet) { + terminalContext.setBufferedContent( + <> +

Wallet not ready.

+ + ); + return; + } + const { TransactionId } = await wallet.deploy(dll); + try { + const result = await wallet.getTxResult(TransactionId); + terminalContext.setBufferedContent( + <> +

TransactionId: {TransactionId}

+

Status: {result.Status}

+

+ See all deployments +

+ + ); + return; + } catch (err) { + terminalContext.setBufferedContent( + <> + {JSON.stringify(err, undefined, 2)} +
+ + ); + return; + } + }, + check: async (id: string) => { + if (!id) return `Please enter the Transaction ID.`; + if (!wallet) return "Wallet not ready."; + try { + const result = await wallet.getTxResult(id); + const logs = await wallet.getLogs(id); + const { data } = z.object({ proposalId: z.string() }).safeParse(logs); + if (!data?.proposalId) return "Missing proposalId."; + const proposalInfo = await wallet.getProposalInfo(data?.proposalId); + const releasedTxId = proposalInfo?.data.proposal.releasedTxId; + const releasedTxLogs = releasedTxId + ? await wallet.getLogs(releasedTxId) + : undefined; + const { data: contractAddressData } = z + .object({ address: z.string() }) + .safeParse(releasedTxLogs); + + return ( + <> + + + + + + + + + + + + + + + + + + + + + +
TransactionId:{id}
Status:{result.Status}
ProposalId:{data?.proposalId}
Proposal Status:{proposalInfo?.data.proposal.status}
Contract Address: + {proposalInfo?.data.proposal.status === "released" + ? contractAddressData?.address + : "-"} +
+ + ); + } catch (err) { + return JSON.stringify(err, undefined, 2); + } + }, + }; + + return commands; +} diff --git a/data/build.ts b/data/build.ts index 05eab7d..d724e47 100644 --- a/data/build.ts +++ b/data/build.ts @@ -32,6 +32,7 @@ export async function build(files: FileContent[]) { `${getBuildServerBaseUrl()}/playground/build`, requestInit ); + if (!response.ok) { const { message } = await response.json(); throw new Error(message);