Skip to content

Commit a6fe226

Browse files
committed
[TOOL-3051] Dashboard: Move everything to App router, Delete Pages router (#5952)
<!-- start pr-codex --> ## PR-Codex overview This PR primarily focuses on the removal of unused files and the refactoring of several components to improve code quality and maintainability, including updates to API calls and component properties. ### Detailed summary - Deleted multiple unused files from various directories. - Refactored API calls to use `apiServerProxy` for better error handling. - Updated component props to be more flexible (e.g., `favoriteButton` can now be `undefined`). - Improved UI components by removing unnecessary class names and simplifying their structure. - Enhanced type definitions for better TypeScript support. - Added new metadata and structured content for landing pages. > The following files were skipped due to too many changes: `apps/dashboard/src/app/(landing)/account-abstraction/page.tsx`, `apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/pay/webhooks/components/webhooks.client.tsx`, `apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts`, `apps/dashboard/src/app/(landing)/in-app-wallets/page.tsx`, `apps/dashboard/src/@3rdweb-sdk/react/hooks/useApi.ts` > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 7a3dff0 commit a6fe226

File tree

76 files changed

+1911
-2223
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+1911
-2223
lines changed

apps/dashboard/next-env.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3-
/// <reference types="next/navigation-types/compat/navigation" />
43

54
// NOTE: This file should not be edited
65
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

apps/dashboard/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@
7070
"lucide-react": "0.468.0",
7171
"next": "15.1.3",
7272
"next-plausible": "^3.12.4",
73-
"next-seo": "^6.5.0",
7473
"next-themes": "^0.4.4",
7574
"nextjs-toploader": "^1.6.12",
7675
"openapi-types": "^12.1.3",
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"use server";
2+
3+
type EmailSignupParams = {
4+
email: string;
5+
send_welcome_email?: boolean;
6+
};
7+
8+
export async function emailSignup(payLoad: EmailSignupParams) {
9+
const response = await fetch(
10+
"https://api.beehiiv.com/v2/publications/pub_9f54090a-6d14-406b-adfd-dbb30574f664/subscriptions",
11+
{
12+
headers: {
13+
"Content-Type": "application/json",
14+
Authorization: `Bearer ${process.env.BEEHIIV_API_KEY}`,
15+
},
16+
method: "POST",
17+
body: JSON.stringify({
18+
email: payLoad.email,
19+
send_welcome_email: payLoad.send_welcome_email || false,
20+
utm_source: "thirdweb.com",
21+
}),
22+
},
23+
);
24+
25+
return {
26+
status: response.status,
27+
};
28+
}

apps/dashboard/src/pages/api/moralis/balances.ts renamed to apps/dashboard/src/@/actions/getBalancesFromMoralis.ts

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
1+
"use server";
2+
13
import { getThirdwebClient } from "@/constants/thirdweb.server";
24
import { defineDashboardChain } from "lib/defineDashboardChain";
3-
import type { NextApiRequest, NextApiResponse } from "next";
45
import { ZERO_ADDRESS, isAddress, toTokens } from "thirdweb";
56
import { getWalletBalance } from "thirdweb/wallets";
67

7-
export type BalanceQueryRequest = {
8-
chainId: number;
9-
address: string;
10-
};
11-
12-
export type BalanceQueryResponse = Array<{
8+
type BalanceQueryResponse = Array<{
139
balance: string;
1410
decimals: number;
1511
name?: string;
@@ -18,21 +14,30 @@ export type BalanceQueryResponse = Array<{
1814
display_balance: string;
1915
}>;
2016

21-
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
22-
if (req.method !== "POST") {
23-
return res.status(400).json({ error: "invalid method" });
24-
}
17+
export async function getTokenBalancesFromMoralis(params: {
18+
contractAddress: string;
19+
chainId: number;
20+
}): Promise<
21+
| { data: BalanceQueryResponse; error: undefined }
22+
| {
23+
data: undefined;
24+
error: string;
25+
}
26+
> {
27+
const { contractAddress, chainId } = params;
2528

26-
const { chainId, address } = req.body;
27-
if (!isAddress(address)) {
28-
return res.status(400).json({ error: "invalid address" });
29+
if (!isAddress(contractAddress)) {
30+
return {
31+
data: undefined,
32+
error: "invalid address",
33+
};
2934
}
3035

3136
const getNativeBalance = async (): Promise<BalanceQueryResponse> => {
3237
// eslint-disable-next-line no-restricted-syntax
3338
const chain = defineDashboardChain(chainId, undefined);
3439
const balance = await getWalletBalance({
35-
address,
40+
address: contractAddress,
3641
chain,
3742
client: getThirdwebClient(),
3843
});
@@ -50,7 +55,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
5055

5156
const getTokenBalances = async (): Promise<BalanceQueryResponse> => {
5257
const _chain = encodeURIComponent(`0x${chainId?.toString(16)}`);
53-
const _address = encodeURIComponent(address);
58+
const _address = encodeURIComponent(contractAddress);
5459
const tokenBalanceEndpoint = `https://deep-index.moralis.io/api/v2/${_address}/erc20?chain=${_chain}`;
5560

5661
const resp = await fetch(tokenBalanceEndpoint, {
@@ -59,6 +64,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
5964
"x-api-key": process.env.MORALIS_API_KEY || "",
6065
},
6166
});
67+
6268
if (!resp.ok) {
6369
resp.body?.cancel();
6470
return [];
@@ -76,7 +82,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
7682
getTokenBalances(),
7783
]);
7884

79-
return res.status(200).json([...nativeBalance, ...tokenBalances]);
80-
};
81-
82-
export default handler;
85+
return {
86+
error: undefined,
87+
data: [...nativeBalance, ...tokenBalances],
88+
};
89+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
"use server";
2+
3+
import {
4+
generateAlchemyUrl,
5+
isAlchemySupported,
6+
transformAlchemyResponseToNFT,
7+
} from "lib/wallet/nfts/alchemy";
8+
import {
9+
generateMoralisUrl,
10+
isMoralisSupported,
11+
transformMoralisResponseToNFT,
12+
} from "lib/wallet/nfts/moralis";
13+
import {
14+
generateSimpleHashUrl,
15+
isSimpleHashSupported,
16+
transformSimpleHashResponseToNFT,
17+
} from "lib/wallet/nfts/simpleHash";
18+
import type { WalletNFT } from "lib/wallet/nfts/types";
19+
20+
type WalletNFTApiReturn =
21+
| { result: WalletNFT[]; error?: undefined }
22+
| { result?: undefined; error: string };
23+
24+
export async function getWalletNFTs(params: {
25+
chainId: number;
26+
owner: string;
27+
}): Promise<WalletNFTApiReturn> {
28+
const { chainId, owner } = params;
29+
const supportedChainSlug = await isSimpleHashSupported(chainId);
30+
31+
if (supportedChainSlug && process.env.SIMPLEHASH_API_KEY) {
32+
const url = generateSimpleHashUrl({ chainSlug: supportedChainSlug, owner });
33+
34+
const response = await fetch(url, {
35+
method: "GET",
36+
headers: {
37+
"X-API-KEY": process.env.SIMPLEHASH_API_KEY,
38+
},
39+
next: {
40+
revalidate: 10, // cache for 10 seconds
41+
},
42+
});
43+
44+
if (response.status >= 400) {
45+
return {
46+
error: response.statusText,
47+
};
48+
}
49+
50+
try {
51+
const parsedResponse = await response.json();
52+
const result = await transformSimpleHashResponseToNFT(
53+
parsedResponse,
54+
owner,
55+
);
56+
57+
return { result };
58+
} catch {
59+
return { error: "error parsing response" };
60+
}
61+
}
62+
63+
if (isAlchemySupported(chainId)) {
64+
const url = generateAlchemyUrl({ chainId, owner });
65+
66+
const response = await fetch(url, {
67+
next: {
68+
revalidate: 10, // cache for 10 seconds
69+
},
70+
});
71+
if (response.status >= 400) {
72+
return { error: response.statusText };
73+
}
74+
try {
75+
const parsedResponse = await response.json();
76+
const result = await transformAlchemyResponseToNFT(parsedResponse, owner);
77+
78+
return { result, error: undefined };
79+
} catch (err) {
80+
console.error("Error fetching NFTs", err);
81+
return { error: "error parsing response" };
82+
}
83+
}
84+
85+
if (isMoralisSupported(chainId) && process.env.MORALIS_API_KEY) {
86+
const url = generateMoralisUrl({ chainId, owner });
87+
88+
const response = await fetch(url, {
89+
method: "GET",
90+
headers: {
91+
"X-API-Key": process.env.MORALIS_API_KEY,
92+
},
93+
next: {
94+
revalidate: 10, // cache for 10 seconds
95+
},
96+
});
97+
98+
if (response.status >= 400) {
99+
return { error: response.statusText };
100+
}
101+
102+
try {
103+
const parsedResponse = await response.json();
104+
const result = await transformMoralisResponseToNFT(
105+
await parsedResponse,
106+
owner,
107+
);
108+
109+
return { result };
110+
} catch (err) {
111+
console.error("Error fetching NFTs", err);
112+
return { error: "error parsing response" };
113+
}
114+
}
115+
116+
return { error: "unsupported chain" };
117+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
"use server";
2+
3+
import { getAuthToken } from "../../app/api/lib/getAuthToken";
4+
import { API_SERVER_URL } from "../constants/env";
5+
6+
type ProxyActionParams = {
7+
pathname: string;
8+
searchParams?: Record<string, string>;
9+
method: "GET" | "POST" | "PUT" | "DELETE";
10+
body?: string;
11+
headers?: Record<string, string>;
12+
};
13+
14+
type ProxyActionResult<T extends object> =
15+
| {
16+
status: number;
17+
ok: true;
18+
data: T;
19+
}
20+
| {
21+
status: number;
22+
ok: false;
23+
error: string;
24+
};
25+
26+
async function proxy<T extends object>(
27+
baseUrl: string,
28+
params: ProxyActionParams,
29+
): Promise<ProxyActionResult<T>> {
30+
const authToken = await getAuthToken();
31+
32+
// build URL
33+
const url = new URL(baseUrl);
34+
url.pathname = params.pathname;
35+
if (params.searchParams) {
36+
for (const key in params.searchParams) {
37+
url.searchParams.append(key, params.searchParams[key] as string);
38+
}
39+
}
40+
41+
const res = await fetch(url, {
42+
method: params.method,
43+
headers: {
44+
...params.headers,
45+
...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
46+
},
47+
body: params.body,
48+
});
49+
50+
if (!res.ok) {
51+
try {
52+
const errorMessage = await res.text();
53+
return {
54+
status: res.status,
55+
ok: false,
56+
error: errorMessage || res.statusText,
57+
};
58+
} catch {
59+
return {
60+
status: res.status,
61+
ok: false,
62+
error: res.statusText,
63+
};
64+
}
65+
}
66+
67+
return {
68+
status: res.status,
69+
ok: true,
70+
data: await res.json(),
71+
};
72+
}
73+
74+
export async function analyticsServerProxy<T extends object = object>(
75+
params: ProxyActionParams,
76+
) {
77+
return proxy<T>(
78+
process.env.ANALYTICS_SERVICE_URL || "https://analytics.thirdweb.com",
79+
params,
80+
);
81+
}
82+
83+
export async function apiServerProxy<T extends object = object>(
84+
params: ProxyActionParams,
85+
) {
86+
return proxy<T>(API_SERVER_URL, params);
87+
}
88+
89+
export async function payServerProxy<T extends object = object>(
90+
params: ProxyActionParams,
91+
) {
92+
return proxy<T>(
93+
process.env.NEXT_PUBLIC_PAY_URL
94+
? `https://${process.env.NEXT_PUBLIC_PAY_URL}`
95+
: "https://pay.thirdweb-dev.com",
96+
params,
97+
);
98+
}

apps/dashboard/src/@/components/blocks/wallet-address.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,11 @@ export function WalletAddress(props: {
121121
>
122122
{walletAvatarLink && (
123123
<Avatar>
124-
<AvatarImage src={walletAvatarLink} alt={profile.name} />
124+
<AvatarImage
125+
src={walletAvatarLink}
126+
alt={profile.name}
127+
className="object-cover"
128+
/>
125129
{profile.name && (
126130
<AvatarFallback>
127131
{profile.name.slice(0, 2)}

apps/dashboard/src/@/components/ui/tooltip.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ export function ToolTipLabel(props: {
3636
leftIcon?: React.ReactNode;
3737
hoverable?: boolean;
3838
contentClassName?: string;
39+
side?: "top" | "right" | "bottom" | "left";
40+
align?: "center" | "start" | "end";
3941
}) {
4042
if (!props.label) {
4143
return props.children;
@@ -48,6 +50,8 @@ export function ToolTipLabel(props: {
4850
{props.children}
4951
</TooltipTrigger>
5052
<TooltipContent
53+
side={props.side}
54+
align={props.align}
5155
sideOffset={10}
5256
className={cn(
5357
"max-w-[400px] whitespace-normal leading-relaxed",

0 commit comments

Comments
 (0)