Skip to content

Commit

Permalink
[TOOL-3051] Dashboard: Move everything to App router, Delete Pages ro…
Browse files Browse the repository at this point in the history
…uter (#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 -->
  • Loading branch information
MananTank committed Jan 14, 2025
1 parent 7a3dff0 commit a6fe226
Show file tree
Hide file tree
Showing 76 changed files with 1,911 additions and 2,223 deletions.
1 change: 0 additions & 1 deletion apps/dashboard/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
1 change: 0 additions & 1 deletion apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@
"lucide-react": "0.468.0",
"next": "15.1.3",
"next-plausible": "^3.12.4",
"next-seo": "^6.5.0",
"next-themes": "^0.4.4",
"nextjs-toploader": "^1.6.12",
"openapi-types": "^12.1.3",
Expand Down
28 changes: 28 additions & 0 deletions apps/dashboard/src/@/actions/emailSignup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use server";

type EmailSignupParams = {
email: string;
send_welcome_email?: boolean;
};

export async function emailSignup(payLoad: EmailSignupParams) {
const response = await fetch(
"https://api.beehiiv.com/v2/publications/pub_9f54090a-6d14-406b-adfd-dbb30574f664/subscriptions",
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.BEEHIIV_API_KEY}`,
},
method: "POST",
body: JSON.stringify({
email: payLoad.email,
send_welcome_email: payLoad.send_welcome_email || false,
utm_source: "thirdweb.com",
}),
},
);

return {
status: response.status,
};
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
"use server";

import { getThirdwebClient } from "@/constants/thirdweb.server";
import { defineDashboardChain } from "lib/defineDashboardChain";
import type { NextApiRequest, NextApiResponse } from "next";
import { ZERO_ADDRESS, isAddress, toTokens } from "thirdweb";
import { getWalletBalance } from "thirdweb/wallets";

export type BalanceQueryRequest = {
chainId: number;
address: string;
};

export type BalanceQueryResponse = Array<{
type BalanceQueryResponse = Array<{
balance: string;
decimals: number;
name?: string;
Expand All @@ -18,21 +14,30 @@ export type BalanceQueryResponse = Array<{
display_balance: string;
}>;

const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method !== "POST") {
return res.status(400).json({ error: "invalid method" });
}
export async function getTokenBalancesFromMoralis(params: {
contractAddress: string;
chainId: number;
}): Promise<
| { data: BalanceQueryResponse; error: undefined }
| {
data: undefined;
error: string;
}
> {
const { contractAddress, chainId } = params;

const { chainId, address } = req.body;
if (!isAddress(address)) {
return res.status(400).json({ error: "invalid address" });
if (!isAddress(contractAddress)) {
return {
data: undefined,
error: "invalid address",
};
}

const getNativeBalance = async (): Promise<BalanceQueryResponse> => {
// eslint-disable-next-line no-restricted-syntax
const chain = defineDashboardChain(chainId, undefined);
const balance = await getWalletBalance({
address,
address: contractAddress,
chain,
client: getThirdwebClient(),
});
Expand All @@ -50,7 +55,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {

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

const resp = await fetch(tokenBalanceEndpoint, {
Expand All @@ -59,6 +64,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
"x-api-key": process.env.MORALIS_API_KEY || "",
},
});

if (!resp.ok) {
resp.body?.cancel();
return [];
Expand All @@ -76,7 +82,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
getTokenBalances(),
]);

return res.status(200).json([...nativeBalance, ...tokenBalances]);
};

export default handler;
return {
error: undefined,
data: [...nativeBalance, ...tokenBalances],
};
}
117 changes: 117 additions & 0 deletions apps/dashboard/src/@/actions/getWalletNFTs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"use server";

import {
generateAlchemyUrl,
isAlchemySupported,
transformAlchemyResponseToNFT,
} from "lib/wallet/nfts/alchemy";
import {
generateMoralisUrl,
isMoralisSupported,
transformMoralisResponseToNFT,
} from "lib/wallet/nfts/moralis";
import {
generateSimpleHashUrl,
isSimpleHashSupported,
transformSimpleHashResponseToNFT,
} from "lib/wallet/nfts/simpleHash";
import type { WalletNFT } from "lib/wallet/nfts/types";

type WalletNFTApiReturn =
| { result: WalletNFT[]; error?: undefined }
| { result?: undefined; error: string };

export async function getWalletNFTs(params: {
chainId: number;
owner: string;
}): Promise<WalletNFTApiReturn> {
const { chainId, owner } = params;
const supportedChainSlug = await isSimpleHashSupported(chainId);

if (supportedChainSlug && process.env.SIMPLEHASH_API_KEY) {
const url = generateSimpleHashUrl({ chainSlug: supportedChainSlug, owner });

const response = await fetch(url, {
method: "GET",
headers: {
"X-API-KEY": process.env.SIMPLEHASH_API_KEY,
},
next: {
revalidate: 10, // cache for 10 seconds
},
});

if (response.status >= 400) {
return {
error: response.statusText,
};
}

try {
const parsedResponse = await response.json();
const result = await transformSimpleHashResponseToNFT(
parsedResponse,
owner,
);

return { result };
} catch {
return { error: "error parsing response" };
}
}

if (isAlchemySupported(chainId)) {
const url = generateAlchemyUrl({ chainId, owner });

const response = await fetch(url, {
next: {
revalidate: 10, // cache for 10 seconds
},
});
if (response.status >= 400) {
return { error: response.statusText };
}
try {
const parsedResponse = await response.json();
const result = await transformAlchemyResponseToNFT(parsedResponse, owner);

return { result, error: undefined };
} catch (err) {
console.error("Error fetching NFTs", err);
return { error: "error parsing response" };
}
}

if (isMoralisSupported(chainId) && process.env.MORALIS_API_KEY) {
const url = generateMoralisUrl({ chainId, owner });

const response = await fetch(url, {
method: "GET",
headers: {
"X-API-Key": process.env.MORALIS_API_KEY,
},
next: {
revalidate: 10, // cache for 10 seconds
},
});

if (response.status >= 400) {
return { error: response.statusText };
}

try {
const parsedResponse = await response.json();
const result = await transformMoralisResponseToNFT(
await parsedResponse,
owner,
);

return { result };
} catch (err) {
console.error("Error fetching NFTs", err);
return { error: "error parsing response" };
}
}

return { error: "unsupported chain" };
}
98 changes: 98 additions & 0 deletions apps/dashboard/src/@/actions/proxies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"use server";

import { getAuthToken } from "../../app/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";

type ProxyActionParams = {
pathname: string;
searchParams?: Record<string, string>;
method: "GET" | "POST" | "PUT" | "DELETE";
body?: string;
headers?: Record<string, string>;
};

type ProxyActionResult<T extends object> =
| {
status: number;
ok: true;
data: T;
}
| {
status: number;
ok: false;
error: string;
};

async function proxy<T extends object>(
baseUrl: string,
params: ProxyActionParams,
): Promise<ProxyActionResult<T>> {
const authToken = await getAuthToken();

// build URL
const url = new URL(baseUrl);
url.pathname = params.pathname;
if (params.searchParams) {
for (const key in params.searchParams) {
url.searchParams.append(key, params.searchParams[key] as string);
}
}

const res = await fetch(url, {
method: params.method,
headers: {
...params.headers,
...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
},
body: params.body,
});

if (!res.ok) {
try {
const errorMessage = await res.text();
return {
status: res.status,
ok: false,
error: errorMessage || res.statusText,
};
} catch {
return {
status: res.status,
ok: false,
error: res.statusText,
};
}
}

return {
status: res.status,
ok: true,
data: await res.json(),
};
}

export async function analyticsServerProxy<T extends object = object>(
params: ProxyActionParams,
) {
return proxy<T>(
process.env.ANALYTICS_SERVICE_URL || "https://analytics.thirdweb.com",
params,
);
}

export async function apiServerProxy<T extends object = object>(
params: ProxyActionParams,
) {
return proxy<T>(API_SERVER_URL, params);
}

export async function payServerProxy<T extends object = object>(
params: ProxyActionParams,
) {
return proxy<T>(
process.env.NEXT_PUBLIC_PAY_URL
? `https://${process.env.NEXT_PUBLIC_PAY_URL}`
: "https://pay.thirdweb-dev.com",
params,
);
}
6 changes: 5 additions & 1 deletion apps/dashboard/src/@/components/blocks/wallet-address.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,11 @@ export function WalletAddress(props: {
>
{walletAvatarLink && (
<Avatar>
<AvatarImage src={walletAvatarLink} alt={profile.name} />
<AvatarImage
src={walletAvatarLink}
alt={profile.name}
className="object-cover"
/>
{profile.name && (
<AvatarFallback>
{profile.name.slice(0, 2)}
Expand Down
4 changes: 4 additions & 0 deletions apps/dashboard/src/@/components/ui/tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export function ToolTipLabel(props: {
leftIcon?: React.ReactNode;
hoverable?: boolean;
contentClassName?: string;
side?: "top" | "right" | "bottom" | "left";
align?: "center" | "start" | "end";
}) {
if (!props.label) {
return props.children;
Expand All @@ -48,6 +50,8 @@ export function ToolTipLabel(props: {
{props.children}
</TooltipTrigger>
<TooltipContent
side={props.side}
align={props.align}
sideOffset={10}
className={cn(
"max-w-[400px] whitespace-normal leading-relaxed",
Expand Down
Loading

0 comments on commit a6fe226

Please sign in to comment.