Skip to content

Commit fd29031

Browse files
committed
[TOOL-3051] Dashboard: Move everything to App router, Delete Pages router
1 parent 0e2b3df commit fd29031

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)