Skip to content

Commit

Permalink
Feat/better tx result (#130)
Browse files Browse the repository at this point in the history
  • Loading branch information
portdeveloper authored Jul 7, 2024
1 parent 28f4977 commit aca8393
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 49 deletions.
6 changes: 3 additions & 3 deletions packages/nextjs/components/scaffold-eth/Address.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export const Address = ({ address, disableAddressLink, format, size = "base" }:
}

return (
<div className="flex items-center">
<div className="flex items-center flex-shrink-0">
<div className="flex-shrink-0">
<BlockieAvatar
address={address}
Expand All @@ -109,7 +109,7 @@ export const Address = ({ address, disableAddressLink, format, size = "base" }:
)}
{addressCopied ? (
<CheckCircleIcon
className="ml-1.5 text-xl font-normal text-primary h-5 w-5 cursor-pointer"
className="ml-1.5 text-xl font-normal text-secondary-content h-5 w-5 cursor-pointer"
aria-hidden="true"
/>
) : (
Expand All @@ -123,7 +123,7 @@ export const Address = ({ address, disableAddressLink, format, size = "base" }:
}}
>
<DocumentDuplicateIcon
className="ml-1.5 text-xl font-normal text-primary h-5 w-5 cursor-pointer"
className="ml-1.5 text-xl font-normal text-secondary-content h-5 w-5 cursor-pointer"
aria-hidden="true"
/>
</CopyToClipboard>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import { useEffect, useState } from "react";
import { InheritanceTooltip } from "./InheritanceTooltip";
import { Abi, AbiFunction } from "abitype";
Expand Down Expand Up @@ -74,17 +76,17 @@ export const ReadOnlyFunctionForm = ({
<InheritanceTooltip inheritedFrom={inheritedFrom} />
</p>
{inputElements}
<div className="flex justify-end w-full gap-5">
<div className="flex-grow w-4/5">
<div className="flex flex-col md:flex-row justify-between gap-2 flex-wrap">
<div className="flex-grow w-full md:max-w-[80%]">
{result !== null && result !== undefined && (
<div className="bg-secondary rounded-xl text-sm px-4 py-1.5 break-words">
<div className="bg-secondary rounded-3xl text-sm px-4 py-1.5 break-words overflow-auto">
<p className="font-bold m-0 mb-1">Result:</p>
<pre className="whitespace-pre-wrap break-words">{displayTxResult(result)}</pre>
<pre className="whitespace-pre-wrap break-words">{displayTxResult(result, "sm")}</pre>
</div>
)}
</div>
<button
className="btn btn-secondary btn-sm"
className="btn btn-secondary btn-sm self-end md:self-start"
onClick={async () => {
const { data } = await refetch();
setResult(data);
Expand Down
27 changes: 12 additions & 15 deletions packages/nextjs/components/scaffold-eth/Contract/TxReceipt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,28 @@ import { useState } from "react";
import { CopyToClipboard } from "react-copy-to-clipboard";
import { TransactionReceipt } from "viem";
import { CheckCircleIcon, DocumentDuplicateIcon } from "@heroicons/react/24/outline";
import { displayTxResult } from "~~/components/scaffold-eth";
import { ObjectFieldDisplay } from "~~/components/scaffold-eth";
import { replacer } from "~~/utils/scaffold-eth/common";

export const TxReceipt = (
txResult: string | number | bigint | Record<string, any> | TransactionReceipt | undefined,
) => {
export const TxReceipt = ({ txResult }: { txResult: TransactionReceipt }) => {
const [txResultCopied, setTxResultCopied] = useState(false);

return (
<div className="flex text-sm rounded-3xl peer-checked:rounded-b-none min-h-0 bg-secondary py-0">
<div className="mt-1 pl-2">
{txResultCopied ? (
<CheckCircleIcon
className="ml-1.5 text-xl font-normal text-sky-600 h-5 w-5 cursor-pointer"
aria-hidden="true"
/>
<CheckCircleIcon className="ml-1.5 text-xl font-normal h-5 w-5 cursor-pointer" aria-hidden="true" />
) : (
<CopyToClipboard
text={displayTxResult(txResult) as string}
text={JSON.stringify(txResult, replacer, 2)}
onCopy={() => {
setTxResultCopied(true);
setTimeout(() => {
setTxResultCopied(false);
}, 800);
}}
>
<DocumentDuplicateIcon
className="ml-1.5 text-xl font-normal text-sky-600 h-5 w-5 cursor-pointer"
aria-hidden="true"
/>
<DocumentDuplicateIcon className="ml-1.5 text-xl font-normal h-5 w-5 cursor-pointer" aria-hidden="true" />
</CopyToClipboard>
)}
</div>
Expand All @@ -39,8 +32,12 @@ export const TxReceipt = (
<div className="collapse-title text-sm min-h-0 py-1.5 pl-1">
<strong>Transaction Receipt</strong>
</div>
<div className="collapse-content overflow-auto bg-secondary rounded-t-none rounded-3xl">
<pre className="text-xs pt-4">{displayTxResult(txResult)}</pre>
<div className="collapse-content overflow-auto bg-secondary rounded-t-none rounded-3xl !pl-0">
<pre className="text-xs">
{Object.entries(txResult).map(([k, v]) => (
<ObjectFieldDisplay name={k} value={v} size="xs" leftPad={false} key={k} />
))}
</pre>
</div>
</div>
</div>
Expand Down
108 changes: 83 additions & 25 deletions packages/nextjs/components/scaffold-eth/Contract/utilsDisplay.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ReactElement } from "react";
import { TransactionBase, TransactionReceipt, formatEther, isAddress } from "viem";
import { ReactElement, useState } from "react";
import { TransactionBase, TransactionReceipt, formatEther, isAddress, isHex } from "viem";
import { ArrowsRightLeftIcon } from "@heroicons/react/24/solid";
import { Address } from "~~/components/scaffold-eth";
import { replacer } from "~~/utils/scaffold-eth/common";

Expand All @@ -13,44 +14,101 @@ type DisplayContent =
| undefined
| unknown;

type ResultFontSize = "sm" | "base" | "xs" | "lg" | "xl" | "2xl" | "3xl";

export const displayTxResult = (
displayContent: DisplayContent | DisplayContent[],
asText = false,
fontSize: ResultFontSize = "base",
): string | ReactElement | number => {
if (displayContent == null) {
return "";
}

if (typeof displayContent === "bigint") {
try {
const asNumber = Number(displayContent);
if (asNumber <= Number.MAX_SAFE_INTEGER && asNumber >= Number.MIN_SAFE_INTEGER) {
return asNumber;
} else {
return "Ξ" + formatEther(displayContent);
}
} catch (e) {
return "Ξ" + formatEther(displayContent);
}
return <NumberDisplay value={displayContent} />;
}

if (typeof displayContent === "string" && isAddress(displayContent)) {
return asText ? displayContent : <Address address={displayContent} />;
if (typeof displayContent === "string") {
if (isAddress(displayContent)) {
return <Address address={displayContent} size={fontSize} />;
}

if (isHex(displayContent)) {
return displayContent; // don't add quotes
}
}

if (Array.isArray(displayContent)) {
const mostReadable = (v: DisplayContent) =>
["number", "boolean"].includes(typeof v) ? v : displayTxResultAsText(v);
const displayable = JSON.stringify(displayContent.map(mostReadable), replacer);

return asText ? (
displayable
) : (
<span style={{ overflowWrap: "break-word", width: "100%" }}>{displayable.replaceAll(",", ",\n")}</span>
);
return <ArrayDisplay values={displayContent} size={fontSize} />;
}

if (typeof displayContent === "object") {
return <StructDisplay struct={displayContent} size={fontSize} />;
}

return JSON.stringify(displayContent, replacer, 2);
};

const displayTxResultAsText = (displayContent: DisplayContent) => displayTxResult(displayContent, true);
const NumberDisplay = ({ value }: { value: bigint }) => {
const [isEther, setIsEther] = useState(false);

const asNumber = Number(value);
if (asNumber <= Number.MAX_SAFE_INTEGER && asNumber >= Number.MIN_SAFE_INTEGER) {
return <span>{String(value)}</span>;
}

return (
<div className="flex items-baseline">
{isEther ? "Ξ" + formatEther(value) : String(value)}
<span
className="tooltip tooltip-secondary font-sans ml-2"
data-tip={isEther ? "Multiply by 1e18" : "Divide by 1e18"}
>
<button className="btn btn-ghost btn-circle btn-xs" onClick={() => setIsEther(!isEther)}>
<ArrowsRightLeftIcon className="h-3 w-3 opacity-65" />
</button>
</span>
</div>
);
};

export const ObjectFieldDisplay = ({
name,
value,
size,
leftPad = true,
}: {
name: string;
value: DisplayContent;
size: ResultFontSize;
leftPad?: boolean;
}) => {
return (
<div className={`flex flex-row items-baseline ${leftPad ? "ml-4" : ""}`}>
<span className="text-base-content dark:text-gray-400 mr-2">{name}:</span>
<span className="text-base-content">{displayTxResult(value, size)}</span>
</div>
);
};

const ArrayDisplay = ({ values, size }: { values: DisplayContent[]; size: ResultFontSize }) => {
return (
<div className="flex flex-col gap-y-1">
{values.length ? "array" : "[]"}
{values.map((v, i) => (
<ObjectFieldDisplay key={i} name={`[${i}]`} value={v} size={size} />
))}
</div>
);
};

const StructDisplay = ({ struct, size }: { struct: Record<string, any>; size: ResultFontSize }) => {
return (
<div className="flex flex-col gap-y-1">
struct
{Object.entries(struct).map(([k, v]) => (
<ObjectFieldDisplay key={k} name={k} value={v} size={size} />
))}
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const IntegerInput = ({
!disableMultiplyBy1e18 && (
<div
className="space-x-4 flex tooltip tooltip-top before:shadow-lg before:content-[attr(data-tip)] before:right-[-10px] before:left-auto before:transform-none"
data-tip="Multiply by 10^18 (wei)"
data-tip="Multiply by 1e18 (wei)"
>
<button
className={`${disabled ? "cursor-not-allowed" : "cursor-pointer"} font-semibold px-4 text-accent`}
Expand Down

0 comments on commit aca8393

Please sign in to comment.