Skip to content

Commit 8405f6f

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

File tree

68 files changed

+1909
-1987
lines changed

Some content is hidden

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

68 files changed

+1909
-1987
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.
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: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
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

@@ -18,21 +19,30 @@ export type BalanceQueryResponse = Array<{
1819
display_balance: string;
1920
}>;
2021

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

26-
const { chainId, address } = req.body;
27-
if (!isAddress(address)) {
28-
return res.status(400).json({ error: "invalid address" });
34+
if (!isAddress(contractAddress)) {
35+
return {
36+
data: undefined,
37+
error: "invalid address",
38+
};
2939
}
3040

3141
const getNativeBalance = async (): Promise<BalanceQueryResponse> => {
3242
// eslint-disable-next-line no-restricted-syntax
3343
const chain = defineDashboardChain(chainId, undefined);
3444
const balance = await getWalletBalance({
35-
address,
45+
address: contractAddress,
3646
chain,
3747
client: getThirdwebClient(),
3848
});
@@ -50,7 +60,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
5060

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

5666
const resp = await fetch(tokenBalanceEndpoint, {
@@ -59,6 +69,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
5969
"x-api-key": process.env.MORALIS_API_KEY || "",
6070
},
6171
});
72+
6273
if (!resp.ok) {
6374
resp.body?.cancel();
6475
return [];
@@ -76,7 +87,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
7687
getTokenBalances(),
7788
]);
7889

79-
return res.status(200).json([...nativeBalance, ...tokenBalances]);
80-
};
81-
82-
export default handler;
90+
return {
91+
error: undefined,
92+
data: [...nativeBalance, ...tokenBalances],
93+
};
94+
}
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+
export 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+
export 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)