Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -46,7 +46,7 @@ export function AdvancedSearchInput(props: {
value={searchType}
onValueChange={(value) => setSearchType(value as SearchType)}
>
<SelectTrigger className="w-[140px] bg-card">
<SelectTrigger className="w-[140px] bg-background rounded-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
Expand All @@ -61,7 +61,7 @@ export function AdvancedSearchInput(props: {
<div className="flex flex-1">
<div className="relative flex-1">
<Input
className="bg-card pl-9 border-r-0 rounded-r-none"
className="bg-background pl-9 border-r-0 rounded-r-none rounded-l-full"
placeholder={`Search by ${searchType}...`}
value={query}
onChange={(e) => setQuery(e.target.value)}
Expand All @@ -86,7 +86,7 @@ export function AdvancedSearchInput(props: {
onClick={handleSearch}
variant="outline"
disabled={!query.trim() || props.isLoading}
className="rounded-l-none gap-2 bg-card disabled:opacity-100"
className="rounded-l-none rounded-r-full gap-2 bg-background disabled:opacity-100"
>
{props.isLoading && <Spinner className="size-4" />}
Search
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { createColumnHelper } from "@tanstack/react-table";
import { format } from "date-fns";
import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react";
import { ArrowLeftIcon, ArrowRightIcon, UserIcon } from "lucide-react";
import Papa from "papaparse";
import { useCallback, useMemo, useState } from "react";
import type { ThirdwebClient } from "thirdweb";
Expand Down Expand Up @@ -264,11 +264,24 @@ export function InAppWalletUsersPageContent(

return (
<div>
<div className="flex flex-col gap-4">
{/* Top section */}
<div className="flex flex-col gap-4">
<div className="flex flex-col md:flex-row lg:items-center justify-end gap-3">
<div className="w-full max-w-lg">
<div className="overflow-hidden rounded-xl border border-border bg-card">
<div className="flex flex-col lg:flex-row lg:justify-between p-4 lg:px-6 py-5 lg:items-center gap-5">
<div>
<div className="flex mb-3">
<div className="p-2 rounded-full bg-background border border-border">
<UserIcon className="size-5 text-muted-foreground" />
</div>
</div>
<h2 className="font-semibold text-2xl tracking-tight">
User Wallets
</h2>
<p className="text-muted-foreground text-sm">
View and manage your project's users
</p>
</div>

<div className="flex flex-col items-start lg:items-end gap-2.5 border-t lg:border-t-0 pt-5 lg:pt-0 border-dashed">
<div className="w-full lg:w-auto lg:min-w-[320px]">
<AdvancedSearchInput
onSearch={handleSearch}
onClear={handleClearSearch}
Expand All @@ -277,7 +290,7 @@ export function InAppWalletUsersPageContent(
/>
</div>
<Button
className="gap-2 bg-card"
className="gap-2 bg-background rounded-full"
disabled={wallets.length === 0 || isPending}
onClick={downloadCSV}
variant="outline"
Expand All @@ -298,11 +311,11 @@ export function InAppWalletUsersPageContent(
data={wallets}
isFetched={walletsQuery.isFetched}
isPending={walletsQuery.isPending}
tableContainerClassName="rounded-b-none"
tableContainerClassName="rounded-none border-x-0 border-b-0"
title="in-app wallets"
/>

<div className="flex justify-center gap-3 rounded-b-lg border border-t-0 bg-card p-6">
<div className="flex justify-center gap-3 border-t bg-card p-6">
<Button
className="gap-2 bg-background"
disabled={activePage === 1 || walletsQuery.isPending}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,22 @@ export function ServerWalletsTableUI({
</div>

<div className="flex flex-col items-start lg:items-end gap-5 border-t lg:border-t-0 pt-5 lg:pt-0 border-dashed">
<SingleNetworkSelector
chainId={selectedChainId}
onChange={setSelectedChainId}
client={client}
disableChainId
className="w-fit min-w-[180px] rounded-full bg-background hover:bg-accent/50"
placeholder="Select network"
popoverContentClassName="!w-[320px] rounded-xl overflow-hidden"
/>
<div className="flex flex-row gap-2.5">
<CreateServerWallet
managementAccessToken={managementAccessToken}
project={project}
teamSlug={teamSlug}
/>
<SingleNetworkSelector
chainId={selectedChainId}
onChange={setSelectedChainId}
client={client}
disableChainId
className="w-fit min-w-[180px] rounded-full bg-background hover:bg-accent/50"
placeholder="Select network"
popoverContentClassName="!w-[320px] rounded-xl overflow-hidden"
/>
</div>

<div className="flex items-center gap-2.5">
<Label
Expand Down Expand Up @@ -187,25 +194,10 @@ export function ServerWalletsTableUI({
<XIcon className="size-5 text-muted-foreground" />
</div>
<p className="text-muted-foreground">No server wallets found</p>
<CreateServerWallet
managementAccessToken={managementAccessToken}
project={project}
teamSlug={teamSlug}
/>
</div>
)}
</TableContainer>

{wallets.length > 0 && (
<div className="flex justify-end items-center p-4 py-5 lg:px-6 border-t">
<CreateServerWallet
managementAccessToken={managementAccessToken}
project={project}
teamSlug={teamSlug}
/>
</div>
)}

{totalPages > 1 && (
<div className="flex flex-col items-center border-t p-6">
<div className="mb-4 text-muted-foreground text-sm">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { createVaultClient, listEoas } from "@thirdweb-dev/vault-sdk";
import { redirect } from "next/navigation";
import { ResponsiveSearchParamsProvider } from "responsive-rsc";
import { getAuthToken } from "@/api/auth-token";
Expand All @@ -6,10 +7,13 @@ import type { DurationId } from "@/components/analytics/date-range-selector";
import { ResponsiveTimeFilters } from "@/components/analytics/responsive-time-filters";
import { ProjectPage } from "@/components/blocks/project-page/project-page";
import { InAppWalletUsersPageContent } from "@/components/in-app-wallet-users-content/in-app-wallet-users-content";
import { NEXT_PUBLIC_THIRDWEB_VAULT_URL } from "@/constants/public-envs";
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
import { WalletProductIcon } from "@/icons/WalletProductIcon";
import { getFiltersFromSearchParams } from "@/lib/time";
import { loginRedirect } from "@/utils/redirects";
import type { Wallet } from "../transactions/server-wallets/wallet-table/types";
import { ServerWalletsTable } from "../transactions/server-wallets/wallet-table/wallet-table";
import { InAppWalletAnalytics } from "./analytics/chart";
import { InAppWalletsSummary } from "./analytics/chart/Summary";

Expand All @@ -20,6 +24,7 @@ export default async function Page(props: {
to?: string;
type?: string;
interval?: string;
page?: string;
}>;
}) {
const [searchParams, params] = await Promise.all([
Expand All @@ -40,12 +45,47 @@ export default async function Page(props: {
to: searchParams.to,
});

const project = await getProject(params.team_slug, params.project_slug);
const [vaultClient, project] = await Promise.all([
createVaultClient({
baseUrl: NEXT_PUBLIC_THIRDWEB_VAULT_URL,
}).catch(() => undefined),
getProject(params.team_slug, params.project_slug),
]);
Comment on lines +48 to +53
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix vault client fallback when env is unset.

Passing baseUrl as an empty string makes createVaultClient throw (the URL constructor rejects ""), so we silently fall back to vaultClient = undefined and the server wallet list never loads unless the env var is set. Coerce the value to undefined so the SDK’s default base URL is used when the env is absent.

-  const [vaultClient, project] = await Promise.all([
-    createVaultClient({
-      baseUrl: NEXT_PUBLIC_THIRDWEB_VAULT_URL,
-    }).catch(() => undefined),
+  const [vaultClient, project] = await Promise.all([
+    createVaultClient({
+      baseUrl: NEXT_PUBLIC_THIRDWEB_VAULT_URL || undefined,
+    }).catch(() => undefined),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [vaultClient, project] = await Promise.all([
createVaultClient({
baseUrl: NEXT_PUBLIC_THIRDWEB_VAULT_URL,
}).catch(() => undefined),
getProject(params.team_slug, params.project_slug),
]);
const [vaultClient, project] = await Promise.all([
createVaultClient({
baseUrl: NEXT_PUBLIC_THIRDWEB_VAULT_URL || undefined,
}).catch(() => undefined),
getProject(params.team_slug, params.project_slug),
]);
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/page.tsx
around lines 48 to 53, the vault client fallback fails because an empty string
is passed as baseUrl which makes createVaultClient throw; change the baseUrl
argument so that an empty string becomes undefined (e.g. coerce
NEXT_PUBLIC_THIRDWEB_VAULT_URL to undefined when falsy) so the SDK uses its
default URL, and keep the .catch(() => undefined) behavior for any runtime
errors.


if (!project) {
redirect(`/team/${params.team_slug}`);
}

const projectEngineCloudService = project.services.find(
(service) => service.name === "engineCloud",
);

const managementAccessToken =
projectEngineCloudService?.managementAccessToken;

// Fetch server wallets with pagination (5 per page)
const pageSize = 5;
const currentPage = Number.parseInt(searchParams.page ?? "1");

const eoas =
vaultClient && managementAccessToken
? await listEoas({
client: vaultClient,
request: {
auth: {
accessToken: managementAccessToken,
},
options: {
page: currentPage - 1,
// @ts-expect-error - TODO: fix this
page_size: pageSize,
Comment on lines +80 to +81
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The @ts-expect-error suppression suggests a type mismatch between the parameter name used (page_size) and what the Vault SDK expects. Consider checking the SDK documentation for the correct parameter naming convention - it might be pageSize (camelCase) rather than page_size (snake_case). Addressing this type error would improve code maintainability and prevent potential runtime issues.

Suggested change
// @ts-expect-error - TODO: fix this
page_size: pageSize,
pageSize,

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

This comment came from an experimental review—please leave feedback if it was helpful/unhelpful. Learn more about experimental comments here.

},
},
})
: { data: { items: [], totalRecords: 0 }, error: null, success: true };

const serverWallets = eoas.data?.items as Wallet[] | undefined;

const client = getClientThirdwebClient({
jwt: authToken,
teamId: project.teamId,
Expand Down Expand Up @@ -103,12 +143,34 @@ export default async function Page(props: {
authToken={authToken}
/>

<InAppWalletUsersPageContent
authToken={authToken}
client={client}
projectClientId={project.publishableKey}
teamId={project.teamId}
/>
{/* Server Wallets Section */}
<div className="flex flex-col gap-4">
{eoas.error ? null : (
<ServerWalletsTable
client={client}
currentPage={currentPage}
managementAccessToken={managementAccessToken ?? undefined}
project={project}
teamSlug={params.team_slug}
totalPages={Math.ceil(eoas.data.totalRecords / pageSize)}
totalRecords={eoas.data.totalRecords}
wallets={serverWallets ?? []}
/>
)}
</div>

{/* User Wallets Section */}
<div className="flex flex-col gap-4">
<h2 className="font-semibold text-2xl tracking-tight">
User wallets
</h2>
<InAppWalletUsersPageContent
authToken={authToken}
client={client}
projectClientId={project.publishableKey}
teamId={project.teamId}
/>
</div>
</div>
</ProjectPage>
</ResponsiveSearchParamsProvider>
Expand Down
Loading