Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8ebbe54d9 | ||
|
|
ca756819db | ||
|
|
7217e774b2 | ||
|
|
5de72c5ecd | ||
|
|
60c5c26583 | ||
|
|
dc74c3b4b9 | ||
|
|
69a921d01f | ||
|
|
9abdb92028 | ||
|
|
6ed2fd13de | ||
|
|
a22457a69c | ||
|
|
ae675db8c8 | ||
|
|
54c9333fc9 | ||
|
|
541cf0b3e9 | ||
|
|
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>
|
||||
|
||||
@@ -26,6 +26,7 @@ import { ToolTipLabel } from "@/components/ui/tooltip";
|
||||
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { TransactionButton } from "components/buttons/TransactionButton";
|
||||
import { addDays, fromUnixTime } from "date-fns";
|
||||
import { useAllChainsData } from "hooks/chains/allChains";
|
||||
@@ -119,22 +120,39 @@ function ClaimableModule(props: ModuleInstanceProps) {
|
||||
claimConditionQuery.data?.startTimestamp === 0 &&
|
||||
claimConditionQuery.data?.endTimestamp === 0;
|
||||
|
||||
const currencyContract = getContract({
|
||||
address: claimConditionQuery.data?.currency || "",
|
||||
chain: props.contract.chain,
|
||||
client: props.contract.client,
|
||||
});
|
||||
const claimConditionCurrency = claimConditionQuery.data?.currency;
|
||||
|
||||
const shouldFetchTokenDecimals =
|
||||
claimConditionQuery.data &&
|
||||
claimConditionQuery.data?.currency !== ZERO_ADDRESS;
|
||||
const shouldFetchCurrencyDecimals =
|
||||
!!claimConditionCurrency && claimConditionCurrency !== ZERO_ADDRESS;
|
||||
|
||||
const currencyDecimalsQuery = useReadContract(decimals, {
|
||||
contract: currencyContract,
|
||||
queryOptions: {
|
||||
enabled: shouldFetchTokenDecimals,
|
||||
const currencyDecimalsQuery = useQuery({
|
||||
queryKey: [
|
||||
"currency-decimals",
|
||||
{
|
||||
contractAddress: props.contract.address,
|
||||
chainId: props.contract.chain.id,
|
||||
claimConditionCurrency,
|
||||
},
|
||||
],
|
||||
queryFn: async () => {
|
||||
if (!claimConditionCurrency) {
|
||||
throw new Error();
|
||||
}
|
||||
const currencyContract = getContract({
|
||||
address: claimConditionCurrency,
|
||||
chain: props.contract.chain,
|
||||
client: props.contract.client,
|
||||
});
|
||||
|
||||
const decimalsVal = await decimals({
|
||||
contract: currencyContract,
|
||||
});
|
||||
|
||||
return decimalsVal;
|
||||
},
|
||||
enabled: shouldFetchCurrencyDecimals,
|
||||
});
|
||||
|
||||
const tokenDecimalsQuery = useReadContract(decimals, {
|
||||
contract: contract,
|
||||
queryOptions: {
|
||||
@@ -265,7 +283,7 @@ function ClaimableModule(props: ModuleInstanceProps) {
|
||||
// claim conditions data is present
|
||||
claimConditionQuery.data &&
|
||||
// token decimals is fetched if it should be fetched
|
||||
(shouldFetchTokenDecimals ? currencyDecimalsQuery.isFetched : true)
|
||||
(shouldFetchCurrencyDecimals ? currencyDecimalsQuery.isFetched : true)
|
||||
? {
|
||||
claimCondition: claimConditionQuery.data,
|
||||
currencyDecimals: currencyDecimalsQuery.data,
|
||||
@@ -276,7 +294,7 @@ function ClaimableModule(props: ModuleInstanceProps) {
|
||||
tokenId,
|
||||
isLoading:
|
||||
claimConditionQuery.isLoading ||
|
||||
(!!shouldFetchTokenDecimals && currencyDecimalsQuery.isLoading),
|
||||
(!!shouldFetchCurrencyDecimals && currencyDecimalsQuery.isLoading),
|
||||
}}
|
||||
isOwnerAccount={!!ownerAccount}
|
||||
name={props.contractInfo.name}
|
||||
@@ -565,6 +583,7 @@ function ClaimConditionSection(props: {
|
||||
onClick={() => setAddClaimConditionButtonClicked(true)}
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
disabled={!props.isOwnerAccount}
|
||||
>
|
||||
Add Claim Condition
|
||||
</Button>
|
||||
|
||||
@@ -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 |