Skip to content

Add activity log section to transaction page #7529

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9c44b45
Add transaction activity logs with expandable timeline view
cursoragent Jul 4, 2025
1b2b3fa
Checkpoint before follow-up message
cursoragent Jul 4, 2025
6858fba
Checkpoint before follow-up message
cursoragent Jul 4, 2025
d5df1ec
Checkpoint before follow-up message
cursoragent Jul 4, 2025
933aa1d
Refactor server client creation and add fallback for missing secret key
cursoragent Jul 4, 2025
a29b8c7
feat: add activity log section to transaction details page
cursoragent Jul 5, 2025
707076d
feat: improve activity log visual indicators and timestamp handling
cursoragent Jul 5, 2025
29310fb
fix: use same timestamp pattern as Timing Information card
cursoragent Jul 5, 2025
ad64fb9
feat: use createdAt instead of timestamp for activity log time displays
cursoragent Jul 5, 2025
9a42ff8
fix: correct event type matching for red dot color
cursoragent Jul 5, 2025
6f18324
feat: sort activity logs chronologically (oldest first)
cursoragent Jul 5, 2025
e82de0c
Refactor activity log entry UI with improved event type styling
cursoragent Jul 5, 2025
17dc805
Checkpoint before follow-up message
cursoragent Jul 5, 2025
c6c969f
Fix indentation in transaction details error handling logic
cursoragent Jul 5, 2025
0ece641
Refactor event type colors to use Badge component with variants
cursoragent Jul 5, 2025
ecc5479
Refactor activity log rendering with explicit sorting and rendering l…
cursoragent Jul 5, 2025
ccc3837
Refactor activity log sorting to use insertion sort algorithm
cursoragent Jul 5, 2025
3a44bd7
Checkpoint before follow-up message
cursoragent Jul 5, 2025
91a8db8
Remove timestamp from activity log entry item
cursoragent Jul 5, 2025
b106e3d
revert robots.txt change
joaquim-verges Jul 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import type { StaticImageData } from "next/image";
import Image from "next/image";
import type { FetchDeployMetadataResult } from "thirdweb/contract";
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/server-envs";
import { getConfiguredThirdwebClient } from "@/constants/thirdweb.server";
import { replaceIpfsUrl } from "@/lib/sdk";
import generalContractIcon from "../../../../../public/assets/tw-icons/general.png";

Expand All @@ -26,7 +27,17 @@ export const ContractIdImage: React.FC<ContractIdImageProps> = ({
<img
alt=""
className="size-8 rounded-full"
src={replaceIpfsUrl(logo, serverThirdwebClient)}
src={
DASHBOARD_THIRDWEB_SECRET_KEY
? replaceIpfsUrl(
logo,
getConfiguredThirdwebClient({
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
teamId: undefined,
}),
)
: logo
}
/>
);
}
Expand Down
4 changes: 3 additions & 1 deletion apps/dashboard/src/@/constants/thirdweb-client.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import "server-only";
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "./server-envs";
import { getConfiguredThirdwebClient } from "./thirdweb.server";

// During build time, the secret key might not be available
// Create a client that will work for build but may fail at runtime if secret key is needed
export const serverThirdwebClient = getConfiguredThirdwebClient({
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY || "dummy-build-time-secret",
teamId: undefined,
});
8 changes: 6 additions & 2 deletions apps/dashboard/src/@/constants/thirdweb.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,18 @@ export function getConfiguredThirdwebClient(options: {
});
}

// During build time, provide fallbacks if credentials are missing
const clientId = NEXT_PUBLIC_DASHBOARD_CLIENT_ID || "dummy-build-client";
const secretKey = options.secretKey || undefined;

return createThirdwebClient({
clientId: NEXT_PUBLIC_DASHBOARD_CLIENT_ID,
clientId: clientId,
config: {
storage: {
gatewayUrl: NEXT_PUBLIC_IPFS_GATEWAY_URL,
},
},
secretKey: options.secretKey,
secretKey: secretKey,
teamId: options.teamId,
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ImageResponse } from "next/og";
import { useId } from "react";
import { download } from "thirdweb/storage";
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/server-envs";
import { getConfiguredThirdwebClient } from "@/constants/thirdweb.server";
import { fetchChain } from "@/utils/fetchChain";

// Route segment config
Expand Down Expand Up @@ -81,16 +82,29 @@ export default async function Image({
fetch(new URL("og-lib/fonts/inter/700.ttf", import.meta.url)).then((res) =>
res.arrayBuffer(),
),
// download the chain icon if there is one
chain.icon?.url && hasWorkingChainIcon
? download({
client: serverThirdwebClient,
uri: chain.icon.url,
}).then((res) => res.arrayBuffer())
// download the chain icon if there is one and secret key is available
chain.icon?.url && hasWorkingChainIcon && DASHBOARD_THIRDWEB_SECRET_KEY
? (async () => {
try {
const client = getConfiguredThirdwebClient({
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
teamId: undefined,
});
const response = await download({
client,
uri: chain.icon?.url || "",
});
return response.arrayBuffer();
} catch (error) {
// If download fails, return undefined to fallback to no icon
console.warn("Failed to download chain icon:", error);
return undefined;
}
})()
: undefined,
// download the background image (based on chain)
fetch(
chain.icon?.url && hasWorkingChainIcon
chain.icon?.url && hasWorkingChainIcon && DASHBOARD_THIRDWEB_SECRET_KEY
? new URL(
"og-lib/assets/chain/bg-with-icon.png",

Expand Down Expand Up @@ -118,7 +132,7 @@ export default async function Image({
/>
{/* the actual component starts here */}

{hasWorkingChainIcon && (
{hasWorkingChainIcon && chainIcon && (
<img
alt=""
// @ts-expect-error - TS doesn't know about the ImageResponse component
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getAddress, getContract, isAddress } from "thirdweb";
import { localhost } from "thirdweb/chains";
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/server-envs";
import { getConfiguredThirdwebClient } from "@/constants/thirdweb.server";
import { mapV4ChainToV5Chain } from "@/utils/map-chains";
import { getUserThirdwebClient } from "../../../../../../../@/api/auth-token";
import { fetchChainWithLocalOverrides } from "../../../../../../../@/utils/fetchChainWithLocalOverrides";
Expand All @@ -18,13 +19,21 @@ export async function getContractPageParamsInfo(params: {
return undefined;
}

// attempt to get the auth token
// Create server client only if secret key is available
if (!DASHBOARD_THIRDWEB_SECRET_KEY) {
return undefined;
}

const serverClient = getConfiguredThirdwebClient({
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
teamId: undefined,
});

const serverContract = getContract({
address: contractAddress,
// eslint-disable-next-line no-restricted-syntax
chain: mapV4ChainToV5Chain(chainMetadata),
client: serverThirdwebClient,
client: serverClient,
});

const clientContract = getContract({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @next/next/no-img-element */
import "server-only";
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/server-envs";
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
import { getConfiguredThirdwebClient } from "@/constants/thirdweb.server";
import { cn } from "@/lib/utils";
import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler";
import { fallbackChainIcon } from "../../../../../../@/utils/chain-icons";
Expand All @@ -13,10 +13,16 @@ export async function ChainIcon(props: {
if (props.iconUrl) {
let imageLink = fallbackChainIcon;

const resolved = resolveSchemeWithErrorHandler({
client: serverThirdwebClient,
uri: props.iconUrl,
});
// Only resolve if we have a secret key available
const resolved = DASHBOARD_THIRDWEB_SECRET_KEY
? resolveSchemeWithErrorHandler({
client: getConfiguredThirdwebClient({
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
teamId: undefined,
}),
uri: props.iconUrl,
})
: null;

if (resolved) {
// check if it loads or not
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { notFound } from "next/navigation";
import { ImageResponse } from "next/og";
import { resolveAvatar } from "thirdweb/extensions/ens";
import { GradientBlobbie } from "@/components/blocks/avatar/GradientBlobbie";
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/server-envs";
import { getConfiguredThirdwebClient } from "@/constants/thirdweb.server";
/* eslint-disable @next/next/no-img-element */
import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler";
import { shortenIfAddress } from "@/utils/usedapp-external";
Expand All @@ -23,10 +24,18 @@ type PageProps = {

export default async function Image(props: PageProps) {
const params = await props.params;
const resolvedInfo = await resolveAddressAndEns(
params.addressOrEns,
serverThirdwebClient,
);

// Create client only if secret key is available
if (!DASHBOARD_THIRDWEB_SECRET_KEY) {
notFound();
}

const client = getConfiguredThirdwebClient({
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
teamId: undefined,
});

const resolvedInfo = await resolveAddressAndEns(params.addressOrEns, client);

if (!resolvedInfo) {
notFound();
Expand All @@ -43,14 +52,14 @@ export default async function Image(props: PageProps) {

const ensImage = resolvedInfo.ensName
? await resolveAvatar({
client: serverThirdwebClient,
client,
name: resolvedInfo.ensName,
})
: null;

const resolvedENSImageSrc = ensImage
? resolveSchemeWithErrorHandler({
client: serverThirdwebClient,
client,
uri: ensImage,
})
: null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { format } from "date-fns";
import { getSocialProfiles } from "thirdweb/social";
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/server-envs";
import { getConfiguredThirdwebClient } from "@/constants/thirdweb.server";
import { resolveEns } from "@/lib/ens";
import { correctAndUniqueLicenses } from "@/lib/licenses";
import { getPublishedContractsWithPublisherMapping } from "../utils/getPublishedContractsWithPublisherMapping";
Expand All @@ -22,17 +23,25 @@ export default async function Image(props: {
}) {
const { publisher, contract_id } = props.params;

// Create client only if secret key is available
if (!DASHBOARD_THIRDWEB_SECRET_KEY) {
return null;
}

const client = getConfiguredThirdwebClient({
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
teamId: undefined,
});

const [publishedContracts, socialProfiles] = await Promise.all([
getPublishedContractsWithPublisherMapping({
client: serverThirdwebClient,
client,
contract_id: contract_id,
publisher: publisher,
}),
getSocialProfiles({
address:
(await resolveEns(publisher, serverThirdwebClient)).address ||
publisher,
client: serverThirdwebClient,
address: (await resolveEns(publisher, client)).address || publisher,
client,
}),
]);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { format } from "date-fns";
import { getSocialProfiles } from "thirdweb/social";
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/server-envs";
import { getConfiguredThirdwebClient } from "@/constants/thirdweb.server";
import { resolveEns } from "@/lib/ens";
import { correctAndUniqueLicenses } from "@/lib/licenses";
import { getLatestPublishedContractsWithPublisherMapping } from "./utils/getPublishedContractsWithPublisherMapping";
Expand All @@ -21,17 +22,25 @@ export default async function Image(props: {
}) {
const { publisher, contract_id } = props.params;

// Create client only if secret key is available
if (!DASHBOARD_THIRDWEB_SECRET_KEY) {
return null;
}

const client = getConfiguredThirdwebClient({
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
teamId: undefined,
});

const [publishedContract, socialProfiles] = await Promise.all([
getLatestPublishedContractsWithPublisherMapping({
client: serverThirdwebClient,
client,
contract_id: contract_id,
publisher: publisher,
}),
getSocialProfiles({
address:
(await resolveEns(publisher, serverThirdwebClient)).address ||
publisher,
client: serverThirdwebClient,
address: (await resolveEns(publisher, client)).address || publisher,
client,
}),
]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { ImageResponse } from "next/og";
import { isAddress } from "thirdweb";
import { download } from "thirdweb/storage";
import { shortenAddress } from "thirdweb/utils";
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/server-envs";
import { getConfiguredThirdwebClient } from "@/constants/thirdweb.server";

const OgBrandIcon: React.FC = () => (
// biome-ignore lint/a11y/noSvgWithoutTitle: not needed
Expand Down Expand Up @@ -187,17 +188,41 @@ export async function publishedContractOGImageTemplate(params: {
ibmPlexMono500_,
ibmPlexMono700_,
image,
params.logo
? download({
client: serverThirdwebClient,
uri: params.logo,
}).then((res) => res.arrayBuffer())
params.logo && DASHBOARD_THIRDWEB_SECRET_KEY
? (async () => {
try {
const client = getConfiguredThirdwebClient({
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
teamId: undefined,
});
const response = await download({
client,
uri: params.logo || "",
});
return response.arrayBuffer();
} catch (error) {
console.warn("Failed to download logo:", error);
return undefined;
}
})()
: undefined,
params.publisherAvatar
? download({
client: serverThirdwebClient,
uri: params.publisherAvatar,
}).then((res) => res.arrayBuffer())
params.publisherAvatar && DASHBOARD_THIRDWEB_SECRET_KEY
? (async () => {
try {
const client = getConfiguredThirdwebClient({
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
teamId: undefined,
});
const response = await download({
client,
uri: params.publisherAvatar || "",
});
return response.arrayBuffer();
} catch (error) {
console.warn("Failed to download avatar:", error);
return undefined;
}
})()
: undefined,
]);

Expand Down
Loading
Loading