diff --git a/apps/dashboard/src/@/styles/globals.css b/apps/dashboard/src/@/styles/globals.css index 84017105617..724a3a1cf44 100644 --- a/apps/dashboard/src/@/styles/globals.css +++ b/apps/dashboard/src/@/styles/globals.css @@ -157,6 +157,11 @@ input:-webkit-autofill:active { transition: background-color 5000s ease-in-out 0s; } +.shiki, +.shiki span { + background-color: var(--background) !important; +} + .dark .shiki, .dark .shiki span { color: var(--shiki-dark) !important; diff --git a/apps/dashboard/src/app/nebula-app/(app)/components/ChatPageContent.tsx b/apps/dashboard/src/app/nebula-app/(app)/components/ChatPageContent.tsx index 8b530d0ba32..9bf5ef79650 100644 --- a/apps/dashboard/src/app/nebula-app/(app)/components/ChatPageContent.tsx +++ b/apps/dashboard/src/app/nebula-app/(app)/components/ChatPageContent.tsx @@ -43,7 +43,6 @@ export function ChatPageContent(props: { try { const content = JSON.parse(message.content) as { session_id: string; - request_id: string; data: string; type: "sign_transaction" | (string & {}); }; diff --git a/apps/dashboard/src/app/nebula-app/(app)/components/ChatSidebar.tsx b/apps/dashboard/src/app/nebula-app/(app)/components/ChatSidebar.tsx index aea98d73417..91e15e365c5 100644 --- a/apps/dashboard/src/app/nebula-app/(app)/components/ChatSidebar.tsx +++ b/apps/dashboard/src/app/nebula-app/(app)/components/ChatSidebar.tsx @@ -79,7 +79,7 @@ export function ChatSidebar(props: { )} -
+
- - +
+
+ +
+ + {}} - /> - - - - {}} - messages={[ - { - text: randomLorem(10), - type: "user", - }, { text: randomLorem(20), type: "error", }, ]} /> - - - - {}} - client={getThirdwebClient()} - authToken="xxxxx" - isChatStreaming={false} - sessionId="xxxxx" - twAccount={accountStub()} + + - - - - {}} - client={getThirdwebClient()} - authToken="xxxxx" - isChatStreaming={false} - sessionId="xxxxx" - twAccount={accountStub()} + + - - -
+ + + {}} + client={getThirdwebClient()} + authToken="xxxxx" + isChatStreaming={false} + sessionId="xxxxx" + twAccount={accountStub()} + messages={[ + { + text: randomLorem(40), + type: "assistant", + request_id: "xxxxx", + }, + { + text: randomLorem(50), + type: "assistant", + request_id: undefined, + }, + ]} + /> + + + + {}} + client={getThirdwebClient()} + authToken="xxxxx" + isChatStreaming={false} + sessionId="xxxxx" + twAccount={accountStub()} + messages={[ + { + text: responseWithCodeMarkdown, + type: "assistant", + request_id: undefined, + }, + { + text: responseWithCodeMarkdown, + type: "user", + }, + ]} + /> + + + +
+ + ); +} + +function Variant(props: { + label: string; + messages: ChatMessage[]; +}) { + return ( + + {}} + client={getThirdwebClient()} + authToken="xxxxx" + isChatStreaming={false} + sessionId="xxxxx" + twAccount={accountStub()} + messages={props.messages} + /> + ); } diff --git a/apps/dashboard/src/app/nebula-app/(app)/components/Chats.tsx b/apps/dashboard/src/app/nebula-app/(app)/components/Chats.tsx index c59616cdcfe..c2073a44816 100644 --- a/apps/dashboard/src/app/nebula-app/(app)/components/Chats.tsx +++ b/apps/dashboard/src/app/nebula-app/(app)/components/Chats.tsx @@ -12,17 +12,13 @@ import { ThumbsDownIcon, ThumbsUpIcon, } from "lucide-react"; -import { useTheme } from "next-themes"; import { useEffect, useRef, useState } from "react"; import { toast } from "sonner"; -import { type ThirdwebClient, prepareTransaction } from "thirdweb"; -import { useSendTransaction } from "thirdweb/react"; -import { TransactionButton } from "../../../../components/buttons/TransactionButton"; +import type { ThirdwebClient } from "thirdweb"; import { MarkdownRenderer } from "../../../../components/contract-components/published-contract/markdown-renderer"; -import { useV5DashboardChain } from "../../../../lib/v5-adapter"; -import { getSDKTheme } from "../../../components/sdk-component-theme"; import { submitFeedback } from "../api/feedback"; import { NebulaIcon } from "../icons/NebulaIcon"; +import { ExecuteTransactionCard } from "./ExecuteTransactionCard"; export type NebulaTxData = { chainId: number; @@ -119,7 +115,7 @@ export function Chats(props: { key={index} > {message.type === "user" ? ( -
+
) : message.type === "error" ? ( - +
{message.text} - +
) : message.type === "send_transaction" ? ( - {message.type === "assistant" && - !props.isChatStreaming && + !isMessagePending && props.sessionId && message.request_id && ( ); } - -function SendTransactionButton(props: { - txData: NebulaTxData; - twAccount: TWAccount; - client: ThirdwebClient; -}) { - const { theme } = useTheme(); - const { txData } = props; - const sendTransaction = useSendTransaction({ - payModal: { - theme: getSDKTheme(theme === "light" ? "light" : "dark"), - }, - }); - const chain = useV5DashboardChain(txData.chainId); - - return ( - { - const tx = prepareTransaction({ - chain: chain, - client: props.client, - data: txData.data, - to: txData.to, - value: BigInt(txData.value), - }); - - const promise = sendTransaction.mutateAsync(tx); - - toast.promise(promise, { - success: "Transaction sent successfully", - error: "Failed to send transaction", - }); - }} - className="gap-2" - twAccount={props.twAccount} - > - Execute Transaction - - ); -} diff --git a/apps/dashboard/src/app/nebula-app/(app)/components/EmptyStateChatPageContent.tsx b/apps/dashboard/src/app/nebula-app/(app)/components/EmptyStateChatPageContent.tsx index 17a26b30a3c..00935d5a348 100644 --- a/apps/dashboard/src/app/nebula-app/(app)/components/EmptyStateChatPageContent.tsx +++ b/apps/dashboard/src/app/nebula-app/(app)/components/EmptyStateChatPageContent.tsx @@ -20,7 +20,7 @@ export function EmptyStateChatPageContent(props: {
-

+

How can I help you
with the blockchain today?

diff --git a/apps/dashboard/src/app/nebula-app/(app)/components/ExecuteTransactionCard.stories.tsx b/apps/dashboard/src/app/nebula-app/(app)/components/ExecuteTransactionCard.stories.tsx new file mode 100644 index 00000000000..c0dc838bb61 --- /dev/null +++ b/apps/dashboard/src/app/nebula-app/(app)/components/ExecuteTransactionCard.stories.tsx @@ -0,0 +1,88 @@ +import { getThirdwebClient } from "@/constants/thirdweb.server"; +import type { Meta, StoryObj } from "@storybook/react"; +import { useState } from "react"; +import { ConnectButton, ThirdwebProvider } from "thirdweb/react"; +import { accountStub } from "../../../../stories/stubs"; +import { BadgeContainer, mobileViewport } from "../../../../stories/utils"; +import { + ExecuteTransactionCardLayout, + type TxStatus, +} from "./ExecuteTransactionCard"; + +const meta = { + title: "Nebula/ExecuteTransactionCard", + component: Story, + parameters: { + nextjs: { + appDirectory: true, + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Desktop: Story = { + args: {}, +}; + +export const Mobile: Story = { + args: {}, + parameters: { + viewport: mobileViewport("iphone14"), + }, +}; + +const client = getThirdwebClient(); + +const exampleTxHash = + "0xbe81f5a6421625052214b41bb79d1d82751b29aa5639b54d120f00531bb8bcf"; + +function Story() { + return ( + +
+
+ +
+ + + + + +
+
+ ); +} + +function Variant(props: { + label: string; + status: TxStatus; +}) { + const [status, setStatus] = useState(props.status); + return ( + + + + ); +} diff --git a/apps/dashboard/src/app/nebula-app/(app)/components/ExecuteTransactionCard.tsx b/apps/dashboard/src/app/nebula-app/(app)/components/ExecuteTransactionCard.tsx new file mode 100644 index 00000000000..5c32384ea72 --- /dev/null +++ b/apps/dashboard/src/app/nebula-app/(app)/components/ExecuteTransactionCard.tsx @@ -0,0 +1,213 @@ +import { CopyTextButton } from "@/components/ui/CopyTextButton"; +import { Spinner } from "@/components/ui/Spinner/Spinner"; +import {} from "@/components/ui/accordion"; +import { Button } from "@/components/ui/button"; +import { CodeClient } from "@/components/ui/code/code.client"; +import type { Account as TWAccount } from "@3rdweb-sdk/react/hooks/useApi"; +import { + ArrowRightLeftIcon, + CircleCheckIcon, + CircleXIcon, + ExternalLinkIcon, +} from "lucide-react"; +import { useTheme } from "next-themes"; +import Link from "next/link"; +import { useState } from "react"; +import { + type ThirdwebClient, + prepareTransaction, + waitForReceipt, +} from "thirdweb"; +import { useSendTransaction } from "thirdweb/react"; +import { TransactionButton } from "../../../../components/buttons/TransactionButton"; +import { useV5DashboardChain } from "../../../../lib/v5-adapter"; +import { getSDKTheme } from "../../../components/sdk-component-theme"; +import type { NebulaTxData } from "./Chats"; + +export type TxStatus = + | { + type: "idle"; + } + | { + type: "sending"; + } + | { + type: "confirming"; + txHash: string; + } + | { + type: "confirmed"; + txHash: string; + } + | { + type: "failed"; + txHash: string | undefined; + }; + +export function ExecuteTransactionCard(props: { + txData: NebulaTxData; + twAccount: TWAccount; + client: ThirdwebClient; +}) { + const [status, setStatus] = useState({ type: "idle" }); + return ( + + ); +} + +export function ExecuteTransactionCardLayout(props: { + txData: NebulaTxData; + twAccount: TWAccount; + client: ThirdwebClient; + status: TxStatus; + setStatus: (status: TxStatus) => void; +}) { + const { theme } = useTheme(); + const { txData } = props; + const sendTransaction = useSendTransaction({ + payModal: { + theme: getSDKTheme(theme === "light" ? "light" : "dark"), + }, + }); + const chain = useV5DashboardChain(txData.chainId); + const isTransactionSent = + props.status.type === "confirming" || props.status.type === "confirmed"; + + const explorer = chain.blockExplorers?.[0]?.url; + + return ( +
+
+
+

+ Transaction +

+ + +
+ +
+ { + const tx = prepareTransaction({ + chain: chain, + client: props.client, + data: txData.data, + to: txData.to, + value: BigInt(txData.value), + }); + + let txHash: string | undefined; + + try { + // submit transaction + props.setStatus({ type: "sending" }); + const submittedReceipt = await sendTransaction.mutateAsync(tx); + txHash = submittedReceipt.transactionHash; + + // wait for receipt + props.setStatus({ + type: "confirming", + txHash: submittedReceipt.transactionHash, + }); + + const confirmReceipt = await waitForReceipt(submittedReceipt); + txHash = confirmReceipt.transactionHash; + props.setStatus({ + type: "confirmed", + txHash: confirmReceipt.transactionHash, + }); + } catch { + props.setStatus({ + type: "failed", + txHash: txHash, + }); + } + }} + className="gap-2" + twAccount={props.twAccount} + > + + Execute Transaction + +
+
+ + {/* Tx Status */} + {props.status.type !== "idle" && ( +
+
+ {props.status.type === "sending" && ( +
+ +

Sending Transaction

+
+ )} + + {isTransactionSent && ( +
+ +

Transaction Sent

+
+ )} + + {props.status.type === "confirming" && ( +
+ +

Confirming Transaction

+
+ )} + + {props.status.type === "confirmed" && ( +
+ +

Transaction Confirmed

+
+ )} + + {props.status.type === "failed" && ( +
+ +

Transaction Failed

+
+ )} +
+ + {"txHash" in props.status && props.status.txHash && ( +
+ {explorer && ( + + )} + +
+ )} +
+ )} +
+ ); +} diff --git a/apps/dashboard/src/components/buttons/TransactionButton.tsx b/apps/dashboard/src/components/buttons/TransactionButton.tsx index fffcaab57cb..7dc6588d5b8 100644 --- a/apps/dashboard/src/components/buttons/TransactionButton.tsx +++ b/apps/dashboard/src/components/buttons/TransactionButton.tsx @@ -29,7 +29,7 @@ type TransactionButtonProps = Omit & { isPending: boolean; isGasless?: boolean; txChainID: number; - variant?: "destructive" | "primary"; + variant?: "destructive" | "primary" | "default"; twAccount: Account | undefined; };