Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c65022174d | ||
|
|
6818cbabf2 | ||
|
|
a7fb844d87 | ||
|
|
bf431961ec | ||
|
|
c5d4cab8f8 | ||
|
|
1ee0dffec4 | ||
|
|
33611409b0 | ||
|
|
ab55aec10c | ||
|
|
ca22e4659e | ||
|
|
411895c002 | ||
|
|
cfa1c20a91 | ||
|
|
2b16f90715 | ||
|
|
e8892b152f | ||
|
|
073b25afd7 | ||
|
|
4550bb2663 | ||
|
|
f5f8a40320 | ||
|
|
3569763501 | ||
|
|
1401f8d039 | ||
|
|
1616b7f619 | ||
|
|
aa548a359f | ||
|
|
eef52dfa7e | ||
|
|
19ec571500 | ||
|
|
47dce2e05c | ||
|
|
8b4695224c | ||
|
|
94e2d5b8dd | ||
|
|
40fa323294 | ||
|
|
2cebb420e6 | ||
|
|
e89ddb24f2 | ||
|
|
86f205e2aa | ||
|
|
6c277ae676 | ||
|
|
153651df35 | ||
|
|
8ba460b6fc | ||
|
|
1e61a9edfc | ||
|
|
d03e687aa9 | ||
|
|
e1c0dbe19e | ||
|
|
3e7df081d7 | ||
|
|
b6f189c220 | ||
|
|
fb4d0a26f1 | ||
|
|
bc345371ba | ||
|
|
7a8e4e5f86 | ||
|
|
6ba3b30b60 | ||
|
|
5ad442cf0c | ||
|
|
5756c74a71 | ||
|
|
a1cff7a579 | ||
|
|
eafaefec20 | ||
|
|
b81d7a1854 | ||
|
|
21cc45adc2 | ||
|
|
a67d342779 | ||
|
|
60edce49c0 | ||
|
|
20c0dca958 | ||
|
|
4b30b847e2 | ||
|
|
b2cc17fd1e | ||
|
|
ace1dc5b09 | ||
|
|
9f79277344 | ||
|
|
6e0fd205ff | ||
|
|
69b1ee9176 | ||
|
|
0a19142a24 | ||
|
|
feb4fd0908 | ||
|
|
1d8695eb42 | ||
|
|
858dc53a25 | ||
|
|
8997fac1b0 | ||
|
|
403e4224e4 | ||
|
|
de039ef462 | ||
|
|
35fd3bcafa | ||
|
|
80a22ff45e | ||
|
|
eefa2ab424 | ||
|
|
ab9a148b75 | ||
|
|
8bbee03c77 | ||
|
|
b38604cce8 | ||
|
|
59eda22043 | ||
|
|
577636c037 | ||
|
|
1d844ac5d6 | ||
|
|
c7be92f75b | ||
|
|
79ed399e56 | ||
|
|
ded5e22bb7 | ||
|
|
5029345461 | ||
|
|
c9ccc59464 | ||
|
|
dc05f2ea3c | ||
|
|
7d9986f066 | ||
|
|
bb6c71e968 | ||
|
|
89beb3c5d3 | ||
|
|
3b98fbf8bb | ||
|
|
8b5cb47339 | ||
|
|
0760706dbb | ||
|
|
eded4ff1be | ||
|
|
b705406c5d | ||
|
|
17374d15f2 | ||
|
|
79769d6859 | ||
|
|
b6d65cf1c4 | ||
|
|
a92617f0b0 | ||
|
|
8e82ec860c | ||
|
|
0b6239735e | ||
|
|
df4da68d68 | ||
|
|
94ba9cf7aa | ||
|
|
1b64d6eb2d | ||
|
|
f1afa3b52d | ||
|
|
dbb64ea190 | ||
|
|
eed734c766 | ||
|
|
a3a3062ae1 | ||
|
|
4ffcf305ab | ||
|
|
fe3f24e309 | ||
|
|
9f5510018e | ||
|
|
73894d23b9 | ||
|
|
4538441334 | ||
|
|
913f2e4366 | ||
|
|
da4ba21a52 | ||
|
|
8d9854daf1 | ||
|
|
9cbcbe7760 | ||
|
|
00f1d29654 | ||
|
|
b511a695f9 | ||
|
|
ea34a975ca | ||
|
|
a834199dac | ||
|
|
be7759d2fe | ||
|
|
15178b2f96 | ||
|
|
afd0ccfdfd | ||
|
|
07a5c98b5b | ||
|
|
51a28c4ed6 | ||
|
|
c290997b11 |
@@ -12,7 +12,7 @@ packages/eslint-config-thirdweb/ @thirdweb-dev/core-platform @edwardysun
|
||||
packages/tw-tsconfig/ @thirdweb-dev/core-platform @edwardysun
|
||||
|
||||
# apps
|
||||
apps/ @thirdweb-dev/developer-tools @thirdweb-dev/core-platform @jakubkrehel
|
||||
apps/ @thirdweb-dev/developer-tools @thirdweb-dev/core-platform
|
||||
|
||||
# .github folder + .changeset + turbo config is owned by jonas for now
|
||||
scripts/ @jnsdls @joaquim-verges
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
---
|
||||
title: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"
|
||||
---
|
||||
<!--
|
||||
|
||||
## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"
|
||||
|
||||
If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):
|
||||
|
||||
## Notes for the reviewer
|
||||
|
||||
Anything important to call out? Be sure to also clarify these in your comments.
|
||||
|
||||
## How to test
|
||||
|
||||
Unit tests, playground, etc.
|
||||
|
||||
-->
|
||||
|
||||
@@ -27,5 +27,9 @@
|
||||
},
|
||||
"[css]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
}
|
||||
},
|
||||
"eslint.workingDirectories": [
|
||||
{ "pattern": "./packages/*/" },
|
||||
{ "pattern": "./apps/*/" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ module.exports = [
|
||||
// -- connect
|
||||
"/connect",
|
||||
"/connect/sign-in",
|
||||
"/connect/pay",
|
||||
// -- nebula
|
||||
"/nebula",
|
||||
// --insight
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
"next": true,
|
||||
"ignore": [
|
||||
"src/@/components/ui/**",
|
||||
"src/components/notices/AnnouncementBanner.tsx"
|
||||
"src/components/notices/AnnouncementBanner.tsx",
|
||||
"src/components/cmd-k-search/index.tsx",
|
||||
"src/lib/search.ts"
|
||||
],
|
||||
"project": ["src/**"],
|
||||
"ignoreBinaries": ["only-allow", "biome"],
|
||||
|
||||
@@ -31,29 +31,29 @@
|
||||
"@marsidev/react-turnstile": "^1.1.0",
|
||||
"@n8tb1t/use-scroll-position": "^2.0.3",
|
||||
"@radix-ui/react-accordion": "^1.2.2",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.4",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.5",
|
||||
"@radix-ui/react-avatar": "^1.1.2",
|
||||
"@radix-ui/react-checkbox": "^1.1.3",
|
||||
"@radix-ui/react-dialog": "1.1.4",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.3",
|
||||
"@radix-ui/react-hover-card": "^1.1.3",
|
||||
"@radix-ui/react-dialog": "1.1.5",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.5",
|
||||
"@radix-ui/react-hover-card": "^1.1.5",
|
||||
"@radix-ui/react-label": "^2.1.1",
|
||||
"@radix-ui/react-popover": "^1.1.3",
|
||||
"@radix-ui/react-popover": "^1.1.5",
|
||||
"@radix-ui/react-progress": "^1.1.1",
|
||||
"@radix-ui/react-radio-group": "^1.2.2",
|
||||
"@radix-ui/react-select": "^2.1.3",
|
||||
"@radix-ui/react-select": "^2.1.5",
|
||||
"@radix-ui/react-separator": "^1.1.1",
|
||||
"@radix-ui/react-slot": "^1.1.1",
|
||||
"@radix-ui/react-switch": "^1.1.2",
|
||||
"@radix-ui/react-tooltip": "1.1.5",
|
||||
"@sentry/nextjs": "8.45.1",
|
||||
"@shazow/whatsabi": "^0.18.0",
|
||||
"@tanstack/react-query": "5.62.16",
|
||||
"@radix-ui/react-tooltip": "1.1.7",
|
||||
"@sentry/nextjs": "8.52.0",
|
||||
"@shazow/whatsabi": "^0.19.0",
|
||||
"@tanstack/react-query": "5.65.1",
|
||||
"@tanstack/react-table": "^8.20.6",
|
||||
"@thirdweb-dev/service-utils": "workspace:*",
|
||||
"@vercel/functions": "^1.5.2",
|
||||
"@vercel/og": "^0.6.4",
|
||||
"abitype": "1.0.7",
|
||||
"@vercel/functions": "^1.6.0",
|
||||
"@vercel/og": "^0.6.5",
|
||||
"abitype": "1.0.8",
|
||||
"chakra-react-select": "^4.7.6",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -62,19 +62,19 @@
|
||||
"date-fns": "4.1.0",
|
||||
"fetch-event-stream": "0.1.5",
|
||||
"flat": "^6.0.1",
|
||||
"framer-motion": "11.15.0",
|
||||
"framer-motion": "12.0.6",
|
||||
"fuse.js": "7.0.0",
|
||||
"input-otp": "^1.4.1",
|
||||
"ioredis": "^5.4.1",
|
||||
"ipaddr.js": "^2.2.0",
|
||||
"lucide-react": "0.468.0",
|
||||
"next": "15.1.3",
|
||||
"lucide-react": "0.474.0",
|
||||
"next": "15.1.6",
|
||||
"next-plausible": "^3.12.4",
|
||||
"next-themes": "^0.4.4",
|
||||
"nextjs-toploader": "^1.6.12",
|
||||
"openapi-types": "^12.1.3",
|
||||
"p-limit": "^6.2.0",
|
||||
"papaparse": "^5.4.1",
|
||||
"papaparse": "^5.5.2",
|
||||
"pluralize": "^8.0.0",
|
||||
"posthog-js": "1.67.1",
|
||||
"qrcode": "^1.5.3",
|
||||
@@ -83,17 +83,17 @@
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "19.0.0",
|
||||
"react-dropzone": "^14.3.5",
|
||||
"react-error-boundary": "^4.1.2",
|
||||
"react-error-boundary": "^5.0.0",
|
||||
"react-hook-form": "7.54.2",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-table": "^7.8.0",
|
||||
"recharts": "2.14.1",
|
||||
"recharts": "2.15.1",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"server-only": "^0.0.1",
|
||||
"shiki": "1.24.2",
|
||||
"sonner": "^1.7.1",
|
||||
"shiki": "1.27.0",
|
||||
"sonner": "^1.7.3",
|
||||
"spdx-correct": "^3.2.0",
|
||||
"swagger-ui-react": "^5.18.2",
|
||||
"swagger-ui-react": "^5.18.3",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"thirdweb": "workspace:*",
|
||||
@@ -103,42 +103,42 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chakra-ui/cli": "^2.4.1",
|
||||
"@chromatic-com/storybook": "3.2.2",
|
||||
"@next/bundle-analyzer": "15.1.0",
|
||||
"@next/eslint-plugin-next": "15.1.3",
|
||||
"@playwright/test": "1.49.1",
|
||||
"@storybook/addon-essentials": "8.4.7",
|
||||
"@storybook/addon-interactions": "8.4.7",
|
||||
"@storybook/addon-links": "8.4.7",
|
||||
"@storybook/addon-onboarding": "8.4.7",
|
||||
"@storybook/addon-viewport": "8.4.7",
|
||||
"@storybook/blocks": "8.4.7",
|
||||
"@storybook/nextjs": "8.4.7",
|
||||
"@storybook/react": "8.4.7",
|
||||
"@storybook/test": "8.4.7",
|
||||
"@chromatic-com/storybook": "3.2.4",
|
||||
"@next/bundle-analyzer": "15.1.6",
|
||||
"@next/eslint-plugin-next": "15.1.6",
|
||||
"@playwright/test": "1.50.0",
|
||||
"@storybook/addon-essentials": "8.5.2",
|
||||
"@storybook/addon-interactions": "8.5.2",
|
||||
"@storybook/addon-links": "8.5.2",
|
||||
"@storybook/addon-onboarding": "8.5.2",
|
||||
"@storybook/addon-viewport": "8.5.2",
|
||||
"@storybook/blocks": "8.5.2",
|
||||
"@storybook/nextjs": "8.5.2",
|
||||
"@storybook/react": "8.5.2",
|
||||
"@storybook/test": "8.5.2",
|
||||
"@types/color": "4.2.0",
|
||||
"@types/node": "22.10.2",
|
||||
"@types/node": "22.10.10",
|
||||
"@types/papaparse": "^5.3.15",
|
||||
"@types/pluralize": "^0.0.33",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react": "19.0.2",
|
||||
"@types/react-dom": "19.0.2",
|
||||
"@types/react": "19.0.8",
|
||||
"@types/react-dom": "19.0.3",
|
||||
"@types/react-table": "^7.7.20",
|
||||
"@types/spdx-correct": "^3.1.3",
|
||||
"@types/swagger-ui-react": "^4.18.3",
|
||||
"@types/swagger-ui-react": "^4.19.0",
|
||||
"@typescript-eslint/eslint-plugin": "7.14.1",
|
||||
"@typescript-eslint/parser": "7.14.1",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"checkly": "^4.15.0",
|
||||
"checkly": "^4.19.1",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-config-biome": "1.9.4",
|
||||
"eslint-plugin-react-compiler": "19.0.0-beta-df7b47d-20241124",
|
||||
"eslint-plugin-storybook": "^0.11.1",
|
||||
"knip": "5.41.0",
|
||||
"knip": "5.43.6",
|
||||
"next-sitemap": "^4.2.3",
|
||||
"postcss": "8.4.49",
|
||||
"storybook": "8.4.7",
|
||||
"tailwindcss": "3.4.16",
|
||||
"typescript": "5.7.2"
|
||||
"postcss": "8.5.1",
|
||||
"storybook": "8.5.2",
|
||||
"tailwindcss": "3.4.17",
|
||||
"typescript": "5.7.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
name,description,external_url,background_color,youtube_url,additional,properties,can be,added
|
||||
name,description,external_url,background_color,youtube_url,additional,properties,can be,added
|
||||
Token 0 Name,Token 0 Description,https://thirdweb.com,#0098EE,,every,row,is a ,property
|
||||
|
|
Before Width: | Height: | Size: 881 KiB After Width: | Height: | Size: 881 KiB |
@@ -21,7 +21,7 @@ const legacyDashboardToTeamRedirects = [
|
||||
},
|
||||
{
|
||||
source: "/dashboard/settings/api-keys",
|
||||
destination: "/team/~/~/projects",
|
||||
destination: "/team",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
@@ -160,12 +160,12 @@ async function redirects() {
|
||||
},
|
||||
{
|
||||
source: "/create-api-key",
|
||||
destination: "/team/~/~/projects",
|
||||
destination: "/team",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/settings",
|
||||
destination: "/team/~/~/projects",
|
||||
destination: "/team",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ import type {
|
||||
AnalyticsQueryParams,
|
||||
InAppWalletStats,
|
||||
RpcMethodStats,
|
||||
TransactionStats,
|
||||
UserOpStats,
|
||||
WalletStats,
|
||||
WalletUserStats,
|
||||
@@ -77,7 +78,10 @@ export async function getUserOpUsage(
|
||||
});
|
||||
|
||||
if (res?.status !== 200) {
|
||||
console.error("Failed to fetch user ops usage");
|
||||
const reason = await res?.text();
|
||||
console.error(
|
||||
`Failed to fetch user ops usage: ${res?.status} - ${res.statusText} - ${reason}`,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -85,6 +89,30 @@ export async function getUserOpUsage(
|
||||
return json.data as UserOpStats[];
|
||||
}
|
||||
|
||||
export async function getClientTransactions(
|
||||
params: AnalyticsQueryParams,
|
||||
): Promise<TransactionStats[]> {
|
||||
const searchParams = buildSearchParams(params);
|
||||
const res = await fetchAnalytics(
|
||||
`v1/transactions/client?${searchParams.toString()}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
},
|
||||
);
|
||||
|
||||
if (res?.status !== 200) {
|
||||
const reason = await res?.text();
|
||||
console.error(
|
||||
`Failed to fetch client transactions stats: ${res?.status} - ${res.statusText} - ${reason}`,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
const json = await res.json();
|
||||
return json.data as TransactionStats[];
|
||||
}
|
||||
|
||||
export async function getRpcMethodUsage(
|
||||
params: AnalyticsQueryParams,
|
||||
): Promise<RpcMethodStats[]> {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import "server-only";
|
||||
import { COOKIE_ACTIVE_ACCOUNT, COOKIE_PREFIX_TOKEN } from "@/constants/cookie";
|
||||
import { API_SERVER_URL } from "@/constants/env";
|
||||
import { cookies } from "next/headers";
|
||||
import { getAuthToken } from "../../app/api/lib/getAuthToken";
|
||||
|
||||
export type Project = {
|
||||
id: string;
|
||||
@@ -21,11 +20,7 @@ export type Project = {
|
||||
};
|
||||
|
||||
export async function getProjects(teamSlug: string) {
|
||||
const cookiesManager = await cookies();
|
||||
const activeAccount = cookiesManager.get(COOKIE_ACTIVE_ACCOUNT)?.value;
|
||||
const token = activeAccount
|
||||
? cookiesManager.get(COOKIE_PREFIX_TOKEN + activeAccount)?.value
|
||||
: null;
|
||||
const token = await getAuthToken();
|
||||
|
||||
if (!token) {
|
||||
return [];
|
||||
@@ -46,11 +41,7 @@ export async function getProjects(teamSlug: string) {
|
||||
}
|
||||
|
||||
export async function getProject(teamSlug: string, projectSlug: string) {
|
||||
const cookiesManager = await cookies();
|
||||
const activeAccount = cookiesManager.get(COOKIE_ACTIVE_ACCOUNT)?.value;
|
||||
const token = activeAccount
|
||||
? cookiesManager.get(COOKIE_PREFIX_TOKEN + activeAccount)?.value
|
||||
: null;
|
||||
const token = await getAuthToken();
|
||||
|
||||
if (!token) {
|
||||
return null;
|
||||
|
||||
@@ -25,7 +25,7 @@ export type Team = {
|
||||
billingPlan: "pro" | "growth" | "free" | "starter";
|
||||
billingStatus: "validPayment" | (string & {}) | null;
|
||||
billingEmail: string | null;
|
||||
growthTrialEligible: boolean | null;
|
||||
growthTrialEligible: false;
|
||||
enabledScopes: EnabledTeamScope[];
|
||||
};
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ export function ProjectAvatar(props: {
|
||||
className={cn("rounded-lg border border-border", props.className)}
|
||||
alt={""}
|
||||
fallback={
|
||||
<div className="flex items-center justify-center bg-muted/50">
|
||||
<div className="flex items-center justify-center bg-card">
|
||||
<BoxIcon className="size-[50%] text-muted-foreground" />
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export function DangerSettingCard(props: {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"overflow-hidden rounded-lg border border-red-500/70",
|
||||
"overflow-hidden rounded-lg border border-red-500/70 bg-card",
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -25,7 +25,7 @@ export function Img(props: imgElementProps) {
|
||||
: _status;
|
||||
const { className, fallback, skeleton, ...restProps } = props;
|
||||
const defaultSkeleton = <div className="animate-pulse bg-accent" />;
|
||||
const defaultFallback = <div className="bg-muted" />;
|
||||
const defaultFallback = <div className="bg-accent" />;
|
||||
const imgRef = useRef<HTMLImageElement>(null);
|
||||
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
|
||||
@@ -47,7 +47,7 @@ export function MobileSidebar(props: {
|
||||
const defaultTrigger = (
|
||||
<Button
|
||||
className={cn(
|
||||
"w-full justify-between gap-2 bg-muted/50 text-left lg:hidden",
|
||||
"w-full justify-between gap-2 bg-card text-left lg:hidden",
|
||||
props.triggerClassName,
|
||||
)}
|
||||
variant="outline"
|
||||
|
||||
@@ -16,6 +16,8 @@ type Option = { label: string; value: string };
|
||||
export function MultiNetworkSelector(props: {
|
||||
selectedChainIds: number[];
|
||||
onChange: (chainIds: number[]) => void;
|
||||
disableChainId?: boolean;
|
||||
className?: string;
|
||||
}) {
|
||||
const { allChains, idToChain } = useAllChainsData();
|
||||
|
||||
@@ -53,16 +55,24 @@ export function MultiNetworkSelector(props: {
|
||||
return (
|
||||
<div className="flex justify-between gap-4">
|
||||
<span className="flex grow gap-2 truncate text-left">
|
||||
<ChainIcon
|
||||
className="size-5"
|
||||
ipfsSrc={chain.icon?.url}
|
||||
loading="lazy"
|
||||
/>
|
||||
{cleanChainName(chain.name)}
|
||||
</span>
|
||||
<Badge variant="outline" className="gap-2">
|
||||
<span className="text-muted-foreground">Chain ID</span>
|
||||
{chain.chainId}
|
||||
</Badge>
|
||||
|
||||
{!props.disableChainId && (
|
||||
<Badge variant="outline" className="gap-2">
|
||||
<span className="text-muted-foreground">Chain ID</span>
|
||||
{chain.chainId}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[idToChain],
|
||||
[idToChain, props.disableChainId],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -79,6 +89,7 @@ export function MultiNetworkSelector(props: {
|
||||
disabled={allChains.length === 0}
|
||||
overrideSearchFn={searchFn}
|
||||
renderOption={renderOption}
|
||||
className={props.className}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -90,6 +101,9 @@ export function SingleNetworkSelector(props: {
|
||||
popoverContentClassName?: string;
|
||||
// if specified - only these chains will be shown
|
||||
chainIds?: number[];
|
||||
side?: "left" | "right" | "top" | "bottom";
|
||||
disableChainId?: boolean;
|
||||
align?: "center" | "start" | "end";
|
||||
}) {
|
||||
const { allChains, idToChain } = useAllChainsData();
|
||||
|
||||
@@ -140,16 +154,19 @@ export function SingleNetworkSelector(props: {
|
||||
ipfsSrc={chain.icon?.url}
|
||||
loading="lazy"
|
||||
/>
|
||||
{chain.name}
|
||||
{cleanChainName(chain.name)}
|
||||
</span>
|
||||
<Badge variant="outline" className="gap-2 max-sm:hidden">
|
||||
<span className="text-muted-foreground">Chain ID</span>
|
||||
{chain.chainId}
|
||||
</Badge>
|
||||
|
||||
{!props.disableChainId && (
|
||||
<Badge variant="outline" className="gap-2 max-sm:hidden">
|
||||
<span className="text-muted-foreground">Chain ID</span>
|
||||
{chain.chainId}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[idToChain],
|
||||
[idToChain, props.disableChainId],
|
||||
);
|
||||
|
||||
const isLoadingChains = allChains.length === 0;
|
||||
@@ -162,12 +179,15 @@ export function SingleNetworkSelector(props: {
|
||||
onValueChange={(chainId) => {
|
||||
props.onChange(Number(chainId));
|
||||
}}
|
||||
closeOnSelect={true}
|
||||
placeholder={isLoadingChains ? "Loading Chains..." : "Select Chain"}
|
||||
overrideSearchFn={searchFn}
|
||||
renderOption={renderOption}
|
||||
className={props.className}
|
||||
popoverContentClassName={props.popoverContentClassName}
|
||||
disabled={isLoadingChains}
|
||||
side={props.side}
|
||||
align={props.align}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export function SettingsCard(
|
||||
}>,
|
||||
) {
|
||||
return (
|
||||
<div className="relative rounded-lg border border-border bg-muted/50">
|
||||
<div className="relative rounded-lg border border-border bg-card">
|
||||
<div
|
||||
className={cn(
|
||||
"relative border-border border-b px-4 py-6 lg:px-6",
|
||||
|
||||
@@ -67,7 +67,7 @@ export function RenderSidebarLinks(props: { links: SidebarLink[] }) {
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: items won't be reordered
|
||||
key={i}
|
||||
href={link.href}
|
||||
className="flex items-center gap-2 rounded-md px-3 py-2 text-muted-foreground text-sm hover:bg-muted"
|
||||
className="flex items-center gap-2 rounded-md px-3 py-2 text-muted-foreground text-sm hover:bg-accent"
|
||||
activeClassName="text-foreground"
|
||||
exactMatch={link.exactMatch}
|
||||
>
|
||||
|
||||
@@ -19,7 +19,7 @@ export function AppFooter(props: AppFooterProps) {
|
||||
return (
|
||||
<footer
|
||||
className={cn(
|
||||
"w-full border-border border-t bg-muted/50 py-6 md:py-8",
|
||||
"w-full border-border border-t bg-card py-6 md:py-8",
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
@@ -28,7 +28,9 @@ export function AppFooter(props: AppFooterProps) {
|
||||
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<ThirdwebMiniLogo className="h-7" />
|
||||
<p className="text-muted-foreground text-sm">© 2024 thirdweb</p>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
© {new Date().getFullYear()} thirdweb
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-row gap-3">
|
||||
<Button size="icon" variant="ghost" asChild className="size-9">
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
EmptyChartState,
|
||||
LoadingChartState,
|
||||
} from "../../../../components/analytics/empty-chart-state";
|
||||
import { cn } from "../../../lib/utils";
|
||||
|
||||
type ThirdwebBarChartProps<TConfig extends ChartConfig> = {
|
||||
// metadata
|
||||
@@ -36,6 +37,9 @@ type ThirdwebBarChartProps<TConfig extends ChartConfig> = {
|
||||
// chart className
|
||||
chartClassName?: string;
|
||||
isPending: boolean;
|
||||
toolTipLabelFormatter?: (label: string, payload: unknown) => React.ReactNode;
|
||||
hideLabel?: boolean;
|
||||
titleClassName?: string;
|
||||
};
|
||||
|
||||
export function ThirdwebBarChart<TConfig extends ChartConfig>(
|
||||
@@ -45,10 +49,13 @@ export function ThirdwebBarChart<TConfig extends ChartConfig>(
|
||||
// if there are more than 4 keys then we should stack them by default
|
||||
const variant =
|
||||
props.variant || configKeys.length > 4 ? "stacked" : "grouped";
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="mb-2">{props.title}</CardTitle>
|
||||
<CardTitle className={cn("mb-2", props.titleClassName)}>
|
||||
{props.title}
|
||||
</CardTitle>
|
||||
{props.description && (
|
||||
<CardDescription>{props.description}</CardDescription>
|
||||
)}
|
||||
@@ -69,7 +76,16 @@ export function ThirdwebBarChart<TConfig extends ChartConfig>(
|
||||
tickMargin={10}
|
||||
tickFormatter={(value) => formatDate(new Date(value), "MMM d")}
|
||||
/>
|
||||
<ChartTooltip content={<ChartTooltipContent hideLabel />} />
|
||||
<ChartTooltip
|
||||
content={
|
||||
<ChartTooltipContent
|
||||
hideLabel={
|
||||
props.hideLabel !== undefined ? props.hideLabel : true
|
||||
}
|
||||
labelFormatter={props.toolTipLabelFormatter}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{props.showLegend && (
|
||||
<ChartLegend content={<ChartLegendContent />} />
|
||||
)}
|
||||
@@ -92,6 +108,8 @@ export function ThirdwebBarChart<TConfig extends ChartConfig>(
|
||||
: [0, 0, 0, 0]
|
||||
: [4, 4, 4, 4]
|
||||
}
|
||||
strokeWidth={1}
|
||||
className="stroke-background"
|
||||
/>
|
||||
))}
|
||||
</BarChart>
|
||||
|
||||
@@ -23,7 +23,7 @@ export function UnexpectedValueErrorMessage(props: {
|
||||
{stringifiedValue && (
|
||||
<div className="mt-3">
|
||||
<p className="mb-0.5 text-muted-foreground text-sm">Value Received</p>
|
||||
<ScrollShadow className="rounded-lg bg-muted/50">
|
||||
<ScrollShadow className="rounded-lg bg-card">
|
||||
<code className="block whitespace-pre p-4 font-mono text-xs">
|
||||
{stringifiedValue}
|
||||
</code>
|
||||
|
||||
@@ -202,7 +202,7 @@ export const MultiSelect = forwardRef<HTMLButtonElement, MultiSelectProps>(
|
||||
event.stopPropagation();
|
||||
handleClear();
|
||||
}}
|
||||
className="rounded p-1 hover:bg-muted"
|
||||
className="rounded p-1 hover:bg-accent"
|
||||
>
|
||||
<XIcon className="h-4 cursor-pointer text-muted-foreground" />
|
||||
</div>
|
||||
|
||||
@@ -57,8 +57,9 @@ export const PricingCard: React.FC<PricingCardProps> = ({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"z-[999] flex w-full flex-col gap-6 rounded-xl border border-border bg-muted/50 p-4 md:p-6",
|
||||
"z-10 flex w-full flex-col gap-6 rounded-xl border border-border bg-card p-4 md:p-6",
|
||||
current && "border-blue-500",
|
||||
highlighted && "border-active-border",
|
||||
)}
|
||||
style={
|
||||
highlighted
|
||||
|
||||
@@ -31,6 +31,9 @@ interface SelectWithSearchProps
|
||||
) => boolean;
|
||||
renderOption?: (option: { value: string; label: string }) => React.ReactNode;
|
||||
popoverContentClassName?: string;
|
||||
side?: "left" | "right" | "top" | "bottom";
|
||||
align?: "center" | "start" | "end";
|
||||
closeOnSelect?: boolean;
|
||||
}
|
||||
|
||||
export const SelectWithSearch = React.forwardRef<
|
||||
@@ -48,6 +51,7 @@ export const SelectWithSearch = React.forwardRef<
|
||||
overrideSearchFn,
|
||||
popoverContentClassName,
|
||||
searchPlaceholder,
|
||||
closeOnSelect,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
@@ -107,7 +111,7 @@ export const SelectWithSearch = React.forwardRef<
|
||||
}, [searchValue]);
|
||||
|
||||
return (
|
||||
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
|
||||
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen} modal>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
ref={ref}
|
||||
@@ -118,23 +122,24 @@ export const SelectWithSearch = React.forwardRef<
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div className="flex w-full items-center justify-between gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
"text-muted-foreground text-sm",
|
||||
"truncate text-muted-foreground text-sm",
|
||||
selectedOption && "text-foreground",
|
||||
)}
|
||||
>
|
||||
{selectedOption?.label || placeholder}
|
||||
</span>
|
||||
<ChevronDown className="h-4 cursor-pointer text-muted-foreground" />
|
||||
<ChevronDown className="size-4 cursor-pointer text-muted-foreground" />
|
||||
</div>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
|
||||
<PopoverContent
|
||||
className={cn("z-[10001] p-0", popoverContentClassName)}
|
||||
align="center"
|
||||
align={props.align || "center"}
|
||||
side={props.side}
|
||||
sideOffset={10}
|
||||
onEscapeKeyDown={() => setIsPopoverOpen(false)}
|
||||
style={{
|
||||
@@ -174,7 +179,12 @@ export const SelectWithSearch = React.forwardRef<
|
||||
key={option.value}
|
||||
role="option"
|
||||
aria-selected={isSelected}
|
||||
onClick={() => onValueChange(option.value)}
|
||||
onClick={() => {
|
||||
onValueChange(option.value);
|
||||
if (closeOnSelect) {
|
||||
setIsPopoverOpen(false);
|
||||
}
|
||||
}}
|
||||
variant="ghost"
|
||||
className="flex w-full cursor-pointer justify-start gap-3 rounded-sm px-3 py-2 text-left"
|
||||
ref={
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
import { useThirdwebClient } from "@/constants/thirdweb.client";
|
||||
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
|
||||
import { useClipboard } from "hooks/useClipboard";
|
||||
import { Check, Copy, ExternalLinkIcon } from "lucide-react";
|
||||
import { Check, Copy } from "lucide-react";
|
||||
import { useMemo } from "react";
|
||||
import { type ThirdwebClient, isAddress } from "thirdweb";
|
||||
import { ZERO_ADDRESS } from "thirdweb";
|
||||
@@ -148,21 +148,6 @@ export function WalletAddress(props: {
|
||||
);
|
||||
})
|
||||
)}
|
||||
<Button
|
||||
asChild
|
||||
variant="upsell"
|
||||
className="flex flex-row items-center gap-2 text-sm"
|
||||
size="sm"
|
||||
>
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://blog.thirdweb.com/changelog/introducing-the-social-sdk?ref=dashboard-social-wallet"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Learn more
|
||||
<ExternalLinkIcon className="size-4" />
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { useState } from "react";
|
||||
import { BadgeContainer, mobileViewport } from "../../stories/utils";
|
||||
import { PaginationButtons } from "./pagination-buttons";
|
||||
|
||||
const meta = {
|
||||
title: "blocks/PaginationButtons",
|
||||
component: Story,
|
||||
parameters: {
|
||||
nextjs: {
|
||||
appDirectory: true,
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof Story>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Desktop: Story = {
|
||||
args: {},
|
||||
};
|
||||
|
||||
export const Mobile: Story = {
|
||||
args: {},
|
||||
parameters: {
|
||||
viewport: mobileViewport("iphone14"),
|
||||
},
|
||||
};
|
||||
|
||||
function Story() {
|
||||
return (
|
||||
<div className="container flex max-w-[1000px] flex-col gap-8 py-10">
|
||||
<Variant label="10 Pages" totalPages={10} />
|
||||
<Variant label="100 Pages" totalPages={100} />
|
||||
<Variant label="3 Pages" totalPages={2} />
|
||||
<Variant label="6 Pages" totalPages={6} />
|
||||
<Variant label="1 Page - nothing rendered" totalPages={1} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Variant(props: {
|
||||
label: string;
|
||||
totalPages: number;
|
||||
}) {
|
||||
const [activePage, setActivePage] = useState(1);
|
||||
return (
|
||||
<BadgeContainer label={props.label}>
|
||||
<div className="border bg-card py-5">
|
||||
<PaginationButtons
|
||||
activePage={activePage}
|
||||
onPageClick={setActivePage}
|
||||
totalPages={props.totalPages}
|
||||
/>
|
||||
</div>
|
||||
</BadgeContainer>
|
||||
);
|
||||
}
|
||||
@@ -9,6 +9,11 @@ import {
|
||||
PaginationNext,
|
||||
PaginationPrevious,
|
||||
} from "@/components/ui/pagination";
|
||||
import { ArrowUpRightIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { cn } from "../lib/utils";
|
||||
import { Button } from "./ui/button";
|
||||
import { Input } from "./ui/input";
|
||||
|
||||
export const PaginationButtons = (props: {
|
||||
activePage: number;
|
||||
@@ -16,6 +21,47 @@ export const PaginationButtons = (props: {
|
||||
onPageClick: (page: number) => void;
|
||||
}) => {
|
||||
const { activePage, totalPages, onPageClick: setPage } = props;
|
||||
const [inputHasError, setInputHasError] = useState(false);
|
||||
const [pageNumberInput, setPageNumberInput] = useState("");
|
||||
|
||||
if (totalPages === 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function handlePageInputSubmit() {
|
||||
const page = Number(pageNumberInput);
|
||||
|
||||
setInputHasError(false);
|
||||
if (Number.isInteger(page) && page > 0 && page <= totalPages) {
|
||||
setPage(page);
|
||||
setPageNumberInput("");
|
||||
} else {
|
||||
setInputHasError(true);
|
||||
}
|
||||
}
|
||||
|
||||
// just render all the page buttons directly
|
||||
if (totalPages <= 6) {
|
||||
const pages = [...Array(totalPages)].map((_, i) => i + 1);
|
||||
return (
|
||||
<Pagination>
|
||||
<PaginationContent>
|
||||
{pages.map((page) => (
|
||||
<PaginationItem key={page}>
|
||||
<PaginationLink
|
||||
isActive={activePage === page}
|
||||
onClick={() => {
|
||||
setPage(page);
|
||||
}}
|
||||
>
|
||||
{page}
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
))}
|
||||
</PaginationContent>
|
||||
</Pagination>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Pagination>
|
||||
@@ -28,24 +74,28 @@ export const PaginationButtons = (props: {
|
||||
}}
|
||||
/>
|
||||
</PaginationItem>
|
||||
|
||||
{/* First page + ... */}
|
||||
{activePage - 3 > 0 && (
|
||||
<PaginationItem className="max-sm:hidden">
|
||||
<PaginationEllipsis />
|
||||
</PaginationItem>
|
||||
)}
|
||||
{activePage - 2 > 0 && (
|
||||
<PaginationItem className="max-sm:hidden">
|
||||
<PaginationLink
|
||||
onClick={() => {
|
||||
setPage(activePage - 2);
|
||||
}}
|
||||
>
|
||||
{activePage - 2}
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
<>
|
||||
<PaginationItem>
|
||||
<PaginationLink
|
||||
onClick={() => {
|
||||
setPage(1);
|
||||
}}
|
||||
>
|
||||
1
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
|
||||
<PaginationItem>
|
||||
<PaginationEllipsis className="max-sm:w-3" />
|
||||
</PaginationItem>
|
||||
</>
|
||||
)}
|
||||
|
||||
{activePage - 1 > 0 && (
|
||||
<PaginationItem>
|
||||
<PaginationItem className="max-sm:hidden">
|
||||
<PaginationLink
|
||||
onClick={() => {
|
||||
setPage(activePage - 1);
|
||||
@@ -55,11 +105,13 @@ export const PaginationButtons = (props: {
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
)}
|
||||
|
||||
<PaginationItem>
|
||||
<PaginationLink isActive>{activePage}</PaginationLink>
|
||||
</PaginationItem>
|
||||
|
||||
{activePage + 1 <= totalPages && (
|
||||
<PaginationItem>
|
||||
<PaginationItem className="max-sm:hidden">
|
||||
<PaginationLink
|
||||
onClick={() => {
|
||||
setPage(activePage + 1);
|
||||
@@ -69,22 +121,26 @@ export const PaginationButtons = (props: {
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
)}
|
||||
{activePage + 2 <= totalPages && (
|
||||
<PaginationItem className="max-sm:hidden">
|
||||
<PaginationLink
|
||||
onClick={() => {
|
||||
setPage(activePage + 2);
|
||||
}}
|
||||
>
|
||||
{activePage + 2}
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
)}
|
||||
|
||||
{/* ... + Last page */}
|
||||
{activePage + 3 <= totalPages && (
|
||||
<PaginationItem className="max-sm:hidden">
|
||||
<PaginationEllipsis />
|
||||
</PaginationItem>
|
||||
<>
|
||||
<PaginationItem>
|
||||
<PaginationEllipsis className="max-sm:w-3" />
|
||||
</PaginationItem>
|
||||
|
||||
<PaginationItem>
|
||||
<PaginationLink
|
||||
onClick={() => {
|
||||
setPage(totalPages);
|
||||
}}
|
||||
>
|
||||
{totalPages}
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
</>
|
||||
)}
|
||||
|
||||
<PaginationItem>
|
||||
<PaginationNext
|
||||
disabled={activePage === totalPages}
|
||||
@@ -93,6 +149,34 @@ export const PaginationButtons = (props: {
|
||||
}}
|
||||
/>
|
||||
</PaginationItem>
|
||||
|
||||
<div className="relative flex items-center">
|
||||
<Input
|
||||
value={pageNumberInput}
|
||||
onChange={(e) => {
|
||||
setInputHasError(false);
|
||||
setPageNumberInput(e.target.value);
|
||||
}}
|
||||
type="number"
|
||||
placeholder="Page"
|
||||
className={cn(
|
||||
"w-[60px] bg-transparent [appearance:textfield] max-sm:placeholder:text-sm lg:w-[100px] lg:pr-8 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none",
|
||||
inputHasError && "text-red-500",
|
||||
)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
handlePageInputSubmit();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="absolute right-1 h-auto w-auto p-2 max-sm:hidden"
|
||||
onClick={handlePageInputSubmit}
|
||||
>
|
||||
<ArrowUpRightIcon className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</PaginationContent>
|
||||
</Pagination>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { ToolTipLabel } from "./tooltip";
|
||||
export function CopyTextButton(props: {
|
||||
textToShow: string;
|
||||
textToCopy: string;
|
||||
tooltip: string;
|
||||
tooltip: string | undefined;
|
||||
className?: string;
|
||||
iconClassName?: string;
|
||||
variant?:
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as React from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const alertVariants = cva(
|
||||
"dark:bg-muted/50 relative w-full rounded-lg border border-border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
|
||||
"bg-card relative w-full rounded-lg border border-border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
|
||||
@@ -186,7 +186,7 @@ const ChartTooltipContent = React.forwardRef<
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
|
||||
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-popover px-2.5 py-1.5 text-xs shadow-xl",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -21,7 +21,7 @@ export function CodeBlockContainer(props: {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"group relative max-w-full overflow-hidden rounded-lg border border-border bg-background",
|
||||
"group relative max-w-full overflow-hidden rounded-lg border border-border bg-card",
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -33,7 +33,7 @@ const ImageUpload = React.forwardRef<HTMLInputElement, ImageUploadProps>(
|
||||
<div
|
||||
{...getRootProps()}
|
||||
className={cn(
|
||||
"group relative flex cursor-pointer flex-col items-center justify-center gap-1.5 overflow-hidden rounded-md border bg-card p-4 py-6 text-muted-foreground transition-all hover:border-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"group relative flex cursor-pointer flex-col items-center justify-center gap-1.5 overflow-hidden rounded-md border bg-background p-4 py-6 text-muted-foreground transition-all hover:border-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
isDragActive ? "border-primary" : "border-input",
|
||||
!activeFile ? "w-full" : "min-h-32 min-w-32",
|
||||
className,
|
||||
|
||||
@@ -11,7 +11,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-10 w-full rounded-md border border-input bg-card px-3 py-2 text-base ring-offset-background selection:bg-foreground selection:text-background file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background selection:bg-foreground selection:text-background file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
|
||||
@@ -63,11 +63,11 @@ const PaginationPrevious = ({
|
||||
<PaginationLink
|
||||
aria-label="Go to previous page"
|
||||
size="default"
|
||||
className={cn("gap-1 pl-2.5", className)}
|
||||
className={cn("h-10 gap-1 pl-2.5", className)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
<span>Previous</span>
|
||||
<span className="max-sm:hidden">Previous</span>
|
||||
</PaginationLink>
|
||||
);
|
||||
PaginationPrevious.displayName = "PaginationPrevious";
|
||||
@@ -79,10 +79,10 @@ const PaginationNext = ({
|
||||
<PaginationLink
|
||||
aria-label="Go to next page"
|
||||
size="default"
|
||||
className={cn("gap-1 pr-2.5", className)}
|
||||
className={cn("h-10 gap-1 pr-2.5", className)}
|
||||
{...props}
|
||||
>
|
||||
<span>Next</span>
|
||||
<span className="max-sm:hidden">Next</span>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</PaginationLink>
|
||||
);
|
||||
|
||||
@@ -51,7 +51,7 @@ const RadioGroupItemButton = React.forwardRef<
|
||||
<RadioGroupPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"group flex min-w-32 cursor-pointer items-center space-x-3 space-y-0 rounded-lg border px-4 py-3 font-medium transition-all hover:border-foreground/25 hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 peer-hover:border-nonce data-[state=checked]:border-muted-foreground",
|
||||
"group flex min-w-32 cursor-pointer items-center space-x-3 space-y-0 rounded-lg border px-4 py-3 font-medium transition-all hover:border-foreground/25 hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 peer-hover:border-nonce data-[state=checked]:border-muted-foreground",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -21,7 +21,7 @@ const SelectTrigger = React.forwardRef<
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-10 w-full items-center justify-between gap-2 rounded-md border border-input bg-card px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||
"flex h-10 w-full items-center justify-between gap-2 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -114,7 +114,9 @@ function TableDemo(props: {
|
||||
<TableRow
|
||||
key={invoice.invoice}
|
||||
linkBox={props.linkBox}
|
||||
className={cn(props.linkBox && "cursor-pointer hover:bg-muted")}
|
||||
className={cn(
|
||||
props.linkBox && "cursor-pointer hover:bg-accent/50",
|
||||
)}
|
||||
>
|
||||
<TableCell className="font-medium">
|
||||
<Link
|
||||
|
||||
@@ -25,7 +25,8 @@ const TableHeader = React.forwardRef<
|
||||
<thead
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-border border-b bg-muted/50 [&_tr]:border-b",
|
||||
"border-border border-b bg-background",
|
||||
"sticky top-0 z-20 border-none before:absolute before:inset-0 before:border-border before:border-b",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -53,7 +54,7 @@ const TableFooter = React.forwardRef<
|
||||
<tfoot
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-border border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
||||
"border-border border-t bg-card font-medium [&>tr]:last:border-b-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -125,10 +126,20 @@ const TableCaption = React.forwardRef<
|
||||
));
|
||||
TableCaption.displayName = "TableCaption";
|
||||
|
||||
function TableContainer(props: { children: React.ReactNode }) {
|
||||
function TableContainer(props: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
scrollableContainerClassName?: string;
|
||||
}) {
|
||||
return (
|
||||
<ScrollShadow
|
||||
className="relative whitespace-nowrap rounded-lg border border-border"
|
||||
shadowClassName="z-30"
|
||||
disableTopShadow
|
||||
className={cn(
|
||||
"relative whitespace-nowrap rounded-lg border border-border bg-card",
|
||||
props.className,
|
||||
)}
|
||||
scrollableClassName={props.scrollableContainerClassName}
|
||||
shadowColor="hsl(var(--muted))"
|
||||
>
|
||||
{props.children}
|
||||
|
||||
@@ -51,7 +51,7 @@ export function TabLinks(props: {
|
||||
href={tab.href}
|
||||
aria-disabled={tab.isDisabled}
|
||||
className={cn(
|
||||
"relative h-auto rounded-lg px-3 font-normal text-muted-foreground text-sm hover:bg-muted lg:text-sm",
|
||||
"relative h-auto rounded-lg px-3 font-normal text-muted-foreground text-sm hover:bg-accent lg:text-sm",
|
||||
!tab.isActive && !tab.isDisabled && "hover:text-foreground",
|
||||
tab.isDisabled && "pointer-events-none",
|
||||
tab.isActive && "!text-foreground",
|
||||
|
||||
@@ -10,7 +10,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
"flex min-h-[80px] w-full rounded-md border border-input bg-card px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
:root,
|
||||
[data-theme="light"] {
|
||||
/* bg - neutral */
|
||||
--background: 0 0% 100%;
|
||||
--background: 0 0% 98%;
|
||||
--popover: 0 0% 100%;
|
||||
--card: 0 0% 100%;
|
||||
--secondary: 0 0% 92%;
|
||||
--muted: 0 0% 95%;
|
||||
--accent: 0 0% 95%;
|
||||
--secondary: 0 0% 90%;
|
||||
--muted: 0 0% 93%;
|
||||
--accent: 0 0% 93%;
|
||||
--inverted: 0 0% 4%;
|
||||
|
||||
/* bg - colorful */
|
||||
@@ -34,9 +34,10 @@
|
||||
--destructive-text: 357.15deg 100% 68.72%;
|
||||
|
||||
/* Borders */
|
||||
--border: 0 0% 80%;
|
||||
--input: 0 0% 80%;
|
||||
--ring: 0 0% 85%;
|
||||
--border: 0 0% 85%;
|
||||
--active-border: 0 0% 70%;
|
||||
--input: 0 0% 85%;
|
||||
--ring: 0 0% 80%;
|
||||
|
||||
/* Others */
|
||||
--radius: 0.5rem;
|
||||
@@ -46,7 +47,7 @@
|
||||
[data-theme="dark"] {
|
||||
/* bg - neutral */
|
||||
--background: 0 0% 0%;
|
||||
--card: 0 0% 0%;
|
||||
--card: 0 0% 3.92%;
|
||||
--popover: 0 0% 0%;
|
||||
--secondary: 0 0% 11%;
|
||||
--muted: 0 0% 11%;
|
||||
@@ -74,6 +75,7 @@
|
||||
|
||||
/* Borders */
|
||||
--border: 0 0% 15%;
|
||||
--active-border: 0 0% 22%;
|
||||
--ring: 0 0% 30%;
|
||||
--input: 0 0% 15%;
|
||||
}
|
||||
@@ -159,13 +161,12 @@ input:-webkit-autofill:active {
|
||||
|
||||
.shiki,
|
||||
.shiki span {
|
||||
background-color: var(--background) !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.dark .shiki,
|
||||
.dark .shiki span {
|
||||
color: var(--shiki-dark) !important;
|
||||
background-color: var(--background) !important;
|
||||
/* Optional, if you also want font styles */
|
||||
font-style: var(--shiki-dark-font-style) !important;
|
||||
font-weight: var(--shiki-dark-font-weight) !important;
|
||||
|
||||
@@ -76,8 +76,8 @@ export const engineKeys = {
|
||||
[...engineKeys.all, address, "instances"] as const,
|
||||
backendWallets: (instance: string) =>
|
||||
[...engineKeys.all, instance, "backendWallets"] as const,
|
||||
transactions: (instance: string) =>
|
||||
[...engineKeys.all, instance, "transactions"] as const,
|
||||
transactions: (instance: string, params: object) =>
|
||||
[...engineKeys.all, instance, "transactions", params] as const,
|
||||
permissions: (instance: string) =>
|
||||
[...engineKeys.all, instance, "permissions"] as const,
|
||||
accessTokens: (instance: string) =>
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { apiServerProxy } from "@/actions/proxies";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
keepPreviousData,
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from "@tanstack/react-query";
|
||||
import type { ResultItem } from "app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/metrics/components/StatusCodes";
|
||||
import type { EngineBackendWalletType } from "lib/engine";
|
||||
import { useState } from "react";
|
||||
import { useActiveAccount, useActiveWalletChain } from "thirdweb/react";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import invariant from "tiny-invariant";
|
||||
import type { EngineStatus } from "../../../app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/transactions-table";
|
||||
import { engineKeys } from "../cache-keys";
|
||||
|
||||
export type EngineTier = "STARTER" | "PREMIUM" | "ENTERPRISE";
|
||||
@@ -47,45 +53,6 @@ const getEngineRequestHeaders = (token: string | null): HeadersInit => {
|
||||
};
|
||||
};
|
||||
|
||||
// TODO - add authToken in queryKey instead
|
||||
export function useEngineInstances() {
|
||||
const address = useActiveAccount()?.address;
|
||||
return useQuery({
|
||||
queryKey: engineKeys.instances(address || ""),
|
||||
queryFn: async (): Promise<EngineInstance[]> => {
|
||||
type Result = {
|
||||
data?: {
|
||||
instances: EngineInstance[];
|
||||
};
|
||||
};
|
||||
|
||||
const res = await apiServerProxy<Result>({
|
||||
pathname: "/v1/engine",
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(res.error);
|
||||
}
|
||||
|
||||
const json = res.data;
|
||||
const instances = json.data?.instances || [];
|
||||
|
||||
return instances.map((instance) => {
|
||||
// Sanitize: Add trailing slash if not present.
|
||||
const url = instance.url.endsWith("/")
|
||||
? instance.url
|
||||
: `${instance.url}/`;
|
||||
return {
|
||||
...instance,
|
||||
url,
|
||||
};
|
||||
});
|
||||
},
|
||||
enabled: !!address,
|
||||
});
|
||||
}
|
||||
|
||||
// GET Requests
|
||||
export type BackendWallet = {
|
||||
address: string;
|
||||
@@ -106,7 +73,7 @@ export function useEngineBackendWallets(params: {
|
||||
}) {
|
||||
const { instanceUrl, authToken } = params;
|
||||
return useQuery({
|
||||
queryKey: [engineKeys.backendWallets(instanceUrl), authToken],
|
||||
queryKey: [...engineKeys.backendWallets(instanceUrl), authToken],
|
||||
queryFn: async () => {
|
||||
const res = await fetch(`${instanceUrl}backend-wallet/get-all?limit=50`, {
|
||||
method: "GET",
|
||||
@@ -422,13 +389,29 @@ export function useEngineTransactions(params: {
|
||||
instanceUrl: string;
|
||||
autoUpdate: boolean;
|
||||
authToken: string;
|
||||
queryParams?: {
|
||||
limit?: number;
|
||||
page?: number;
|
||||
status?: EngineStatus;
|
||||
};
|
||||
}) {
|
||||
const { instanceUrl, autoUpdate, authToken } = params;
|
||||
|
||||
return useQuery({
|
||||
queryKey: engineKeys.transactions(instanceUrl),
|
||||
queryKey: engineKeys.transactions(instanceUrl, params),
|
||||
queryFn: async () => {
|
||||
const res = await fetch(`${instanceUrl}transaction/get-all`, {
|
||||
const url = new URL(`${instanceUrl}transaction/get-all`);
|
||||
if (params.queryParams) {
|
||||
for (const key in params.queryParams) {
|
||||
const value =
|
||||
params.queryParams[key as keyof typeof params.queryParams];
|
||||
if (value !== undefined) {
|
||||
url.searchParams.append(key, value.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: getEngineRequestHeaders(authToken),
|
||||
});
|
||||
@@ -437,8 +420,8 @@ export function useEngineTransactions(params: {
|
||||
|
||||
return (json.result as TransactionResponse) || {};
|
||||
},
|
||||
enabled: !!instanceUrl,
|
||||
refetchInterval: autoUpdate ? 4_000 : false,
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -486,12 +469,12 @@ export function useEngineBackendWalletBalance(params: {
|
||||
instanceUrl: string;
|
||||
address: string;
|
||||
authToken: string;
|
||||
chainId: number;
|
||||
}) {
|
||||
const { instanceUrl, address, authToken } = params;
|
||||
const chainId = useActiveWalletChain()?.id;
|
||||
const { instanceUrl, address, authToken, chainId } = params;
|
||||
|
||||
return useQuery({
|
||||
queryKey: engineKeys.backendWalletBalance(address, chainId || 1),
|
||||
queryKey: engineKeys.backendWalletBalance(address, chainId),
|
||||
queryFn: async () => {
|
||||
invariant(chainId, "chainId is required");
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ export function BuyFundsSection(props: { chain: ChainMetadata }) {
|
||||
return (
|
||||
<section>
|
||||
<SectionTitle title="Buy Funds" />
|
||||
<div className="flex justify-center rounded-lg border border-border bg-muted/30 px-4 py-10">
|
||||
<div className="flex justify-center rounded-lg border border-border bg-card px-4 py-10">
|
||||
<div className="flex max-w-[520px] flex-col items-center ">
|
||||
<div className="flex items-center">
|
||||
<ChainIcon
|
||||
|
||||
@@ -11,7 +11,7 @@ export function ChainOverviewSection(props: { chain: ChainMetadata }) {
|
||||
return (
|
||||
<section>
|
||||
<SectionTitle title="Chain Overview" />
|
||||
<div className="grid grid-cols-1 gap-6 rounded-lg border bg-muted/50 p-4 md:grid-cols-2 md:p-6 lg:grid-cols-3 lg:gap-8">
|
||||
<div className="grid grid-cols-1 gap-6 rounded-lg border bg-card p-4 md:grid-cols-2 md:p-6 lg:grid-cols-3 lg:gap-8">
|
||||
{/* Info */}
|
||||
{chain.infoURL && (
|
||||
<PrimaryInfoItem title="Info">
|
||||
|
||||
@@ -7,7 +7,7 @@ export function ClaimChainSection() {
|
||||
return (
|
||||
<section>
|
||||
<SectionTitle title="Claim Chain" />
|
||||
<div className="rounded-lg border bg-muted/50 p-4">
|
||||
<div className="rounded-lg border bg-card p-4">
|
||||
<p className="mb-1"> This chain page is unclaimed </p>
|
||||
<p className="mb-5 text-muted-foreground text-sm">
|
||||
Are you the owner of this ecosystem? Claim this page to add additional
|
||||
|
||||
@@ -19,7 +19,7 @@ export async function FaucetSection(props: {
|
||||
return (
|
||||
<section>
|
||||
<SectionTitle title="Faucet" />
|
||||
<div className="flex justify-center rounded-lg border border-border bg-muted/50 px-4 py-10">
|
||||
<div className="flex justify-center rounded-lg border border-border bg-card px-4 py-10">
|
||||
<div className="flex w-full max-w-[520px] flex-col items-center ">
|
||||
<div className="flex items-center">
|
||||
<ChainIcon
|
||||
|
||||
@@ -25,7 +25,7 @@ export function SupportedProductsSection(props: {
|
||||
return (
|
||||
<div
|
||||
key={product.id}
|
||||
className="relative flex gap-3 rounded-lg border bg-muted/50 p-4 pr-8 transition-colors duration-300 hover:bg-muted"
|
||||
className="relative flex gap-3 rounded-lg border bg-card p-4 pr-8 transition-colors hover:border-active-border"
|
||||
>
|
||||
<CircleCheckIcon className="absolute top-4 right-4 size-5 text-success-text" />
|
||||
<product.icon className="mt-0.5 size-5 shrink-0" />
|
||||
|
||||
@@ -14,7 +14,7 @@ export function ExplorersSection(props: {
|
||||
return (
|
||||
<div
|
||||
key={explorer.url}
|
||||
className="relative rounded-xl border bg-muted/50 p-4 hover:bg-muted"
|
||||
className="relative rounded-xl border bg-card p-4 transition-colors hover:border-active-border"
|
||||
>
|
||||
<ExternalLinkIcon className="absolute top-4 right-4 size-4 text-muted-foreground" />
|
||||
<h3 className="mb-1 font-semibold text-base capitalize">
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { MobileSidebar } from "@/components/blocks/MobileSidebar";
|
||||
import { Sidebar } from "@/components/blocks/Sidebar";
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
@@ -15,12 +13,7 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
ChevronDownIcon,
|
||||
MenuIcon,
|
||||
TicketCheckIcon,
|
||||
} from "lucide-react";
|
||||
import { ChevronDownIcon, TicketCheckIcon } from "lucide-react";
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
@@ -71,18 +64,6 @@ export default async function ChainPageLayout(props: {
|
||||
|
||||
const chainMetadata = await getChainMetadata(chain.chainId);
|
||||
|
||||
const sidebarLinks = [
|
||||
{
|
||||
href: `/${chain.slug}`,
|
||||
label: "Overview",
|
||||
exactMatch: true,
|
||||
},
|
||||
{
|
||||
href: `/${chain.slug}/popular`,
|
||||
label: "Popular Contracts",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex h-14 border-border border-b pl-7">
|
||||
@@ -125,9 +106,7 @@ export default async function ChainPageLayout(props: {
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
<div className="container flex h-full flex-row">
|
||||
<Sidebar links={sidebarLinks} />
|
||||
|
||||
<div className="container flex h-full max-w-[1180px] flex-row">
|
||||
<div className="flex w-full flex-col pb-10">
|
||||
{/* Icon + Background */}
|
||||
<ChainHeader
|
||||
@@ -136,28 +115,6 @@ export default async function ChainPageLayout(props: {
|
||||
chain={chain}
|
||||
/>
|
||||
|
||||
<div className="flex justify-end lg:hidden">
|
||||
<MobileSidebar
|
||||
links={sidebarLinks}
|
||||
trigger={
|
||||
<Button size="icon" variant="outline">
|
||||
<MenuIcon strokeWidth={1} />
|
||||
</Button>
|
||||
}
|
||||
footer={
|
||||
<div className="mt-5 border-t pt-6 pb-2">
|
||||
<Link
|
||||
href="/chainlist"
|
||||
className="flex items-center justify-start gap-2 px-2 text-muted-foreground text-sm hover:text-foreground"
|
||||
>
|
||||
<ArrowLeftIcon className="size-4" />
|
||||
View all chains
|
||||
</Link>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="h-4 md:h-8" />
|
||||
|
||||
<div className="flex flex-col gap-3 md:gap-2">
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { fetchTopContracts } from "lib/search";
|
||||
import { ArrowRightIcon, CircleAlertIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { CircleAlertIcon } from "lucide-react";
|
||||
import { getRawAccount } from "../../../../account/settings/getAccount";
|
||||
import { TrendingContractSection } from "../../../trending/components/trending-table";
|
||||
import { getChain, getChainMetadata } from "../../utils";
|
||||
import { BuyFundsSection } from "./components/server/BuyFundsSection";
|
||||
import { ChainOverviewSection } from "./components/server/ChainOverviewSection";
|
||||
@@ -21,14 +17,7 @@ export default async function Page(props: {
|
||||
const chainMetadata = await getChainMetadata(chain.chainId);
|
||||
const isDeprecated = chain.status === "deprecated";
|
||||
|
||||
const [account, topContracts] = await Promise.all([
|
||||
getRawAccount(),
|
||||
fetchTopContracts({
|
||||
chainId: chain.chainId,
|
||||
perPage: 3,
|
||||
timeRange: "month",
|
||||
}),
|
||||
]);
|
||||
const account = await getRawAccount();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-10">
|
||||
@@ -65,31 +54,6 @@ export default async function Page(props: {
|
||||
<ExplorersSection explorers={chain.explorers} />
|
||||
)}
|
||||
|
||||
{topContracts.length > 0 && (
|
||||
<section>
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<h2 className="font-semibold text-xl tracking-tight">
|
||||
Popular Contracts
|
||||
</h2>
|
||||
<Button asChild variant="outline">
|
||||
<Link
|
||||
href={`/${chain.slug}/popular`}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
See All
|
||||
<ArrowRightIcon className="size-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<TrendingContractSection
|
||||
topContracts={topContracts}
|
||||
chainId={chain.chainId}
|
||||
showPagination={false}
|
||||
searchParams={undefined}
|
||||
/>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{chain.services.filter((s) => s.enabled).length > 0 && (
|
||||
<SupportedProductsSection services={chain.services} />
|
||||
)}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import { type SortBy, fetchTopContracts } from "lib/search";
|
||||
import { CircleAlertIcon } from "lucide-react";
|
||||
import { TrendingContractSection } from "../../../../trending/components/trending-table";
|
||||
import { getChain } from "../../../utils";
|
||||
|
||||
export default async function Page(props: {
|
||||
params: Promise<{ chain_id: string }>;
|
||||
searchParams: Promise<{ page?: number; sortBy?: SortBy }>;
|
||||
}) {
|
||||
const searchParams = await props.searchParams;
|
||||
const params = await props.params;
|
||||
const chain = await getChain(params.chain_id);
|
||||
const topContracts = await fetchTopContracts({
|
||||
chainId: chain.chainId,
|
||||
page: searchParams.page,
|
||||
sortBy: searchParams.sortBy,
|
||||
perPage: 15,
|
||||
timeRange: "month",
|
||||
});
|
||||
|
||||
return (
|
||||
<section className="mt-2">
|
||||
<h2 className="mb-2 font-semibold text-2xl tracking-tighter">
|
||||
Popular Contracts
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Explore contracts on Ethereum and sort them by your preferred metrics
|
||||
</p>
|
||||
<div className="h-8" />
|
||||
{topContracts.length > 0 && (
|
||||
<TrendingContractSection
|
||||
topContracts={topContracts}
|
||||
chainId={chain.chainId}
|
||||
searchParams={searchParams}
|
||||
showPagination={true}
|
||||
/>
|
||||
)}
|
||||
{topContracts.length === 0 && (
|
||||
<div className="flex h-[200px] items-center justify-center rounded-lg border border-border text-lg text-muted-foreground">
|
||||
<div className="flex items-center gap-2">
|
||||
<CircleAlertIcon className="size-5 text-destructive-text" />
|
||||
No contracts found
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -247,7 +247,7 @@ export const MarketplaceTable: React.FC<MarketplaceTableProps> = ({
|
||||
<Tr
|
||||
{...row.getRowProps()}
|
||||
role="group"
|
||||
className="hover:bg-muted/50"
|
||||
className="hover:bg-card"
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => setTokenRow(row.original)}
|
||||
borderBottomWidth={1}
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
toTokens,
|
||||
toUnits,
|
||||
} from "thirdweb";
|
||||
import { getContract } from "thirdweb/contract";
|
||||
import type { OverrideEntry } from "thirdweb/dist/types/utils/extensions/drops/types";
|
||||
import type { Prettify } from "thirdweb/dist/types/utils/type-utils";
|
||||
import { getContractMetadata } from "thirdweb/extensions/common";
|
||||
@@ -71,7 +72,10 @@ export async function getClaimPhasesInLegacyFormat(
|
||||
conditions.map(async (condition) => {
|
||||
const [currencyMetadata, metadata, contractMetadata] = await Promise.all([
|
||||
await ERC20Ext.getCurrencyMetadata({
|
||||
contract: { ...options.contract, address: condition.currency },
|
||||
contract: getContract({
|
||||
...options.contract,
|
||||
address: condition.currency,
|
||||
}),
|
||||
}).then((m) => ({
|
||||
...m,
|
||||
displayValue: toTokens(condition.pricePerToken, m.decimals),
|
||||
|
||||
@@ -160,7 +160,7 @@ const SnapshotViewerSheetContent: React.FC<SnapshotUploadProps> = ({
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-full cursor-pointer rounded-md border border-border hover:border-primary",
|
||||
noCsv ? "bg-red-200" : "bg-card",
|
||||
noCsv ? "bg-red-200" : "bg-background",
|
||||
)}
|
||||
{...getRootProps()}
|
||||
>
|
||||
@@ -197,7 +197,7 @@ const SnapshotViewerSheetContent: React.FC<SnapshotUploadProps> = ({
|
||||
<a
|
||||
className="text-link-foreground hover:text-foreground"
|
||||
download
|
||||
href="/snapshot-with-maxclaimable.csv"
|
||||
href="/assets/examples/snapshot-with-maxclaimable.csv"
|
||||
>
|
||||
<DownloadIcon className="inline size-3" /> Example
|
||||
snapshot
|
||||
@@ -212,7 +212,7 @@ const SnapshotViewerSheetContent: React.FC<SnapshotUploadProps> = ({
|
||||
<a
|
||||
download
|
||||
className="text-link-foreground hover:text-foreground"
|
||||
href="/snapshot-with-overrides.csv"
|
||||
href="/assets/examples/snapshot-with-overrides.csv"
|
||||
>
|
||||
<DownloadIcon className="inline size-3" /> Example
|
||||
snapshot
|
||||
@@ -228,7 +228,7 @@ const SnapshotViewerSheetContent: React.FC<SnapshotUploadProps> = ({
|
||||
<a
|
||||
download
|
||||
className="text-link-foreground hover:text-foreground"
|
||||
href="/snapshot.csv"
|
||||
href="/assets/examples/snapshot.csv"
|
||||
>
|
||||
<DownloadIcon className="inline size-3" /> Example
|
||||
snapshot
|
||||
@@ -243,7 +243,7 @@ const SnapshotViewerSheetContent: React.FC<SnapshotUploadProps> = ({
|
||||
<a
|
||||
download
|
||||
className="text-link-foreground hover:text-foreground"
|
||||
href="/snapshot-with-maxclaimable.csv"
|
||||
href="/assets/examples/snapshot-with-maxclaimable.csv"
|
||||
>
|
||||
<DownloadIcon className="inline size-3" /> Example
|
||||
snapshot
|
||||
@@ -262,7 +262,7 @@ const SnapshotViewerSheetContent: React.FC<SnapshotUploadProps> = ({
|
||||
<a
|
||||
download
|
||||
className="text-link-foreground hover:text-foreground"
|
||||
href="/snapshot-with-overrides.csv"
|
||||
href="/assets/examples/snapshot-with-overrides.csv"
|
||||
>
|
||||
<DownloadIcon className="inline size-3" /> Example
|
||||
snapshot
|
||||
|
||||
@@ -83,7 +83,7 @@ export const MetadataHeader: React.FC<MetadataHeaderProps> = ({
|
||||
{chain && (
|
||||
<Link
|
||||
href={`/${chain.slug}`}
|
||||
className="flex w-fit shrink-0 items-center gap-2 rounded-3xl border border-border bg-muted/50 px-2.5 py-1.5 hover:bg-muted"
|
||||
className="flex w-fit shrink-0 items-center gap-2 rounded-3xl border border-border bg-card px-2.5 py-1.5 hover:bg-accent"
|
||||
>
|
||||
<ChainIcon ipfsSrc={chain.icon?.url} className="size-4" />
|
||||
{cleanedChainName && (
|
||||
@@ -109,7 +109,7 @@ export const MetadataHeader: React.FC<MetadataHeaderProps> = ({
|
||||
<CopyAddressButton
|
||||
address={address}
|
||||
copyIconPosition="left"
|
||||
className="bg-muted/50 text-xs"
|
||||
className="bg-card text-xs"
|
||||
variant="outline"
|
||||
/>
|
||||
|
||||
@@ -138,7 +138,7 @@ function BadgeLink(props: {
|
||||
<Button
|
||||
variant="outline"
|
||||
asChild
|
||||
className="!h-auto gap-2 rounded-lg bg-muted/50 px-3 py-1.5 text-xs capitalize"
|
||||
className="!h-auto gap-2 rounded-lg bg-card px-3 py-1.5 text-xs capitalize"
|
||||
>
|
||||
<Link href={props.href} target="_blank">
|
||||
{props.name}
|
||||
|
||||
@@ -136,7 +136,7 @@ function UniqueWalletsChart(props: ChartProps) {
|
||||
color: "hsl(var(--chart-1))",
|
||||
},
|
||||
}}
|
||||
chartClassName="aspect[2] lg:aspect-[4.5]"
|
||||
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -161,7 +161,7 @@ function TotalTransactionsChart(props: ChartProps) {
|
||||
color: "hsl(var(--chart-1))",
|
||||
},
|
||||
}}
|
||||
chartClassName="aspect[2] lg:aspect-[4.5]"
|
||||
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -181,7 +181,7 @@ function TotalEventsChart(props: ChartProps) {
|
||||
color: "hsl(var(--chart-1))",
|
||||
},
|
||||
}}
|
||||
chartClassName="aspect[2] lg:aspect-[4.5]"
|
||||
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -233,7 +233,7 @@ function FunctionBreakdownChart(
|
||||
},
|
||||
{} as Record<string, { label: string; color: string }>,
|
||||
)}
|
||||
chartClassName="aspect[2] lg:aspect-[4.5]"
|
||||
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
|
||||
showLegend
|
||||
/>
|
||||
);
|
||||
@@ -286,7 +286,7 @@ function EventBreakdownChart(
|
||||
},
|
||||
{} as Record<string, { label: string; color: string }>,
|
||||
)}
|
||||
chartClassName="aspect[2] lg:aspect-[4.5]"
|
||||
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
|
||||
showLegend
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CodeClient } from "@/components/ui/code/code.client";
|
||||
import {
|
||||
@@ -11,8 +12,8 @@ import { Flex, FormControl, Input, Select } from "@chakra-ui/react";
|
||||
import { LazyCreateAPIKeyDialog } from "components/settings/ApiKeys/Create/LazyCreateAPIKeyDialog";
|
||||
import { useTrack } from "hooks/analytics/useTrack";
|
||||
import { useAllChainsData } from "hooks/chains/allChains";
|
||||
import { useClipboard } from "hooks/useClipboard";
|
||||
import { CheckIcon, CopyIcon } from "lucide-react";
|
||||
import { AlertCircleIcon } from "lucide-react";
|
||||
import { useTheme } from "next-themes";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useMemo, useState } from "react";
|
||||
@@ -211,6 +212,7 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
|
||||
twAccount,
|
||||
}) => {
|
||||
const trackEvent = useTrack();
|
||||
const { theme } = useTheme();
|
||||
|
||||
const apiKeys = useApiKeys({
|
||||
isLoggedIn: !!twAccount,
|
||||
@@ -268,7 +270,7 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
|
||||
listingId: "0",
|
||||
directListingId: "0",
|
||||
englishAuctionId: "0",
|
||||
theme: "dark",
|
||||
theme: theme === "light" ? "light" : "dark",
|
||||
primaryColor: "purple",
|
||||
secondaryColor: "orange",
|
||||
},
|
||||
@@ -298,13 +300,12 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
|
||||
[iframeSrc],
|
||||
);
|
||||
|
||||
const { hasCopied, onCopy } = useClipboard(embedCode, 3000);
|
||||
const [showCreateAPIKeyModal, setShowCreateAPIKeyModal] = useState(false);
|
||||
const activeAccount = useActiveAccount();
|
||||
const pathname = usePathname();
|
||||
|
||||
return (
|
||||
<Flex gap={8} direction="column">
|
||||
<div>
|
||||
<LazyCreateAPIKeyDialog
|
||||
prefill={{
|
||||
name: "Embed API Key",
|
||||
@@ -322,206 +323,223 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
|
||||
apiKeys.refetch();
|
||||
}}
|
||||
enableNebulaServiceByDefault={false}
|
||||
teamSlug={undefined}
|
||||
/>
|
||||
|
||||
<Flex gap={8} direction={{ base: "column", md: "row" }}>
|
||||
<Card className="flex w-full flex-col gap-5 md:w-1/2">
|
||||
<Heading size="title.sm">Configuration</Heading>
|
||||
<Alert variant="warning">
|
||||
<AlertCircleIcon className="size-5" />
|
||||
<AlertTitle>Deprecated</AlertTitle>
|
||||
<AlertDescription className="leading-relaxed">
|
||||
<span>
|
||||
thirdweb NFT Embeds are deprecated and not actively maintained
|
||||
</span>{" "}
|
||||
<br />
|
||||
<span>
|
||||
Use the{" "}
|
||||
<Link
|
||||
href="https://github.com/thirdweb-example/nft-minting-template"
|
||||
target="_blank"
|
||||
className="underline underline-offset-2 hover:text-foreground"
|
||||
>
|
||||
{" "}
|
||||
NFT Minting Template
|
||||
</Link>{" "}
|
||||
or build a custom NFT minting app using{" "}
|
||||
<Link
|
||||
href="https://portal.thirdweb.com/react/v5"
|
||||
className="underline underline-offset-2 hover:text-foreground"
|
||||
>
|
||||
thirdweb React SDK components
|
||||
</Link>{" "}
|
||||
instead
|
||||
</span>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<div className="h-8" />
|
||||
|
||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
<div>
|
||||
<h2 className="mb-3 font-semibold text-xl tracking-tight">
|
||||
Configuration
|
||||
</h2>
|
||||
|
||||
<Card className="flex w-full flex-col gap-5 bg-card">
|
||||
{ercOrMarketplace === "marketplace" ? (
|
||||
<FormControl>
|
||||
<FormLabel>Listing ID</FormLabel>
|
||||
<Input type="number" {...register("listingId")} />
|
||||
<FormHelperText>
|
||||
The listing ID the embed should display
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
) : null}
|
||||
|
||||
{ercOrMarketplace === "marketplace-v3" ? (
|
||||
<FormControl>
|
||||
<FormLabel>Listing type</FormLabel>
|
||||
<Select {...register("listingType")}>
|
||||
<option value="direct-listing">Direct Listing</option>
|
||||
<option value="english-auction">English Auction</option>
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
The type of listing the embed should display
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
) : null}
|
||||
|
||||
{ercOrMarketplace === "marketplace-v3" &&
|
||||
watch("listingType") === "direct-listing" ? (
|
||||
<FormControl>
|
||||
<FormLabel>Direct Listing ID</FormLabel>
|
||||
<Input type="number" {...register("directListingId")} />
|
||||
<FormHelperText>
|
||||
The direct listing ID the embed should display
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
) : null}
|
||||
|
||||
{ercOrMarketplace === "marketplace-v3" &&
|
||||
watch("listingType") === "english-auction" ? (
|
||||
<FormControl>
|
||||
<FormLabel>English Auction ID</FormLabel>
|
||||
<Input type="number" {...register("englishAuctionId")} />
|
||||
<FormHelperText>
|
||||
The english auction ID the embed should display
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
) : null}
|
||||
|
||||
{ercOrMarketplace === "erc1155" ? (
|
||||
<FormControl>
|
||||
<FormLabel>Token ID</FormLabel>
|
||||
<Input type="number" {...register("tokenId")} />
|
||||
<FormHelperText>
|
||||
The token ID the embed should display
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
) : null}
|
||||
|
||||
{ercOrMarketplace === "marketplace" ? (
|
||||
<FormControl>
|
||||
<FormLabel>Listing ID</FormLabel>
|
||||
<Input type="number" {...register("listingId")} />
|
||||
<FormHelperText>
|
||||
The listing ID the embed should display
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
) : null}
|
||||
<FormLabel>Client ID</FormLabel>
|
||||
{!activeAccount ? (
|
||||
<Button asChild className="w-full">
|
||||
<Link
|
||||
href={`/login${
|
||||
pathname ? `?next=${encodeURIComponent(pathname)}` : ""
|
||||
}`}
|
||||
>
|
||||
Sign in to create a client ID
|
||||
</Link>
|
||||
</Button>
|
||||
) : validApiKey ? (
|
||||
<Input
|
||||
readOnly
|
||||
disabled
|
||||
value={`${validApiKey?.name} - ${validApiKey?.key}`}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={() => {
|
||||
trackEvent({
|
||||
category: "api-keys",
|
||||
action: "create",
|
||||
label: "attempt",
|
||||
fromEmbed: true,
|
||||
});
|
||||
|
||||
{ercOrMarketplace === "marketplace-v3" ? (
|
||||
<FormControl>
|
||||
<FormLabel>Listing type</FormLabel>
|
||||
<Select {...register("listingType")}>
|
||||
<option value="direct-listing">Direct Listing</option>
|
||||
<option value="english-auction">English Auction</option>
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
The type of listing the embed should display
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
) : null}
|
||||
|
||||
{ercOrMarketplace === "marketplace-v3" &&
|
||||
watch("listingType") === "direct-listing" ? (
|
||||
<FormControl>
|
||||
<FormLabel>Direct Listing ID</FormLabel>
|
||||
<Input type="number" {...register("directListingId")} />
|
||||
<FormHelperText>
|
||||
The direct listing ID the embed should display
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
) : null}
|
||||
|
||||
{ercOrMarketplace === "marketplace-v3" &&
|
||||
watch("listingType") === "english-auction" ? (
|
||||
<FormControl>
|
||||
<FormLabel>English Auction ID</FormLabel>
|
||||
<Input type="number" {...register("englishAuctionId")} />
|
||||
<FormHelperText>
|
||||
The english auction ID the embed should display
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
) : null}
|
||||
|
||||
{ercOrMarketplace === "erc1155" ? (
|
||||
<FormControl>
|
||||
<FormLabel>Token ID</FormLabel>
|
||||
<Input type="number" {...register("tokenId")} />
|
||||
<FormHelperText>
|
||||
The token ID the embed should display
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
) : null}
|
||||
|
||||
<FormControl>
|
||||
<FormLabel>Client ID</FormLabel>
|
||||
{!activeAccount ? (
|
||||
<Button asChild className="w-full">
|
||||
<Link
|
||||
href={`/login${
|
||||
pathname ? `?next=${encodeURIComponent(pathname)}` : ""
|
||||
}`}
|
||||
setShowCreateAPIKeyModal(true);
|
||||
}}
|
||||
disabled={createKeyMutation.isPending}
|
||||
>
|
||||
Sign in to create a client ID
|
||||
</Link>
|
||||
</Button>
|
||||
) : validApiKey ? (
|
||||
<Input
|
||||
readOnly
|
||||
disabled
|
||||
value={`${validApiKey?.name} - ${validApiKey?.key}`}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={() => {
|
||||
trackEvent({
|
||||
category: "api-keys",
|
||||
action: "create",
|
||||
label: "attempt",
|
||||
fromEmbed: true,
|
||||
});
|
||||
|
||||
setShowCreateAPIKeyModal(true);
|
||||
}}
|
||||
disabled={createKeyMutation.isPending}
|
||||
>
|
||||
Create Client ID
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<FormHelperText>
|
||||
You need a client ID to use embeds.{" "}
|
||||
<Link
|
||||
href="https://portal.thirdweb.com/account/api-keys"
|
||||
className="text-link-foreground hover:text-foreground"
|
||||
target="_blank"
|
||||
>
|
||||
Learn more
|
||||
</Link>
|
||||
.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<FormControl>
|
||||
<FormLabel>RPC Url</FormLabel>
|
||||
<Input type="url" {...register("rpcUrl")} />
|
||||
<FormHelperText>
|
||||
RPC the embed should use to connect to the blockchain.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
{ercOrMarketplace === "marketplace" ||
|
||||
ercOrMarketplace === "marketplace-v3" ? null : (
|
||||
<FormControl gap={4}>
|
||||
<Heading size="title.sm" my={4}>
|
||||
Gasless
|
||||
</Heading>
|
||||
<Select {...register("gasless")} mb={4}>
|
||||
<option value="false">Disabled</option>
|
||||
<option value="openZeppelin">OpenZeppelin Relayer</option>
|
||||
<option value="biconomy">Biconomy Relayer</option>
|
||||
</Select>
|
||||
{watch("gasless") === "openZeppelin" && (
|
||||
<FormControl>
|
||||
<FormLabel>OpenZeppelin Relayer URL</FormLabel>
|
||||
<Input type="url" {...register("relayUrl")} />
|
||||
</FormControl>
|
||||
)}
|
||||
{watch("gasless") === "biconomy" && (
|
||||
<Flex gap={4} flexDir="column">
|
||||
<FormControl>
|
||||
<FormLabel>Biconomy API key</FormLabel>
|
||||
<Input type="url" {...register("biconomyApiKey")} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>Biconomy API ID</FormLabel>
|
||||
<Input type="url" {...register("biconomyApiId")} />
|
||||
</FormControl>
|
||||
</Flex>
|
||||
Create Client ID
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<FormHelperText>
|
||||
A relayer can be used to make the transaction gasless for the
|
||||
end user.{" "}
|
||||
You need a client ID to use embeds.{" "}
|
||||
<Link
|
||||
target="_blank"
|
||||
href="https://portal.thirdweb.com/account/api-keys"
|
||||
className="text-link-foreground hover:text-foreground"
|
||||
href="https://blog.thirdweb.com/guides/setup-gasless-transactions"
|
||||
target="_blank"
|
||||
>
|
||||
Learn more
|
||||
</Link>
|
||||
.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
)}
|
||||
|
||||
<FormControl>
|
||||
<Heading size="title.sm" my={4}>
|
||||
Customization
|
||||
</Heading>
|
||||
<FormLabel>Theme</FormLabel>
|
||||
<Select {...register("theme")}>
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
<option value="system">User system</option>
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
Selecting system will make it so the embed would change depending
|
||||
on the user system's preferences.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<FormControl>
|
||||
<FormLabel>Primary Color</FormLabel>
|
||||
<Select {...register("primaryColor")}>
|
||||
{ercOrMarketplace === "erc721" && (
|
||||
<option value="default">Default</option>
|
||||
)}
|
||||
{colorOptions.map((color) => (
|
||||
<option key={color} value={color}>
|
||||
{color[0]?.toUpperCase() + color.substring(1)}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
Used for the main actions button backgrounds.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
{ercOrMarketplace === "marketplace" ||
|
||||
ercOrMarketplace === "marketplace-v3" ? (
|
||||
<FormControl>
|
||||
<FormLabel>Secondary Color</FormLabel>
|
||||
<Select {...register("secondaryColor")}>
|
||||
<FormLabel>RPC Url</FormLabel>
|
||||
<Input type="url" {...register("rpcUrl")} />
|
||||
<FormHelperText>
|
||||
RPC the embed should use to connect to the blockchain.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
{ercOrMarketplace === "marketplace" ||
|
||||
ercOrMarketplace === "marketplace-v3" ? null : (
|
||||
<FormControl gap={4}>
|
||||
<Heading size="title.sm" mb={2}>
|
||||
Gasless
|
||||
</Heading>
|
||||
<Select {...register("gasless")} mb={4}>
|
||||
<option value="false">Disabled</option>
|
||||
<option value="openZeppelin">OpenZeppelin Relayer</option>
|
||||
<option value="biconomy">Biconomy Relayer</option>
|
||||
</Select>
|
||||
{watch("gasless") === "openZeppelin" && (
|
||||
<FormControl>
|
||||
<FormLabel>OpenZeppelin Relayer URL</FormLabel>
|
||||
<Input type="url" {...register("relayUrl")} />
|
||||
</FormControl>
|
||||
)}
|
||||
{watch("gasless") === "biconomy" && (
|
||||
<Flex gap={4} flexDir="column">
|
||||
<FormControl>
|
||||
<FormLabel>Biconomy API key</FormLabel>
|
||||
<Input type="url" {...register("biconomyApiKey")} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>Biconomy API ID</FormLabel>
|
||||
<Input type="url" {...register("biconomyApiId")} />
|
||||
</FormControl>
|
||||
</Flex>
|
||||
)}
|
||||
<FormHelperText>
|
||||
A relayer can be used to make the transaction gasless for the
|
||||
end user.{" "}
|
||||
<Link
|
||||
target="_blank"
|
||||
className="text-link-foreground hover:text-foreground"
|
||||
href="https://blog.thirdweb.com/guides/setup-gasless-transactions"
|
||||
>
|
||||
Learn more
|
||||
</Link>
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
)}
|
||||
|
||||
<FormControl>
|
||||
<FormLabel>Theme</FormLabel>
|
||||
<Select {...register("theme")}>
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
<option value="system">User system</option>
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
Selecting system will make it so the embed would change
|
||||
depending on the user system's preferences.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<FormControl>
|
||||
<FormLabel>Primary Color</FormLabel>
|
||||
<Select {...register("primaryColor")}>
|
||||
{ercOrMarketplace === "erc721" && (
|
||||
<option value="default">Default</option>
|
||||
)}
|
||||
{colorOptions.map((color) => (
|
||||
<option key={color} value={color}>
|
||||
{color[0]?.toUpperCase() + color.substring(1)}
|
||||
@@ -529,21 +547,38 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
|
||||
))}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
Use for secondary actions (like when the user is connected to
|
||||
the wrong network)
|
||||
Used for the main actions button backgrounds.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
) : null}
|
||||
</Card>
|
||||
|
||||
<Card className="flex w-full flex-col gap-2 md:w-1/2">
|
||||
<Heading size="title.sm">Embed Code</Heading>
|
||||
<CodeClient code={embedCode} lang="html" />
|
||||
<Button
|
||||
className="w-auto gap-2"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
onCopy();
|
||||
{ercOrMarketplace === "marketplace" ||
|
||||
ercOrMarketplace === "marketplace-v3" ? (
|
||||
<FormControl>
|
||||
<FormLabel>Secondary Color</FormLabel>
|
||||
<Select {...register("secondaryColor")}>
|
||||
{colorOptions.map((color) => (
|
||||
<option key={color} value={color}>
|
||||
{color[0]?.toUpperCase() + color.substring(1)}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
Use for secondary actions (like when the user is connected to
|
||||
the wrong network)
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
) : null}
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 className="mb-3 font-semibold text-xl tracking-tight">
|
||||
Embed Code
|
||||
</h2>
|
||||
<CodeClient
|
||||
code={embedCode}
|
||||
lang="html"
|
||||
onCopy={() => {
|
||||
trackEvent({
|
||||
category: "embed",
|
||||
action: "click",
|
||||
@@ -552,19 +587,15 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
|
||||
chainId,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{hasCopied ? (
|
||||
<CheckIcon className="size-4" />
|
||||
) : (
|
||||
<CopyIcon className="size-4" />
|
||||
)}
|
||||
{hasCopied ? "Copied!" : "Copy to clipboard"}
|
||||
</Button>
|
||||
</Card>
|
||||
</Flex>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Heading size="title.sm">Preview</Heading>
|
||||
<div className="h-8" />
|
||||
|
||||
<h2 className="mb-3 font-semibold text-xl tracking-tight">Preview</h2>
|
||||
|
||||
<div className="overflow-hidden rounded-lg border border-border">
|
||||
{!validApiKey ? (
|
||||
<Text>You need to create a client ID to use embeds</Text>
|
||||
) : iframeSrc ? (
|
||||
@@ -577,6 +608,6 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@ export const ContractExplorerPage: React.FC<ContractExplorePageProps> = ({
|
||||
|
||||
const functions = getContractFunctionsFromAbi(abi);
|
||||
return (
|
||||
<div className="flex h-[70vh] flex-col">
|
||||
<div className="flex h-[70vh] flex-col ">
|
||||
{functions && functions.length > 0 ? (
|
||||
<ContractFunctionsOverview
|
||||
onlyFunctions
|
||||
@@ -44,7 +44,7 @@ export const ContractExplorerPage: React.FC<ContractExplorePageProps> = ({
|
||||
twAccount={twAccount}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center gap-2 text-center">
|
||||
<div className="flex flex-col items-center justify-center gap-2 bg-card text-center">
|
||||
<h2 className="font-semibold text-2xl tracking-tight">
|
||||
No callable functions discovered in ABI.
|
||||
</h2>
|
||||
|
||||
@@ -4,13 +4,13 @@ import { ScrollShadow } from "@/components/ui/ScrollShadow/ScrollShadow";
|
||||
import { Alert, AlertTitle } from "@/components/ui/alert";
|
||||
import type { Account as TWAccount } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { CircleSlash } from "lucide-react";
|
||||
import type { ContractOptions } from "thirdweb";
|
||||
import type { ThirdwebContract } from "thirdweb/contract";
|
||||
import type { Account } from "thirdweb/wallets";
|
||||
import { ModuleCard } from "./module-card";
|
||||
import { useAllModuleContractInfo } from "./moduleContractInfo";
|
||||
|
||||
export const InstalledModulesTable = (props: {
|
||||
contract: ContractOptions;
|
||||
contract: ThirdwebContract;
|
||||
installedModules: {
|
||||
data?: string[];
|
||||
isPending: boolean;
|
||||
|
||||
@@ -15,8 +15,9 @@ import { FormProvider, type UseFormReturn, useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
type Chain,
|
||||
type ContractOptions,
|
||||
type ThirdwebClient,
|
||||
type ThirdwebContract,
|
||||
getContract,
|
||||
sendTransaction,
|
||||
waitForReceipt,
|
||||
} from "thirdweb";
|
||||
@@ -47,7 +48,7 @@ type FormData = {
|
||||
};
|
||||
|
||||
type InstallModuleFormProps = {
|
||||
contract: ContractOptions;
|
||||
contract: ThirdwebContract;
|
||||
refetchModules: () => void;
|
||||
account: Account;
|
||||
installedModules: {
|
||||
@@ -180,11 +181,13 @@ export const InstallModuleForm = (props: InstallModuleFormProps) => {
|
||||
|
||||
return Promise.all(
|
||||
moduleAddress.map(async (address) => {
|
||||
const result = await resolveImplementation({
|
||||
client,
|
||||
address,
|
||||
chain: contract.chain,
|
||||
});
|
||||
const result = await resolveImplementation(
|
||||
getContract({
|
||||
client,
|
||||
address,
|
||||
chain: contract.chain,
|
||||
}),
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
throw new Error("Failed to fetch bytecode for module");
|
||||
|
||||
@@ -23,7 +23,7 @@ import { Suspense, useEffect, useState } from "react";
|
||||
import { ErrorBoundary, type FallbackProps } from "react-error-boundary";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
type ContractOptions,
|
||||
type ThirdwebContract,
|
||||
getContract,
|
||||
sendTransaction,
|
||||
waitForReceipt,
|
||||
@@ -35,7 +35,7 @@ import { useModuleContractInfo } from "./moduleContractInfo";
|
||||
|
||||
type ModuleCardProps = {
|
||||
moduleAddress: string;
|
||||
contract: ContractOptions;
|
||||
contract: ThirdwebContract;
|
||||
onRemoveModule: () => void;
|
||||
ownerAccount: Account | undefined;
|
||||
allModuleContractInfo: {
|
||||
@@ -204,7 +204,7 @@ export type ModuleCardUIProps = {
|
||||
|
||||
export function ModuleCardUI(props: ModuleCardUIProps) {
|
||||
return (
|
||||
<section className="rounded-lg border border-border bg-muted/50">
|
||||
<section className="rounded-lg border border-border bg-card">
|
||||
{/* Header */}
|
||||
<div className="relative p-4 lg:p-6">
|
||||
{/* Title */}
|
||||
|
||||
@@ -239,7 +239,7 @@ export const NFTGetAllTable: React.FC<ContractOverviewNFTGetAllProps> = ({
|
||||
<Flex gap={4} direction="column">
|
||||
<TableContainer
|
||||
maxW="100%"
|
||||
className="relative rounded-lg border border-border"
|
||||
className="relative rounded-lg border border-border bg-card"
|
||||
>
|
||||
{getNFTsQuery.isFetching && (
|
||||
<Spinner
|
||||
@@ -251,7 +251,7 @@ export const NFTGetAllTable: React.FC<ContractOverviewNFTGetAllProps> = ({
|
||||
/>
|
||||
)}
|
||||
<Table {...getTableProps()}>
|
||||
<Thead className="!bg-muted/50">
|
||||
<Thead className="!bg-background">
|
||||
{headerGroups.map((headerGroup, headerGroupIndex) => (
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: FIXME
|
||||
<Tr {...headerGroup.getHeaderGroupProps()} key={headerGroupIndex}>
|
||||
@@ -275,7 +275,7 @@ export const NFTGetAllTable: React.FC<ContractOverviewNFTGetAllProps> = ({
|
||||
<Tbody
|
||||
{...getTableBodyProps()}
|
||||
position="relative"
|
||||
className="!bg-background"
|
||||
className="!bg-card"
|
||||
>
|
||||
{page.map((row, rowIndex) => {
|
||||
const failedToLoad = !row.original.tokenURI;
|
||||
@@ -284,7 +284,7 @@ export const NFTGetAllTable: React.FC<ContractOverviewNFTGetAllProps> = ({
|
||||
<Tr
|
||||
{...row.getRowProps()}
|
||||
role="group"
|
||||
className="hover:bg-muted/50"
|
||||
className="bg-card hover:bg-accent/50"
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => {
|
||||
const tokenId = row.original.id;
|
||||
|
||||
@@ -135,7 +135,7 @@ function OverviewAnalytics(props: ChartProps) {
|
||||
data={mergedData || []}
|
||||
isPending={isPending}
|
||||
showLegend
|
||||
chartClassName="aspect[2] lg:aspect-[4.5]"
|
||||
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -265,7 +265,7 @@ const ListingCards: React.FC<ListingCardsProps> = ({
|
||||
<div className="grid grid-cols-2 gap-3 md:grid-cols-3 md:gap-6 max-sm:[&>*:nth-child(n+3)]:hidden">
|
||||
{listings.map((listing, index) => (
|
||||
<div
|
||||
className="relative rounded-lg border border-border bg-muted/50 hover:border-foreground hover:bg-muted"
|
||||
className="relative rounded-lg border border-border bg-card transition-colors hover:border-active-border"
|
||||
key={`${listing.creatorAddress}-${index}`}
|
||||
>
|
||||
<div className="relative aspect-square w-full overflow-hidden rounded-lg">
|
||||
|
||||
@@ -6,7 +6,7 @@ export function StatCard(props: {
|
||||
label: string;
|
||||
}) {
|
||||
return (
|
||||
<dl className="block rounded-lg border border-border bg-muted/50 p-4">
|
||||
<dl className="block rounded-lg border border-border bg-card p-4">
|
||||
<dt className="mb-1.5 text-sm md:text-base">{props.label}</dt>
|
||||
<SkeletonContainer
|
||||
loadedData={props.isPending ? undefined : props.value}
|
||||
|
||||
@@ -157,7 +157,7 @@ export const AirdropUpload: React.FC<AirdropUploadProps> = ({
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-full cursor-pointer items-center justify-center rounded-md border border-border hover:border-primary",
|
||||
noCsv ? "bg-red-200" : "bg-card",
|
||||
noCsv ? "bg-red-200" : "bg-background",
|
||||
)}
|
||||
{...getRootProps()}
|
||||
>
|
||||
@@ -193,7 +193,11 @@ export const AirdropUpload: React.FC<AirdropUploadProps> = ({
|
||||
Files <em>must</em> contain one .csv file with an address and
|
||||
quantity column, if the quantity column is not provided, that
|
||||
record will be flagged as invalid.
|
||||
<Link download color="primary.500" href="/airdrop.csv">
|
||||
<Link
|
||||
download
|
||||
color="primary.500"
|
||||
href="/assets/examples/airdrop.csv"
|
||||
>
|
||||
Download an example CSV
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
@@ -158,7 +158,7 @@ export default async function Page(props: {
|
||||
|
||||
<div className="grid grid-cols-2 items-start">
|
||||
<div className="flex items-center gap-x-2">Raw Input:</div>
|
||||
<div className="break-words rounded-lg bg-muted/50 p-4 text-sm">
|
||||
<div className="break-words rounded-lg bg-card p-4 text-sm">
|
||||
{transaction.input}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -182,7 +182,9 @@ export const ChainTypeFilter: React.FC<ChainTypeFilterProps> = ({
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={value === "all" ? undefined : "border-primary pr-11"}
|
||||
className={
|
||||
value === "all" ? "bg-card" : "border-active-border bg-card pr-11"
|
||||
}
|
||||
>
|
||||
{value === "mainnets"
|
||||
? "Mainnets Only"
|
||||
@@ -196,7 +198,7 @@ export const ChainTypeFilter: React.FC<ChainTypeFilterProps> = ({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="absolute top-[1px] right-[1px] bottom-[1px] h-auto w-10"
|
||||
className="absolute top-[1px] right-[1px] bottom-[1px] h-auto w-10 bg-card"
|
||||
onClick={() => {
|
||||
const url = makeUrl("all");
|
||||
router.replace(url);
|
||||
@@ -275,7 +277,9 @@ export const ChainOptionsFilter: React.FC<ChainOptionsFilterProps> = ({
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={hasFilters ? "border-primary pr-11" : undefined}
|
||||
className={
|
||||
hasFilters ? "border-active-border bg-card pr-11" : "bg-card"
|
||||
}
|
||||
>
|
||||
Chain Options
|
||||
{!hasFilters && <ChevronDownIcon className="ml-2 size-4" />}
|
||||
@@ -285,7 +289,7 @@ export const ChainOptionsFilter: React.FC<ChainOptionsFilterProps> = ({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="absolute top-[1px] right-[1px] bottom-[1px] h-auto w-10"
|
||||
className="absolute top-[1px] right-[1px] bottom-[1px] h-auto w-10 bg-card"
|
||||
onClick={() => {
|
||||
mutableSearchParams.delete("includeDeprecated");
|
||||
mutableSearchParams.delete("page");
|
||||
@@ -399,7 +403,9 @@ export const ChainServiceFilter: React.FC<ChainServiceFilterProps> = ({
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={hasActiveFilter ? "border-primary pr-11" : undefined}
|
||||
className={
|
||||
hasActiveFilter ? "border-active-border bg-card pr-11" : "bg-card"
|
||||
}
|
||||
>
|
||||
{buttonTitle}
|
||||
{!hasActiveFilter && <ChevronDownIcon className="ml-2 size-4" />}
|
||||
|
||||
@@ -48,7 +48,7 @@ export const SearchInput: React.FC = () => {
|
||||
<SearchIcon className="-translate-y-1/2 absolute top-[50%] left-3 size-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Search by name or chain ID"
|
||||
className="h-10 rounded-lg py-2 pl-9"
|
||||
className="h-10 rounded-lg bg-card py-2 pl-9 lg:min-w-[300px]"
|
||||
defaultValue={searchParams?.get("query") || ""}
|
||||
onChange={(e) => handleSearch(e.target.value)}
|
||||
ref={inputRef}
|
||||
|
||||
@@ -33,7 +33,7 @@ export async function ChainListCard({
|
||||
|
||||
return (
|
||||
<div className="relative h-full">
|
||||
<Card className="h-full w-full bg-muted/50 hover:bg-muted">
|
||||
<Card className="h-full w-full transition-colors hover:border-active-border">
|
||||
<CardHeader className="flex flex-row items-center justify-between p-4">
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<ChainIcon iconUrl={iconUrl} className="size-6" />
|
||||
|
||||
@@ -39,7 +39,7 @@ export async function ChainListRow({
|
||||
}: ChainListRowProps) {
|
||||
const chainMetadata = await getChainMetadata(chainId);
|
||||
return (
|
||||
<TableRow linkBox className="hover:bg-muted/50">
|
||||
<TableRow linkBox className="hover:bg-accent/50">
|
||||
{/* Name */}
|
||||
<TableCell>
|
||||
<div className="flex w-[370px] flex-row items-center gap-4">
|
||||
|
||||
@@ -52,7 +52,7 @@ export default async function ChainListPage(props: {
|
||||
</h1>
|
||||
<AddYourChainButton className="lg:hidden" />
|
||||
</div>
|
||||
<div className="flex flex-row items-end gap-4 lg:flex-col">
|
||||
<div className="flex flex-row items-end gap-4 lg:flex-col ">
|
||||
<div className="flex w-full flex-row gap-4">
|
||||
<SearchInput />
|
||||
<ChainListView activeView={activeView} />
|
||||
|
||||
|
After Width: | Height: | Size: 919 KiB |
|
After Width: | Height: | Size: 203 KiB |
|
Before Width: | Height: | Size: 388 KiB After Width: | Height: | Size: 918 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 764 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 546 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 540 KiB |
|
Before Width: | Height: | Size: 546 KiB |
|
Before Width: | Height: | Size: 540 KiB |
|
After Width: | Height: | Size: 427 KiB |
|
After Width: | Height: | Size: 453 KiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 11 MiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 456 KiB |
|
After Width: | Height: | Size: 660 KiB |
@@ -38,17 +38,19 @@ import creatorCTA from "./temp-assets/creatorCTA.png";
|
||||
import lumiaBanner from "./temp-assets/lumiaBanner.png";
|
||||
import somniaBanner from "./temp-assets/somniaBanner.png";
|
||||
|
||||
import plumeBannerv2 from "./temp-assets/plumev2Banner.png";
|
||||
import plumeCTAv2 from "./temp-assets/plumev2CTA.png";
|
||||
|
||||
import flowBanner from "./temp-assets/flowBanner.png";
|
||||
import flowCTA from "./temp-assets/flowCTA.png";
|
||||
import mantleBanner from "./temp-assets/mantle.png";
|
||||
import metalBanner from "./temp-assets/metalBanner.png";
|
||||
import metalCTA from "./temp-assets/metalCTA.png";
|
||||
import plumeBanner from "./temp-assets/plumeBanner.png";
|
||||
|
||||
import plumeCTA from "./temp-assets/plumeCTA.png";
|
||||
import rivalzBanner from "./temp-assets/rivalzBanner.png";
|
||||
import rivalzCTA from "./temp-assets/rivalzCTA.png";
|
||||
import rootStockBanner from "./temp-assets/rootstock-banner.png";
|
||||
import rootStockCTABG from "./temp-assets/rootstock-cta.png";
|
||||
import saakuruBanner from "./temp-assets/saakuruBanner.png";
|
||||
import saakuruCTA from "./temp-assets/saakuruCTA.png";
|
||||
import shidoBanner from "./temp-assets/shidoBanner.png";
|
||||
import shidoCta from "./temp-assets/shidoCta.png";
|
||||
import soneiumBanner from "./temp-assets/soneiumBanner.png";
|
||||
@@ -59,15 +61,15 @@ import treasureBanner from "./temp-assets/treasureBanner.png";
|
||||
import treasureCTA from "./temp-assets/treasureCta.png";
|
||||
import vanarBanner from "./temp-assets/vanar-banner.png";
|
||||
import vanarCTABG from "./temp-assets/vanar-cta.png";
|
||||
import victionBanner from "./temp-assets/victionBanner.png";
|
||||
import victionCTA from "./temp-assets/victionCTA.png";
|
||||
import worldChainBanner from "./temp-assets/worldChainBanner.jpg";
|
||||
import worldChainCTA from "./temp-assets/worldChainCTA.jpg";
|
||||
import xaiBanner from "./temp-assets/xai-banner.jpg";
|
||||
import zetachainBanner from "./temp-assets/zetachainBanner.png";
|
||||
import zetachainCTA from "./temp-assets/zetachainCTA.png";
|
||||
import zytronBanner from "./temp-assets/zytronBanner.png";
|
||||
import zytronCTA from "./temp-assets/zytronCTA.jpg";
|
||||
|
||||
import fhenixBanner from "./temp-assets/fhenixBanner.png";
|
||||
import fhenixCTA from "./temp-assets/fhenixCTA.png";
|
||||
|
||||
// END TEMPORARY
|
||||
|
||||
import { API_SERVER_URL } from "@/constants/env";
|
||||
@@ -169,6 +171,11 @@ const chainMetaRecord = {
|
||||
|
||||
cta: OP_CTA,
|
||||
},
|
||||
//celo Mainnet
|
||||
42220: {
|
||||
cta: OP_CTA,
|
||||
gasSponsored: true,
|
||||
},
|
||||
// optimism
|
||||
10: {
|
||||
cta: OP_CTA,
|
||||
@@ -419,15 +426,29 @@ const chainMetaRecord = {
|
||||
buttonText: "Learn More",
|
||||
},
|
||||
},
|
||||
//fhenix Nitrogen
|
||||
8008148: {
|
||||
headerImgUrl: fhenixBanner.src,
|
||||
//EVM on Flow testnet
|
||||
545: {
|
||||
headerImgUrl: flowBanner.src,
|
||||
about:
|
||||
"Discover Fhenix—the first-ever FHE-based blockchain. Fhenix is an L2 built on Ethereum that brings powerful Fully Homomorphic Encryption (FHE) into the EVM space. This technology enables confidential computation on-chain, allowing users to explore a new generation of use cases.",
|
||||
"Flow is a secure, high-performance, decentralized L1 EVM equivalent chain, with innovative next-generation features not available on other EVM compatible chains, the lowest fees anywhere, and a vibrant ecosystem of major consumer brands",
|
||||
cta: {
|
||||
backgroundImageUrl: fhenixCTA.src,
|
||||
title: "Onchain confidentiality powered by the first FHE L2 Rollup",
|
||||
buttonLink: "https://thirdweb.com/fhenix-nitrogen",
|
||||
backgroundImageUrl: flowCTA.src,
|
||||
title:
|
||||
"Explore Flow's innovative features not available on any other EVM compatible chain",
|
||||
buttonLink: "https://developers.flow.com/evm/how-it-works",
|
||||
buttonText: "Learn More",
|
||||
},
|
||||
},
|
||||
//EVM on Flow Mainnet
|
||||
747: {
|
||||
headerImgUrl: flowBanner.src,
|
||||
about:
|
||||
"Flow is a secure, high-performance, decentralized L1 EVM equivalent chain, with innovative next-generation features not available on other EVM compatible chains, the lowest fees anywhere, and a vibrant ecosystem of major consumer brands",
|
||||
cta: {
|
||||
backgroundImageUrl: flowCTA.src,
|
||||
title:
|
||||
"Learn about innovative features not available on any other EVM compatible chains!",
|
||||
buttonLink: "https://developers.flow.com/evm/how-it-works",
|
||||
buttonText: "Learn More",
|
||||
},
|
||||
},
|
||||
@@ -467,7 +488,7 @@ const chainMetaRecord = {
|
||||
buttonText: "Learn More",
|
||||
},
|
||||
},
|
||||
//Gemuchain
|
||||
//Gemuchain -- Deprecated
|
||||
1903648807: {
|
||||
headerImgUrl: thirdwebBanner.src,
|
||||
about:
|
||||
@@ -547,12 +568,19 @@ const chainMetaRecord = {
|
||||
headerImgUrl: metalBanner.src,
|
||||
about:
|
||||
"Metal L2 is a banking-focused Layer 2 blockchain built on the Optimism Superchain, designed to enable direct on-chain fiat deposits through its connection to The Digital Banking Network—an open-source blockchain banking protocol developed by Metallicus",
|
||||
cta: {
|
||||
backgroundImageUrl: metalCTA.src,
|
||||
title: "Connect to The Banking Layer, Metal L2",
|
||||
buttonLink: "https://metall2.com/",
|
||||
buttonText: "Connect Here",
|
||||
},
|
||||
// cta: {
|
||||
// backgroundImageUrl: metalCTA.src,
|
||||
// title: "Connect to The Banking Layer, Metal L2",
|
||||
// buttonLink: "https://metall2.com/",
|
||||
// buttonText: "Connect Here",
|
||||
// },
|
||||
cta: OP_CTA,
|
||||
gasSponsored: true,
|
||||
},
|
||||
//Ozean Testnet
|
||||
7849306: {
|
||||
cta: OP_CTA,
|
||||
gasSponsored: true,
|
||||
},
|
||||
//0G-Newton-Testnet
|
||||
16600: {
|
||||
@@ -566,11 +594,11 @@ const chainMetaRecord = {
|
||||
buttonText: "Learn more",
|
||||
},
|
||||
},
|
||||
//Plume
|
||||
161221135: {
|
||||
//Plume Testnet
|
||||
98864: {
|
||||
headerImgUrl: plumeBanner.src,
|
||||
about:
|
||||
"Plume is the first modular L2 blockchain dedicated for all real-world assets (RWAs) that integrates asset tokenization and compliance providers directly into the chain. Our mission is to simplify the convoluted processes of RWA project deployment and offer investors a blockchain ecosystem to cross-pollinate and invest in various RWAs. In addition, Plume enables RWA composability through its thriving DeFi applications and provides access to high-quality buyers to increase liquidity for all tokenized RWAs.",
|
||||
"Plume Network is the first full-stack L1 RWA Chain and ecosystem purpose-built for RWAfi, enabling the rapid adoption and demand-driven integration of real world assets. With 180+ projects building on the network, Plume offers a composable, EVM-compatible environment for onboarding and managing diverse real world assets. Coupled with an end-to-end tokenization engine and a network of financial infrastructure partners, Plume simplifies asset onboarding and enables seamless DeFi integration for RWAs so anyone can tokenize real world assets, distribute them globally, and make them useful for native crypto users.",
|
||||
cta: {
|
||||
backgroundImageUrl: plumeCTA.src,
|
||||
title: "Participate on the Testnet Now!",
|
||||
@@ -578,27 +606,64 @@ const chainMetaRecord = {
|
||||
buttonText: "Learn more",
|
||||
},
|
||||
},
|
||||
98864: {
|
||||
headerImgUrl: plumeBannerv2.src,
|
||||
//Plume Mainnet
|
||||
98865: {
|
||||
headerImgUrl: plumeBanner.src,
|
||||
about:
|
||||
"Plume is the first full stack L1 blockchain purpose-built for RWAfi. Our products and chain make it easy for anyone to interact with RWAs just like they would with any other crypto native asset. Earn, trade, speculate on anything from financial instruments, collectibles, alternative assets, and more.",
|
||||
"Plume Network is the first full-stack L1 RWA Chain and ecosystem purpose-built for RWAfi, enabling the rapid adoption and demand-driven integration of real world assets. With 180+ projects building on the network, Plume offers a composable, EVM-compatible environment for onboarding and managing diverse real world assets. Coupled with an end-to-end tokenization engine and a network of financial infrastructure partners, Plume simplifies asset onboarding and enables seamless DeFi integration for RWAs so anyone can tokenize real world assets, distribute them globally, and make them useful for native crypto users.",
|
||||
cta: {
|
||||
backgroundImageUrl: plumeCTAv2.src,
|
||||
backgroundImageUrl: plumeCTA.src,
|
||||
title: "Bringing the Real World Onchain",
|
||||
buttonLink: "https://plumenetwork.xyz/",
|
||||
buttonLink: "https://miles.plumenetwork.xyz/",
|
||||
buttonText: "Learn more",
|
||||
},
|
||||
},
|
||||
//Plume
|
||||
98865: {
|
||||
headerImgUrl: plumeBannerv2.src,
|
||||
//Rivalz Mainnet
|
||||
753: {
|
||||
headerImgUrl: rivalzBanner.src,
|
||||
about:
|
||||
"Plume is the first full stack L1 blockchain purpose-built for RWAfi. Our products and chain make it easy for anyone to interact with RWAs just like they would with any other crypto native asset. Earn, trade, speculate on anything from financial instruments, collectibles, alternative assets, and more.",
|
||||
"Rivalz merges real-world economies with AI, redefining how agents interact with global resources. Through its modular infrastructure, Rivalz enables AI agents to tap into the global workforce, digital services, and DePIN—from computers to smart homes—seamlessly connecting AI with reality.",
|
||||
cta: {
|
||||
backgroundImageUrl: plumeCTAv2.src,
|
||||
title: "Bringing the Real World Onchain",
|
||||
buttonLink: "https://plumenetwork.xyz/",
|
||||
buttonText: "Learn more",
|
||||
backgroundImageUrl: rivalzCTA.src,
|
||||
title: "Empower the Age of AI Agents with Rivalz",
|
||||
buttonLink: "https://rivalz.ai/download",
|
||||
buttonText: "Learn More",
|
||||
},
|
||||
},
|
||||
//Rivalz Testnet
|
||||
6966: {
|
||||
headerImgUrl: rivalzBanner.src,
|
||||
about:
|
||||
"Rivalz merges real-world economies with AI, redefining how agents interact with global resources. Through its modular infrastructure, Rivalz enables AI agents to tap into the global workforce, digital services, and DePIN—from computers to smart homes—seamlessly connecting AI with reality.",
|
||||
cta: {
|
||||
backgroundImageUrl: rivalzCTA.src,
|
||||
title: "Empower the Age of AI Agents with Rivalz",
|
||||
buttonLink: "https://rivalz.ai/download",
|
||||
buttonText: "Learn More",
|
||||
},
|
||||
},
|
||||
//Saakuru Mainnet
|
||||
7225878: {
|
||||
headerImgUrl: saakuruBanner.src,
|
||||
about:
|
||||
"Saakuru Labs is a Web3 GameFi innovation and growth hub, dedicated to empowering game studios through tailored collaborations that cater to their unique needs and stages of game development. By providing a structured process to evaluate and support studios, Saakuru Labs aims to expand the Saakuru gaming ecosystem while ensuring the success of its partners.",
|
||||
cta: {
|
||||
backgroundImageUrl: saakuruCTA.src,
|
||||
title: "Empowering Next-Gen Blockchain Gaming",
|
||||
buttonLink: "https://saakuru.com/",
|
||||
buttonText: "Learn More",
|
||||
},
|
||||
},
|
||||
//Saakuru Testnet
|
||||
247253: {
|
||||
headerImgUrl: saakuruBanner.src,
|
||||
about:
|
||||
"Saakuru Labs is a Web3 GameFi innovation and growth hub, dedicated to empowering game studios through tailored collaborations that cater to their unique needs and stages of game development. By providing a structured process to evaluate and support studios, Saakuru Labs aims to expand the Saakuru gaming ecosystem while ensuring the success of its partners.",
|
||||
cta: {
|
||||
backgroundImageUrl: saakuruCTA.src,
|
||||
title: "Empowering Next-Gen Blockchain Gaming",
|
||||
buttonLink: "https://saakuru.com/",
|
||||
buttonText: "Learn More",
|
||||
},
|
||||
},
|
||||
//Somnia Devnet
|
||||
@@ -613,6 +678,63 @@ const chainMetaRecord = {
|
||||
buttonText: "Learn More",
|
||||
},
|
||||
},
|
||||
//Unichain Sepolis Testnet
|
||||
1301: {
|
||||
cta: OP_CTA,
|
||||
gasSponsored: true,
|
||||
},
|
||||
//viction Mainnet
|
||||
88: {
|
||||
headerImgUrl: victionBanner.src,
|
||||
about:
|
||||
"Viction is a global layer-1 EVM blockchain that empowers builders at every level, provides both the foundation and enhancements necessary for projects to achieve stability and soar to their higher selves. \n We provide seamless interoperability, scalability, flexible development, zero gas fees, enhanced security, and speed to enable builders with tech capabilities. Beyond technical advantages, Viction offers holistic guidance through consultation, resources, funding, and strong network connections to foster project growth.",
|
||||
cta: {
|
||||
backgroundImageUrl: victionCTA.src,
|
||||
title:
|
||||
"Build, own, win, and be part of Viction World Wide Chain where everyone scales beyond limits.",
|
||||
buttonLink: "https://thirdweb.com/viction",
|
||||
buttonText: "Learn More",
|
||||
},
|
||||
},
|
||||
//viction Testnet
|
||||
89: {
|
||||
headerImgUrl: victionBanner.src,
|
||||
about:
|
||||
"Viction is a global layer-1 EVM blockchain that empowers builders at every level, provides both the foundation and enhancements necessary for projects to achieve stability and soar to their higher selves. \n We provide seamless interoperability, scalability, flexible development, zero gas fees, enhanced security, and speed to enable builders with tech capabilities. Beyond technical advantages, Viction offers holistic guidance through consultation, resources, funding, and strong network connections to foster project growth.",
|
||||
cta: {
|
||||
backgroundImageUrl: victionCTA.src,
|
||||
title:
|
||||
"Build, own, win, and be part of Viction World Wide Chain where everyone scales beyond limits.",
|
||||
buttonLink: "https://thirdweb.com/viction",
|
||||
buttonText: "Learn More",
|
||||
},
|
||||
},
|
||||
// World Chain Mainnet
|
||||
480: {
|
||||
headerImgUrl: worldChainBanner.src,
|
||||
about:
|
||||
"World Chain is the blockchain for real humans, offering priority blockspace and gas-free transactions for World ID-verified users.",
|
||||
// cta: {
|
||||
// backgroundImageUrl: worldChainCTA.src,
|
||||
// title: "Build for real humans with World Chain",
|
||||
// buttonLink: "https://world.org/world-chain",
|
||||
// buttonText: "Learn More",
|
||||
// },
|
||||
cta: OP_CTA,
|
||||
gasSponsored: true,
|
||||
},
|
||||
//World Chain Testnet
|
||||
4801: {
|
||||
headerImgUrl: worldChainBanner.src,
|
||||
about:
|
||||
"World Chain is the blockchain for real humans, offering priority blockspace and gas-free transactions for World ID-verified users.",
|
||||
cta: {
|
||||
backgroundImageUrl: worldChainCTA.src,
|
||||
title: "Build for real humans with World Chain",
|
||||
buttonLink: "https://world.org/world-chain",
|
||||
buttonText: "Learn More",
|
||||
},
|
||||
},
|
||||
//zkCandy
|
||||
302: {
|
||||
headerImgUrl: thirdwebBanner.src,
|
||||
@@ -721,13 +843,20 @@ const chainMetaRecord = {
|
||||
buttonText: "Learn more",
|
||||
},
|
||||
},
|
||||
//Soneium
|
||||
//Soneium Testnet
|
||||
1946: {
|
||||
headerImgUrl: soneiumBanner.src,
|
||||
about:
|
||||
"Soneium, an Ethereum layer-2 developed by Sony Block Solutions Labs. This versatile chain is a general-purpose blockchain platform that aims to evoke emotion, empower creativity, and meet diverse needs to go mainstream. Soneium will be simplifying blockchain experiences while empowering developers, creators, and communities. Built on accessibility, scalability, and efficiency, it aims to solve real-world problems across industries globally. Soneium will change the way we interact with the internet, opening up a world of innovative applications and unlimited potential for users worldwide.",
|
||||
cta: OP_CTA,
|
||||
},
|
||||
//Soneium Mainnet
|
||||
1868: {
|
||||
headerImgUrl: soneiumBanner.src,
|
||||
about:
|
||||
"Soneium, an Ethereum layer-2 developed by Sony Block Solutions Labs. This versatile chain is a general-purpose blockchain platform that aims to evoke emotion, empower creativity, and meet diverse needs to go mainstream. Soneium will be simplifying blockchain experiences while empowering developers, creators, and communities. Built on accessibility, scalability, and efficiency, it aims to solve real-world problems across industries globally. Soneium will change the way we interact with the internet, opening up a world of innovative applications and unlimited potential for users worldwide.",
|
||||
cta: OP_CTA,
|
||||
},
|
||||
//Shido
|
||||
9008: {
|
||||
headerImgUrl: shidoBanner.src,
|
||||
|
||||