Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9eab56fff2 | ||
|
|
b6fdd0cc36 | ||
|
|
31ce837b54 | ||
|
|
68aa693b18 | ||
|
|
270e53ccaf | ||
|
|
7809c6204e | ||
|
|
31e87b07a8 | ||
|
|
f2d0f119c1 | ||
|
|
dc07fdd952 | ||
|
|
1d598ce907 | ||
|
|
f157e3484a | ||
|
|
d95b3d38c7 | ||
|
|
b50b320144 | ||
|
|
4c339363c0 | ||
|
|
125cc972de | ||
|
|
436750ed15 | ||
|
|
9cf5ba83e2 | ||
|
|
33666aa8c5 | ||
|
|
e526f75f22 | ||
|
|
f912e42342 | ||
|
|
fe46e81cd5 | ||
|
|
525e60393f | ||
|
|
4baefa5dae | ||
|
|
b2e56f89fe | ||
|
|
ec194a623e | ||
|
|
dc2e514e56 | ||
|
|
ec5362b84a | ||
|
|
683e3b0838 | ||
|
|
8b4b873317 | ||
|
|
67a7d3e3a5 | ||
|
|
755e5d40ca | ||
|
|
a2f561f97b | ||
|
|
72a4ac5a98 | ||
|
|
69ec6e0553 | ||
|
|
21cc01a0aa | ||
|
|
c03734aea9 | ||
|
|
8da0c1fdcd | ||
|
|
8dc27ffcc4 | ||
|
|
f85163edee | ||
|
|
7965d93b7a | ||
|
|
4ccf06260b | ||
|
|
d7dc83f9e6 | ||
|
|
9877176f65 | ||
|
|
67768ac375 | ||
|
|
8937d5511e | ||
|
|
bf81deeb35 | ||
|
|
25a7ad7eb9 | ||
|
|
ac8b09f35e | ||
|
|
f7f179c771 | ||
|
|
d0bf3f3f5e | ||
|
|
96689c6e2a | ||
|
|
0c5c918b21 | ||
|
|
e1763e216a | ||
|
|
309d8f81a9 | ||
|
|
9d72ddd604 | ||
|
|
17b83686e4 | ||
|
|
8ee427d590 | ||
|
|
24319f4570 | ||
|
|
f38b262cd0 | ||
|
|
5b35a6227b | ||
|
|
03e238515b | ||
|
|
72875f9de5 | ||
|
|
97ce74f307 | ||
|
|
3a43fb23a4 |
@@ -89,7 +89,7 @@ jobs:
|
||||
- run: pnpm test
|
||||
|
||||
- name: Code Coverage
|
||||
uses: codecov/codecov-action@0565863a31f2c772f9f0395002a31e3f06189574 # v5.4.0
|
||||
uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2
|
||||
with:
|
||||
directory: packages/
|
||||
flags: packages
|
||||
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11
|
||||
uses: github/codeql-action/init@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11
|
||||
uses: github/codeql-action/autobuild@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
@@ -72,4 +72,4 @@ jobs:
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11
|
||||
uses: github/codeql-action/analyze@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15
|
||||
|
||||
@@ -58,4 +58,7 @@ module.exports = [
|
||||
"/faucets",
|
||||
// -- brand kit --
|
||||
"/brand-kit",
|
||||
// -- universal bridge landing pages --
|
||||
"/universal-bridge-regions/:region_slug",
|
||||
"/enterprise",
|
||||
];
|
||||
|
||||
@@ -111,6 +111,9 @@ const SENTRY_OPTIONS: SentryBuildOptions = {
|
||||
automaticVercelMonitors: false,
|
||||
};
|
||||
|
||||
// add additional languages to the framer rewrite paths here (english is already included by default)
|
||||
const FRAMER_ADDITIONAL_LANGUAGES = ["es"];
|
||||
|
||||
const baseNextConfig: NextConfig = {
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
@@ -155,10 +158,18 @@ const baseNextConfig: NextConfig = {
|
||||
source: "/home",
|
||||
destination: "https://landing.thirdweb.com",
|
||||
},
|
||||
...FRAMER_PATHS.map((path) => ({
|
||||
source: path,
|
||||
destination: `https://landing.thirdweb.com${path}`,
|
||||
})),
|
||||
// flatmap the framer paths for the default language and the additional languages
|
||||
...FRAMER_PATHS.flatMap((path) => [
|
||||
{
|
||||
source: path,
|
||||
destination: `https://landing.thirdweb.com${path}`,
|
||||
},
|
||||
// this is for additional languages
|
||||
...FRAMER_ADDITIONAL_LANGUAGES.map((lang) => ({
|
||||
source: `/${lang}${path}`,
|
||||
destination: `https://landing.thirdweb.com/${lang}${path}`,
|
||||
})),
|
||||
]),
|
||||
];
|
||||
},
|
||||
images: {
|
||||
|
||||
+31
-31
@@ -29,26 +29,26 @@
|
||||
"@emotion/styled": "11.14.0",
|
||||
"@hookform/resolvers": "^3.9.1",
|
||||
"@marsidev/react-turnstile": "^1.1.0",
|
||||
"@radix-ui/react-accordion": "^1.2.4",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.7",
|
||||
"@radix-ui/react-avatar": "^1.1.4",
|
||||
"@radix-ui/react-checkbox": "^1.1.5",
|
||||
"@radix-ui/react-dialog": "1.1.7",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.7",
|
||||
"@radix-ui/react-hover-card": "^1.1.7",
|
||||
"@radix-ui/react-label": "^2.1.3",
|
||||
"@radix-ui/react-popover": "^1.1.7",
|
||||
"@radix-ui/react-progress": "^1.1.3",
|
||||
"@radix-ui/react-radio-group": "^1.2.4",
|
||||
"@radix-ui/react-select": "^2.1.7",
|
||||
"@radix-ui/react-separator": "^1.1.3",
|
||||
"@radix-ui/react-accordion": "^1.2.7",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.10",
|
||||
"@radix-ui/react-avatar": "^1.1.6",
|
||||
"@radix-ui/react-checkbox": "^1.2.2",
|
||||
"@radix-ui/react-dialog": "1.1.10",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.11",
|
||||
"@radix-ui/react-hover-card": "^1.1.10",
|
||||
"@radix-ui/react-label": "^2.1.4",
|
||||
"@radix-ui/react-popover": "^1.1.10",
|
||||
"@radix-ui/react-progress": "^1.1.4",
|
||||
"@radix-ui/react-radio-group": "^1.3.3",
|
||||
"@radix-ui/react-select": "^2.2.2",
|
||||
"@radix-ui/react-separator": "^1.1.4",
|
||||
"@radix-ui/react-slot": "^1.2.0",
|
||||
"@radix-ui/react-switch": "^1.1.4",
|
||||
"@radix-ui/react-tooltip": "1.2.0",
|
||||
"@sentry/nextjs": "9.12.0",
|
||||
"@radix-ui/react-switch": "^1.2.2",
|
||||
"@radix-ui/react-tooltip": "1.2.3",
|
||||
"@sentry/nextjs": "9.13.0",
|
||||
"@shazow/whatsabi": "0.21.0",
|
||||
"@tanstack/react-query": "5.72.1",
|
||||
"@tanstack/react-table": "^8.21.2",
|
||||
"@tanstack/react-query": "5.74.4",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@thirdweb-dev/service-utils": "workspace:*",
|
||||
"@vercel/functions": "2.0.0",
|
||||
"@vercel/og": "^0.6.8",
|
||||
@@ -66,14 +66,14 @@
|
||||
"fuse.js": "7.1.0",
|
||||
"idb-keyval": "^6.2.1",
|
||||
"input-otp": "^1.4.1",
|
||||
"ioredis": "^5.6.0",
|
||||
"ioredis": "^5.6.1",
|
||||
"ipaddr.js": "^2.2.0",
|
||||
"lucide-react": "0.487.0",
|
||||
"next": "15.2.4",
|
||||
"lucide-react": "0.503.0",
|
||||
"next": "15.3.1",
|
||||
"next-plausible": "^3.12.4",
|
||||
"next-themes": "^0.4.6",
|
||||
"nextjs-toploader": "^1.6.12",
|
||||
"nuqs": "^2.4.1",
|
||||
"nuqs": "^2.4.3",
|
||||
"p-limit": "^6.2.0",
|
||||
"papaparse": "^5.5.2",
|
||||
"pluralize": "^8.0.0",
|
||||
@@ -89,7 +89,7 @@
|
||||
"react-hook-form": "7.55.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-table": "^7.8.0",
|
||||
"recharts": "2.15.2",
|
||||
"recharts": "2.15.3",
|
||||
"remark-gfm": "4.0.1",
|
||||
"responsive-rsc": "0.0.7",
|
||||
"server-only": "^0.0.1",
|
||||
@@ -97,20 +97,20 @@
|
||||
"sonner": "2.0.3",
|
||||
"spdx-correct": "^3.2.0",
|
||||
"stripe": "17.7.0",
|
||||
"swagger-ui-react": "^5.20.7",
|
||||
"swagger-ui-react": "^5.21.0",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"thirdweb": "workspace:*",
|
||||
"tiny-invariant": "^1.3.3",
|
||||
"use-debounce": "^10.0.4",
|
||||
"zod": "3.24.2"
|
||||
"zod": "3.24.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chakra-ui/cli": "^2.4.1",
|
||||
"@chromatic-com/storybook": "3.2.6",
|
||||
"@next/bundle-analyzer": "15.2.4",
|
||||
"@next/eslint-plugin-next": "15.2.4",
|
||||
"@playwright/test": "1.51.1",
|
||||
"@next/bundle-analyzer": "15.3.1",
|
||||
"@next/eslint-plugin-next": "15.3.1",
|
||||
"@playwright/test": "1.52.0",
|
||||
"@storybook/addon-essentials": "8.6.12",
|
||||
"@storybook/addon-interactions": "8.6.12",
|
||||
"@storybook/addon-links": "8.6.12",
|
||||
@@ -121,11 +121,11 @@
|
||||
"@storybook/react": "8.6.12",
|
||||
"@storybook/test": "8.6.12",
|
||||
"@types/color": "4.2.0",
|
||||
"@types/node": "22.14.0",
|
||||
"@types/node": "22.14.1",
|
||||
"@types/papaparse": "^5.3.15",
|
||||
"@types/pluralize": "^0.0.33",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react": "19.1.0",
|
||||
"@types/react": "19.1.2",
|
||||
"@types/react-dom": "19.1.2",
|
||||
"@types/react-table": "^7.7.20",
|
||||
"@types/spdx-correct": "^3.1.3",
|
||||
@@ -138,7 +138,7 @@
|
||||
"eslint-config-biome": "1.9.4",
|
||||
"eslint-plugin-react-compiler": "19.0.0-beta-e993439-20250405",
|
||||
"eslint-plugin-storybook": "0.12.0",
|
||||
"knip": "5.47.0",
|
||||
"knip": "5.50.5",
|
||||
"next-sitemap": "^4.2.3",
|
||||
"postcss": "8.5.3",
|
||||
"storybook": "8.6.12",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use server";
|
||||
|
||||
import { getAuthToken } from "../../app/api/lib/getAuthToken";
|
||||
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
|
||||
import { API_SERVER_URL } from "../constants/env";
|
||||
|
||||
export async function acceptInvite(options: {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import "server-only";
|
||||
|
||||
import { API_SERVER_URL } from "@/constants/env";
|
||||
import { getAuthToken } from "../../app/api/lib/getAuthToken";
|
||||
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
|
||||
import type { ProductSKU } from "../lib/billing";
|
||||
|
||||
export type GetBillingCheckoutUrlOptions = {
|
||||
@@ -64,6 +64,83 @@ export async function getBillingCheckoutUrl(
|
||||
|
||||
export type GetBillingCheckoutUrlAction = typeof getBillingCheckoutUrl;
|
||||
|
||||
export async function getPlanCancelUrl(options: {
|
||||
teamId: string;
|
||||
redirectUrl: string;
|
||||
}): Promise<{ status: number; url?: string }> {
|
||||
const token = await getAuthToken();
|
||||
if (!token) {
|
||||
return {
|
||||
status: 401,
|
||||
};
|
||||
}
|
||||
|
||||
const res = await fetch(
|
||||
`${API_SERVER_URL}/v1/teams/${options.teamId}/checkout/cancel-plan-link`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
redirectTo: options.redirectUrl,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return {
|
||||
status: res.status,
|
||||
};
|
||||
}
|
||||
|
||||
const json = await res.json();
|
||||
|
||||
if (!json.result) {
|
||||
return {
|
||||
status: 500,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
url: json.result as string,
|
||||
};
|
||||
}
|
||||
|
||||
export async function reSubscribePlan(options: {
|
||||
teamId: string;
|
||||
}): Promise<{ status: number }> {
|
||||
const token = await getAuthToken();
|
||||
if (!token) {
|
||||
return {
|
||||
status: 401,
|
||||
};
|
||||
}
|
||||
|
||||
const res = await fetch(
|
||||
`${API_SERVER_URL}/v1/teams/${options.teamId}/checkout/resubscribe-plan`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({}),
|
||||
},
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return {
|
||||
status: res.status,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
};
|
||||
}
|
||||
export type GetBillingPortalUrlOptions = {
|
||||
teamSlug: string | undefined;
|
||||
redirectUrl: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use server";
|
||||
|
||||
import { getAuthToken } from "../../app/api/lib/getAuthToken";
|
||||
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
|
||||
import { API_SERVER_URL } from "../constants/env";
|
||||
|
||||
export async function confirmEmailWithOTP(otp: string) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use server";
|
||||
|
||||
import { getRawAccount } from "../../app/account/settings/getAccount";
|
||||
import { getRawAccount } from "../../app/(app)/account/settings/getAccount";
|
||||
|
||||
export async function getRawAccountAction() {
|
||||
return getRawAccount();
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
"use server";
|
||||
|
||||
import { getThirdwebClient } from "@/constants/thirdweb.server";
|
||||
import { defineDashboardChain } from "lib/defineDashboardChain";
|
||||
import { ZERO_ADDRESS, isAddress, toTokens } from "thirdweb";
|
||||
import { getWalletBalance } from "thirdweb/wallets";
|
||||
import { getUserThirdwebClient } from "../../app/(app)/api/lib/getAuthToken";
|
||||
|
||||
type BalanceQueryResponse = Array<{
|
||||
balance: string;
|
||||
@@ -24,6 +23,7 @@ export async function getTokenBalancesFromMoralis(params: {
|
||||
error: string;
|
||||
}
|
||||
> {
|
||||
const client = await getUserThirdwebClient();
|
||||
const { contractAddress, chainId } = params;
|
||||
|
||||
if (!isAddress(contractAddress)) {
|
||||
@@ -39,7 +39,7 @@ export async function getTokenBalancesFromMoralis(params: {
|
||||
const balance = await getWalletBalance({
|
||||
address: contractAddress,
|
||||
chain,
|
||||
client: getThirdwebClient(),
|
||||
client,
|
||||
});
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
|
||||
import {
|
||||
generateAlchemyUrl,
|
||||
isAlchemySupported,
|
||||
transformAlchemyResponseToNFT,
|
||||
} from "lib/wallet/nfts/alchemy";
|
||||
import {
|
||||
generateMoralisUrl,
|
||||
isMoralisSupported,
|
||||
transformMoralisResponseToNFT,
|
||||
} from "lib/wallet/nfts/moralis";
|
||||
import type { WalletNFT } from "lib/wallet/nfts/types";
|
||||
import { getVercelEnv } from "../../lib/vercel-utils";
|
||||
import { isAlchemySupported } from "../../lib/wallet/nfts/isAlchemySupported";
|
||||
import { isMoralisSupported } from "../../lib/wallet/nfts/isMoralisSupported";
|
||||
import { DASHBOARD_THIRDWEB_CLIENT_ID } from "../constants/env";
|
||||
|
||||
type WalletNFTApiReturn =
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use server";
|
||||
|
||||
import { getAuthToken } from "../../app/api/lib/getAuthToken";
|
||||
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
|
||||
import { API_SERVER_URL } from "../constants/env";
|
||||
|
||||
type ProxyActionParams = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use server";
|
||||
|
||||
import { getAuthToken } from "../../app/api/lib/getAuthToken";
|
||||
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
|
||||
import { API_SERVER_URL } from "../constants/env";
|
||||
|
||||
export async function sendTeamInvites(options: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"use server";
|
||||
import { getAuthToken } from "../../app/api/lib/getAuthToken";
|
||||
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
|
||||
import { API_SERVER_URL } from "../constants/env";
|
||||
|
||||
export async function updateAccount(values: {
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
"use server";
|
||||
|
||||
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { cookies } from "next/headers";
|
||||
import { getAddress } from "thirdweb";
|
||||
import { COOKIE_PREFIX_TOKEN } from "../constants/cookie";
|
||||
import { API_SERVER_URL } from "../constants/env";
|
||||
|
||||
/**
|
||||
* Check that the connected wallet is valid for the current active account
|
||||
*/
|
||||
export async function isWalletValidForActiveAccount(params: {
|
||||
address: string;
|
||||
account: Account;
|
||||
authToken: string;
|
||||
}) {
|
||||
const cookieStore = await cookies();
|
||||
const authCookieNameForAddress =
|
||||
COOKIE_PREFIX_TOKEN + getAddress(params.address);
|
||||
|
||||
// authToken for this wallet address should be present
|
||||
const authTokenForAddress = cookieStore.get(authCookieNameForAddress)?.value;
|
||||
if (!authTokenForAddress) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// this authToken should be same as current active authToken
|
||||
if (authTokenForAddress !== params.authToken) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// authToken should be valid
|
||||
const accountRes = await fetch(`${API_SERVER_URL}/v1/account/me`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${authTokenForAddress}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (accountRes.status !== 200) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const account = (await accountRes.json()).data as Account;
|
||||
|
||||
// the current account should match the account fetched for the authToken
|
||||
return account.id === params.account.id;
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import type {
|
||||
WalletStats,
|
||||
WalletUserStats,
|
||||
} from "types/analytics";
|
||||
import { getAuthToken } from "../../app/api/lib/getAuthToken";
|
||||
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
|
||||
import { getChains } from "./chain";
|
||||
|
||||
async function fetchAnalytics(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "server-only";
|
||||
import type { ChainMetadata } from "thirdweb/chains";
|
||||
import type { ChainService } from "../../app/(dashboard)/(chain)/types/chain";
|
||||
import type { ChainService } from "../../app/(app)/(dashboard)/(chain)/types/chain";
|
||||
import { API_SERVER_URL, THIRDWEB_API_SECRET } from "../constants/env";
|
||||
|
||||
export async function getGasSponsoredChains() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getAuthToken } from "../../app/api/lib/getAuthToken";
|
||||
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
|
||||
import { API_SERVER_URL } from "../constants/env";
|
||||
|
||||
export type LinkedWallet = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import "server-only";
|
||||
import { API_SERVER_URL } from "@/constants/env";
|
||||
import type { ProjectResponse } from "@thirdweb-dev/service-utils";
|
||||
import { getAuthToken } from "../../app/api/lib/getAuthToken";
|
||||
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
|
||||
|
||||
export type Project = ProjectResponse;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getAuthToken } from "../../app/api/lib/getAuthToken";
|
||||
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
|
||||
import { API_SERVER_URL } from "../constants/env";
|
||||
|
||||
export type TeamInvite = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "server-only";
|
||||
import { API_SERVER_URL } from "@/constants/env";
|
||||
import { getAuthToken } from "../../app/api/lib/getAuthToken";
|
||||
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
|
||||
|
||||
const TeamAccountRole = {
|
||||
OWNER: "OWNER",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getAuthToken } from "../../app/api/lib/getAuthToken";
|
||||
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
|
||||
import { API_SERVER_URL } from "../constants/env";
|
||||
import type { ProductSKU } from "../lib/billing";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import "server-only";
|
||||
import { API_SERVER_URL, THIRDWEB_API_SECRET } from "@/constants/env";
|
||||
import type { TeamResponse } from "@thirdweb-dev/service-utils";
|
||||
import { getAuthToken } from "../../app/api/lib/getAuthToken";
|
||||
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
|
||||
|
||||
export type Team = TeamResponse & { stripeCustomerId: string | null };
|
||||
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
"use server";
|
||||
import { getAuthToken } from "app/(app)/api/lib/getAuthToken";
|
||||
|
||||
const UB_BASE_URL = process.env.NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST;
|
||||
|
||||
type Webhook = {
|
||||
url: string;
|
||||
label: string;
|
||||
active: boolean;
|
||||
createdAt: string;
|
||||
id: string;
|
||||
secret: string;
|
||||
version?: number; // TODO (UB) make this mandatory after migration
|
||||
};
|
||||
|
||||
export async function getWebhooks(props: {
|
||||
clientId: string;
|
||||
}) {
|
||||
const authToken = await getAuthToken();
|
||||
const res = await fetch(`${UB_BASE_URL}/v1/developer/webhooks`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-client-id-override": props.clientId,
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
throw new Error(text);
|
||||
}
|
||||
|
||||
const json = await res.json();
|
||||
return json.data as Array<Webhook>;
|
||||
}
|
||||
|
||||
export async function createWebhook(props: {
|
||||
clientId: string;
|
||||
version?: number;
|
||||
url: string;
|
||||
label: string;
|
||||
secret?: string;
|
||||
}) {
|
||||
const authToken = await getAuthToken();
|
||||
const res = await fetch(`${UB_BASE_URL}/v1/developer/webhooks`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
url: props.url,
|
||||
label: props.label,
|
||||
version: props.version,
|
||||
secret: props.secret,
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-client-id-override": props.clientId,
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
throw new Error(text);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
export async function deleteWebhook(props: {
|
||||
clientId: string;
|
||||
webhookId: string;
|
||||
}) {
|
||||
const authToken = await getAuthToken();
|
||||
const res = await fetch(
|
||||
`${UB_BASE_URL}/v1/developer/webhooks/${props.webhookId}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-client-id-override": props.clientId,
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
throw new Error(text);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
export type Fee = {
|
||||
feeRecipient: string;
|
||||
feeBps: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export async function getFees(props: {
|
||||
clientId: string;
|
||||
}) {
|
||||
const authToken = await getAuthToken();
|
||||
const res = await fetch(`${UB_BASE_URL}/v1/developer/fees`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-client-id-override": props.clientId,
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
throw new Error(text);
|
||||
}
|
||||
|
||||
const json = await res.json();
|
||||
return json.data as Fee;
|
||||
}
|
||||
|
||||
export async function updateFee(props: {
|
||||
clientId: string;
|
||||
feeRecipient: string;
|
||||
feeBps: number;
|
||||
}) {
|
||||
const authToken = await getAuthToken();
|
||||
const res = await fetch(`${UB_BASE_URL}/v1/developer/fees`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-client-id-override": props.clientId,
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
feeRecipient: props.feeRecipient,
|
||||
feeBps: props.feeBps,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
throw new Error(text);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -56,19 +56,20 @@ export const fetchRPCUsage = unstable_cache(
|
||||
type Last24HoursRPCUsageApiResponse = {
|
||||
peakRate: {
|
||||
date: string;
|
||||
peakRPS: number;
|
||||
peakRPS: string;
|
||||
};
|
||||
averageRate: {
|
||||
date: string;
|
||||
averageRate: number;
|
||||
includedCount: number;
|
||||
rateLimitedCount: number;
|
||||
overageCount: number;
|
||||
averageRate: string;
|
||||
peakRPS: string;
|
||||
includedCount: string;
|
||||
rateLimitedCount: string;
|
||||
overageCount: string;
|
||||
}[];
|
||||
totalCounts: {
|
||||
includedCount: number;
|
||||
rateLimitedCount: number;
|
||||
overageCount: number;
|
||||
includedCount: string;
|
||||
rateLimitedCount: string;
|
||||
overageCount: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -106,7 +107,7 @@ export const getLast24HoursRPCUsage = unstable_cache(
|
||||
data: resData.data as Last24HoursRPCUsageApiResponse,
|
||||
};
|
||||
},
|
||||
["rpc-usage-last-24-hours"],
|
||||
["rpc-usage-last-24-hours:v2"],
|
||||
{
|
||||
revalidate: 60, // 1 minute
|
||||
},
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { useState } from "react";
|
||||
import { BadgeContainer } from "../../../../stories/utils";
|
||||
import { getThirdwebClient } from "../../../constants/thirdweb.server";
|
||||
import {
|
||||
BadgeContainer,
|
||||
storybookThirdwebClient,
|
||||
} from "../../../../stories/utils";
|
||||
import { Button } from "../../ui/button";
|
||||
import { GradientAvatar } from "./GradientAvatar";
|
||||
|
||||
@@ -17,8 +19,6 @@ export const Variants: Story = {
|
||||
args: {},
|
||||
};
|
||||
|
||||
const client = getThirdwebClient();
|
||||
|
||||
function Story() {
|
||||
return (
|
||||
<div className="container flex max-w-6xl flex-col gap-10 py-10">
|
||||
@@ -29,7 +29,7 @@ function Story() {
|
||||
id={undefined}
|
||||
src={undefined}
|
||||
className="size-20"
|
||||
client={client}
|
||||
client={storybookThirdwebClient}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
@@ -38,7 +38,7 @@ function Story() {
|
||||
id={"foo"}
|
||||
src={undefined}
|
||||
className="size-20"
|
||||
client={client}
|
||||
client={storybookThirdwebClient}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
@@ -47,7 +47,7 @@ function Story() {
|
||||
id={"foo"}
|
||||
src={""}
|
||||
className="size-20"
|
||||
client={client}
|
||||
client={storybookThirdwebClient}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
@@ -56,7 +56,7 @@ function Story() {
|
||||
id={"bar"}
|
||||
src={""}
|
||||
className="size-20"
|
||||
client={client}
|
||||
client={storybookThirdwebClient}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
@@ -65,7 +65,7 @@ function Story() {
|
||||
src="invalid-src"
|
||||
id={undefined}
|
||||
className="size-20"
|
||||
client={client}
|
||||
client={storybookThirdwebClient}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
@@ -74,7 +74,7 @@ function Story() {
|
||||
src="https://picsum.photos/200/300"
|
||||
id={undefined}
|
||||
className="size-20"
|
||||
client={client}
|
||||
client={storybookThirdwebClient}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
@@ -83,7 +83,7 @@ function Story() {
|
||||
src="ipfs://QmZbeJYEs7kCJHyQxjxU2SJUtjSAr4m87wzJFJUyWomKdj/Smily.svg"
|
||||
id={undefined}
|
||||
className="size-20"
|
||||
client={client}
|
||||
client={storybookThirdwebClient}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
@@ -123,7 +123,7 @@ function ToggleTest() {
|
||||
src={data?.src}
|
||||
id={data?.id}
|
||||
className="size-20"
|
||||
client={client}
|
||||
client={storybookThirdwebClient}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
@@ -132,7 +132,7 @@ function ToggleTest() {
|
||||
className="size-20"
|
||||
src={data ? "invalid-src" : undefined}
|
||||
id={undefined}
|
||||
client={client}
|
||||
client={storybookThirdwebClient}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { useState } from "react";
|
||||
import { BadgeContainer } from "../../../../stories/utils";
|
||||
import { getThirdwebClient } from "../../../constants/thirdweb.server";
|
||||
import {
|
||||
BadgeContainer,
|
||||
storybookThirdwebClient,
|
||||
} from "../../../../stories/utils";
|
||||
import { Button } from "../../ui/button";
|
||||
import { ProjectAvatar } from "./ProjectAvatar";
|
||||
|
||||
@@ -18,19 +20,25 @@ export const Variants: Story = {
|
||||
args: {},
|
||||
};
|
||||
|
||||
const client = getThirdwebClient();
|
||||
|
||||
function Story() {
|
||||
return (
|
||||
<div className="container flex max-w-6xl flex-col gap-10 py-10">
|
||||
<p> All images below are set with size-6 className </p>
|
||||
|
||||
<BadgeContainer label="No Src - Skeleton">
|
||||
<ProjectAvatar src={undefined} className="size-6" client={client} />
|
||||
<ProjectAvatar
|
||||
src={undefined}
|
||||
className="size-6"
|
||||
client={storybookThirdwebClient}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
<BadgeContainer label="Invalid/Empty Src - BoxIcon Fallback">
|
||||
<ProjectAvatar src={""} className="size-6" client={client} />
|
||||
<ProjectAvatar
|
||||
src={""}
|
||||
className="size-6"
|
||||
client={storybookThirdwebClient}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
<ToggleTest />
|
||||
@@ -65,14 +73,18 @@ function ToggleTest() {
|
||||
<p> Src+Name is: {data ? "set" : "not set"} </p>
|
||||
|
||||
<BadgeContainer label="Valid Src">
|
||||
<ProjectAvatar src={data?.src} className="size-6" client={client} />
|
||||
<ProjectAvatar
|
||||
src={data?.src}
|
||||
className="size-6"
|
||||
client={storybookThirdwebClient}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
<BadgeContainer label="invalid Src">
|
||||
<ProjectAvatar
|
||||
src={data ? "invalid-src" : undefined}
|
||||
className="size-6"
|
||||
client={client}
|
||||
client={storybookThirdwebClient}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { useState } from "react";
|
||||
import { BadgeContainer } from "../../../stories/utils";
|
||||
import {
|
||||
BadgeContainer,
|
||||
storybookThirdwebClient,
|
||||
} from "../../../stories/utils";
|
||||
import { MultiNetworkSelector } from "./NetworkSelectors";
|
||||
|
||||
const meta = {
|
||||
@@ -41,6 +44,7 @@ function Variant(props: {
|
||||
<BadgeContainer label={props.label}>
|
||||
<MultiNetworkSelector
|
||||
selectedChainIds={chainIds}
|
||||
client={storybookThirdwebClient}
|
||||
onChange={setChainIds}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { MultiSelect } from "@/components/blocks/multi-select";
|
||||
import { SelectWithSearch } from "@/components/blocks/select-with-search";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import type { ThirdwebClient } from "thirdweb";
|
||||
import { ChainIconClient } from "../../../components/icons/ChainIcon";
|
||||
import { useAllChainsData } from "../../../hooks/chains/allChains";
|
||||
|
||||
@@ -20,6 +21,12 @@ export function MultiNetworkSelector(props: {
|
||||
className?: string;
|
||||
priorityChains?: number[];
|
||||
hideTestnets?: boolean;
|
||||
popoverContentClassName?: string;
|
||||
customTrigger?: React.ReactNode;
|
||||
align?: "center" | "start" | "end";
|
||||
side?: "left" | "right" | "top" | "bottom";
|
||||
showSelectedValuesInModal?: boolean;
|
||||
client: ThirdwebClient;
|
||||
}) {
|
||||
const { allChains, idToChain } = useAllChainsData();
|
||||
|
||||
@@ -84,8 +91,9 @@ export function MultiNetworkSelector(props: {
|
||||
<span className="flex grow gap-2 truncate text-left">
|
||||
<ChainIconClient
|
||||
className="size-5"
|
||||
ipfsSrc={chain.icon?.url}
|
||||
loading="lazy"
|
||||
src={chain.icon?.url}
|
||||
client={props.client}
|
||||
/>
|
||||
{cleanChainName(chain.name)}
|
||||
</span>
|
||||
@@ -99,14 +107,19 @@ export function MultiNetworkSelector(props: {
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[idToChain, props.disableChainId],
|
||||
[idToChain, props.disableChainId, props.client],
|
||||
);
|
||||
|
||||
return (
|
||||
<MultiSelect
|
||||
searchPlaceholder="Search by Name or Chain Id"
|
||||
selectedValues={props.selectedChainIds.map(String)}
|
||||
popoverContentClassName={props.popoverContentClassName}
|
||||
customTrigger={props.customTrigger}
|
||||
options={options}
|
||||
align={props.align}
|
||||
side={props.side}
|
||||
showSelectedValuesInModal={props.showSelectedValuesInModal}
|
||||
onSelectedValuesChange={(chainIds) => {
|
||||
props.onChange(chainIds.map(Number));
|
||||
}}
|
||||
@@ -132,6 +145,7 @@ export function SingleNetworkSelector(props: {
|
||||
disableChainId?: boolean;
|
||||
align?: "center" | "start" | "end";
|
||||
placeholder?: string;
|
||||
client: ThirdwebClient;
|
||||
}) {
|
||||
const { allChains, idToChain } = useAllChainsData();
|
||||
|
||||
@@ -179,7 +193,8 @@ export function SingleNetworkSelector(props: {
|
||||
<span className="flex grow gap-2 truncate text-left">
|
||||
<ChainIconClient
|
||||
className="size-5"
|
||||
ipfsSrc={chain.icon?.url}
|
||||
src={chain.icon?.url}
|
||||
client={props.client}
|
||||
loading="lazy"
|
||||
/>
|
||||
{cleanChainName(chain.name)}
|
||||
@@ -194,7 +209,7 @@ export function SingleNetworkSelector(props: {
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[idToChain, props.disableChainId],
|
||||
[idToChain, props.disableChainId, props.client],
|
||||
);
|
||||
|
||||
const isLoadingChains = allChains.length === 0;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { useState } from "react";
|
||||
import { BadgeContainer } from "../../../stories/utils";
|
||||
import {
|
||||
BadgeContainer,
|
||||
storybookThirdwebClient,
|
||||
} from "../../../stories/utils";
|
||||
import { SingleNetworkSelector } from "./NetworkSelectors";
|
||||
|
||||
const meta = {
|
||||
@@ -43,6 +46,7 @@ function Variant(props: {
|
||||
return (
|
||||
<BadgeContainer label={props.label}>
|
||||
<SingleNetworkSelector
|
||||
client={storybookThirdwebClient}
|
||||
chainId={chainId}
|
||||
onChange={setChainId}
|
||||
chainIds={props.chainIds}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ThirdwebMiniLogo } from "app/components/ThirdwebMiniLogo";
|
||||
import { ThirdwebMiniLogo } from "app/(app)/components/ThirdwebMiniLogo";
|
||||
import { GithubIcon } from "components/icons/brand-icons/GithubIcon";
|
||||
import { InstagramIcon } from "components/icons/brand-icons/InstagramIcon";
|
||||
import { LinkedInIcon } from "components/icons/brand-icons/LinkedinIcon";
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
@@ -31,18 +30,19 @@ type ThirdwebAreaChartProps<TConfig extends ChartConfig> = {
|
||||
description?: string;
|
||||
titleClassName?: string;
|
||||
};
|
||||
footer?: React.ReactNode;
|
||||
customHeader?: React.ReactNode;
|
||||
// chart config
|
||||
config: TConfig;
|
||||
data: Array<Record<keyof TConfig, number> & { time: number | string | Date }>;
|
||||
showLegend?: boolean;
|
||||
maxLimit?: number;
|
||||
|
||||
yAxis?: boolean;
|
||||
xAxis?: {
|
||||
sameDay?: boolean;
|
||||
};
|
||||
|
||||
variant?: "stacked" | "individual";
|
||||
|
||||
// chart className
|
||||
chartClassName?: string;
|
||||
isPending: boolean;
|
||||
@@ -77,17 +77,7 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
|
||||
) : props.data.length === 0 ? (
|
||||
<EmptyChartState />
|
||||
) : (
|
||||
<AreaChart
|
||||
accessibilityLayer
|
||||
data={
|
||||
props.maxLimit
|
||||
? props.data.map((d) => ({
|
||||
...d,
|
||||
maxLimit: props.maxLimit,
|
||||
}))
|
||||
: props.data
|
||||
}
|
||||
>
|
||||
<AreaChart accessibilityLayer data={props.data}>
|
||||
<CartesianGrid vertical={false} />
|
||||
{props.yAxis && <YAxis tickLine={false} axisLine={false} />}
|
||||
<XAxis
|
||||
@@ -136,26 +126,28 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
|
||||
</linearGradient>
|
||||
))}
|
||||
</defs>
|
||||
{configKeys.map((key) => (
|
||||
<Area
|
||||
key={key}
|
||||
dataKey={key}
|
||||
type="natural"
|
||||
fill={`url(#fill_${key})`}
|
||||
fillOpacity={0.4}
|
||||
stroke={`var(--color-${key})`}
|
||||
stackId="a"
|
||||
/>
|
||||
))}
|
||||
{props.maxLimit && (
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="maxLimit"
|
||||
stroke="#ef4444"
|
||||
strokeWidth={2}
|
||||
strokeDasharray="5 5"
|
||||
fill="none"
|
||||
/>
|
||||
{configKeys.map((key) =>
|
||||
key === "maxLine" ? (
|
||||
<Area
|
||||
key={key}
|
||||
type="monotone"
|
||||
dataKey="maxLine"
|
||||
stroke="#ef4444"
|
||||
strokeWidth={2}
|
||||
strokeDasharray="5 5"
|
||||
fill="none"
|
||||
/>
|
||||
) : (
|
||||
<Area
|
||||
key={key}
|
||||
dataKey={key}
|
||||
type="natural"
|
||||
fill={`url(#fill_${key})`}
|
||||
fillOpacity={0.4}
|
||||
stroke={`var(--color-${key})`}
|
||||
stackId={props.variant !== "stacked" ? undefined : "a"}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
|
||||
{props.showLegend && (
|
||||
@@ -167,9 +159,6 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
|
||||
)}
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
{props.footer && (
|
||||
<CardFooter className="w-full">{props.footer}</CardFooter>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "@/components/ui/select";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { useState } from "react";
|
||||
import { BadgeContainer } from "../../../stories/utils";
|
||||
import { BadgeContainer } from "stories/utils";
|
||||
import { type CodeEnvironment, CodeSegment } from "./code-segment.client";
|
||||
|
||||
const meta = {
|
||||
|
||||
@@ -47,7 +47,12 @@ interface MultiSelectProps
|
||||
searchTerm: string,
|
||||
) => boolean;
|
||||
|
||||
popoverContentClassName?: string;
|
||||
customTrigger?: React.ReactNode;
|
||||
renderOption?: (option: { value: string; label: string }) => React.ReactNode;
|
||||
align?: "center" | "start" | "end";
|
||||
side?: "left" | "right" | "top" | "bottom";
|
||||
showSelectedValuesInModal?: boolean;
|
||||
}
|
||||
|
||||
export const MultiSelect = forwardRef<HTMLButtonElement, MultiSelectProps>(
|
||||
@@ -62,6 +67,8 @@ export const MultiSelect = forwardRef<HTMLButtonElement, MultiSelectProps>(
|
||||
overrideSearchFn,
|
||||
renderOption,
|
||||
searchPlaceholder,
|
||||
popoverContentClassName,
|
||||
showSelectedValuesInModal = false,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
@@ -155,111 +162,100 @@ export const MultiSelect = forwardRef<HTMLButtonElement, MultiSelectProps>(
|
||||
return (
|
||||
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen} modal>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
ref={ref}
|
||||
{...props}
|
||||
onClick={handleTogglePopover}
|
||||
className={cn(
|
||||
"flex h-auto min-h-10 w-full items-center justify-between rounded-md border border-border bg-inherit p-3 hover:bg-inherit",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{selectedValues.length > 0 ? (
|
||||
<div className="flex w-full justify-between">
|
||||
{/* badges */}
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
{selectedValues.slice(0, maxCount).map((value) => {
|
||||
const option = options.find((o) => o.value === value);
|
||||
if (!option) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ClosableBadge
|
||||
key={value}
|
||||
label={option.label}
|
||||
onClose={() => toggleOption(value)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* +X more */}
|
||||
{selectedValues.length > maxCount && (
|
||||
<ClosableBadge
|
||||
label={`+ ${selectedValues.length - maxCount} more`}
|
||||
onClose={clearExtraOptions}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
{/* Clear All */}
|
||||
{/* biome-ignore lint/a11y/useKeyWithClickEvents: <explanation> */}
|
||||
{/* biome-ignore lint/a11y/useFocusableInteractive: <explanation> */}
|
||||
<div
|
||||
role="button"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
handleClear();
|
||||
}}
|
||||
className="rounded p-1 hover:bg-accent"
|
||||
>
|
||||
<XIcon className="h-4 cursor-pointer text-muted-foreground" />
|
||||
</div>
|
||||
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
className="flex h-full min-h-6"
|
||||
{props.customTrigger || (
|
||||
<Button
|
||||
ref={ref}
|
||||
{...props}
|
||||
onClick={handleTogglePopover}
|
||||
className={cn(
|
||||
"flex h-auto min-h-10 w-full items-center justify-between rounded-md border border-border bg-inherit p-3 hover:bg-inherit",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{selectedValues.length > 0 ? (
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<SelectedChainsBadges
|
||||
selectedValues={selectedValues}
|
||||
options={options}
|
||||
maxCount={maxCount}
|
||||
onClose={handleClear}
|
||||
toggleOption={toggleOption}
|
||||
clearExtraOptions={clearExtraOptions}
|
||||
/>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
{/* Clear All */}
|
||||
{/* biome-ignore lint/a11y/useKeyWithClickEvents: <explanation> */}
|
||||
{/* biome-ignore lint/a11y/useFocusableInteractive: <explanation> */}
|
||||
<div
|
||||
role="button"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
handleClear();
|
||||
}}
|
||||
className="rounded p-1 hover:bg-accent"
|
||||
>
|
||||
<XIcon className="h-4 cursor-pointer text-muted-foreground" />
|
||||
</div>
|
||||
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
className="flex h-full min-h-6"
|
||||
/>
|
||||
<ChevronDown className="h-4 cursor-pointer text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<span className="text-muted-foreground text-sm">
|
||||
{placeholder}
|
||||
</span>
|
||||
<ChevronDown className="h-4 cursor-pointer text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<span className="text-muted-foreground text-sm">
|
||||
{placeholder}
|
||||
</span>
|
||||
<ChevronDown className="h-4 cursor-pointer text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="p-0"
|
||||
align="center"
|
||||
className={cn(
|
||||
"flex max-h-[60vh] flex-col p-0",
|
||||
popoverContentClassName,
|
||||
)}
|
||||
align={props.align}
|
||||
side={props.side}
|
||||
sideOffset={10}
|
||||
onEscapeKeyDown={() => setIsPopoverOpen(false)}
|
||||
style={{
|
||||
width: "var(--radix-popover-trigger-width)",
|
||||
maxHeight: "var(--radix-popover-content-available-height)",
|
||||
height:
|
||||
"calc(var(--radix-popover-content-available-height) - 40px)",
|
||||
}}
|
||||
ref={popoverElRef}
|
||||
>
|
||||
<div>
|
||||
{/* Search */}
|
||||
<div className="relative">
|
||||
<Input
|
||||
placeholder={searchPlaceholder || "Search"}
|
||||
value={searchValue}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
className="!h-auto rounded-b-none border-0 border-border border-b py-4 pl-10 focus-visible:ring-0 focus-visible:ring-offset-0"
|
||||
onKeyDown={handleInputKeyDown}
|
||||
/>
|
||||
<SearchIcon className="-translate-y-1/2 absolute top-1/2 left-4 size-4 text-muted-foreground" />
|
||||
</div>
|
||||
{/* Search */}
|
||||
<div className="relative">
|
||||
<Input
|
||||
placeholder={searchPlaceholder || "Search"}
|
||||
value={searchValue}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
className="!h-auto rounded-b-none border-0 border-border border-b py-4 pl-10 focus-visible:ring-0 focus-visible:ring-offset-0"
|
||||
onKeyDown={handleInputKeyDown}
|
||||
/>
|
||||
<SearchIcon className="-translate-y-1/2 absolute top-1/2 left-4 size-4 text-muted-foreground" />
|
||||
</div>
|
||||
|
||||
{optionsToShow.length === 0 && (
|
||||
<div className="flex flex-1 flex-col items-center justify-center py-10 text-sm">
|
||||
No results found
|
||||
</div>
|
||||
)}
|
||||
|
||||
{optionsToShow.length > 0 && (
|
||||
<ScrollShadow
|
||||
scrollableClassName="max-h-[min(calc(var(--radix-popover-content-available-height)-60px),350px)] p-1"
|
||||
className="rounded"
|
||||
scrollableClassName="p-1 h-full"
|
||||
className="flex-1 rounded"
|
||||
>
|
||||
{/* List */}
|
||||
<div>
|
||||
{optionsToShow.length === 0 && (
|
||||
<div className="flex justify-center py-10">
|
||||
No results found
|
||||
</div>
|
||||
)}
|
||||
|
||||
{optionsToShow.map((option, i) => {
|
||||
const isSelected = selectedValues.includes(option.value);
|
||||
return (
|
||||
@@ -293,7 +289,20 @@ export const MultiSelect = forwardRef<HTMLButtonElement, MultiSelectProps>(
|
||||
})}
|
||||
</div>
|
||||
</ScrollShadow>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showSelectedValuesInModal && selectedValues.length > 0 && (
|
||||
<div className="border-t px-3 py-3">
|
||||
<SelectedChainsBadges
|
||||
selectedValues={selectedValues}
|
||||
options={options}
|
||||
maxCount={maxCount}
|
||||
onClose={handleClear}
|
||||
toggleOption={toggleOption}
|
||||
clearExtraOptions={clearExtraOptions}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
@@ -319,3 +328,44 @@ function ClosableBadge(props: {
|
||||
}
|
||||
|
||||
MultiSelect.displayName = "MultiSelect";
|
||||
|
||||
function SelectedChainsBadges(props: {
|
||||
selectedValues: string[];
|
||||
options: {
|
||||
label: string;
|
||||
value: string;
|
||||
}[];
|
||||
maxCount: number;
|
||||
onClose: () => void;
|
||||
toggleOption: (value: string) => void;
|
||||
clearExtraOptions: () => void;
|
||||
}) {
|
||||
const { selectedValues, options, maxCount, toggleOption, clearExtraOptions } =
|
||||
props;
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
{selectedValues.slice(0, maxCount).map((value) => {
|
||||
const option = options.find((o) => o.value === value);
|
||||
if (!option) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ClosableBadge
|
||||
key={value}
|
||||
label={option.label}
|
||||
onClose={() => toggleOption(value)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* +X more */}
|
||||
{selectedValues.length > maxCount && (
|
||||
<ClosableBadge
|
||||
label={`+ ${selectedValues.length - maxCount} more`}
|
||||
onClose={clearExtraOptions}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { CheckIcon, CircleDollarSignIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import type React from "react";
|
||||
import { TEAM_PLANS } from "utils/pricing";
|
||||
import { RenewSubscriptionButton } from "../../../components/settings/Account/Billing/renew-subscription/renew-subscription-button";
|
||||
import { useTrack } from "../../../hooks/analytics/useTrack";
|
||||
import { remainingDays } from "../../../utils/date-utils";
|
||||
import type { GetBillingCheckoutUrlAction } from "../../actions/billing";
|
||||
@@ -16,20 +17,27 @@ import { CheckoutButton } from "../billing";
|
||||
|
||||
type PricingCardCta = {
|
||||
hint?: string;
|
||||
title: string;
|
||||
|
||||
onClick?: () => void;
|
||||
} & (
|
||||
| {
|
||||
type: "link";
|
||||
href: string;
|
||||
label: string;
|
||||
}
|
||||
| {
|
||||
type: "checkout";
|
||||
label: string;
|
||||
}
|
||||
| {
|
||||
type: "renew";
|
||||
}
|
||||
);
|
||||
|
||||
type PricingCardProps = {
|
||||
getTeam: () => Promise<Team>;
|
||||
teamSlug: string;
|
||||
teamId: string;
|
||||
billingStatus: Team["billingStatus"];
|
||||
billingPlan: keyof typeof TEAM_PLANS;
|
||||
cta?: PricingCardCta;
|
||||
@@ -41,7 +49,9 @@ type PricingCardProps = {
|
||||
};
|
||||
|
||||
export const PricingCard: React.FC<PricingCardProps> = ({
|
||||
getTeam,
|
||||
teamSlug,
|
||||
teamId,
|
||||
billingStatus,
|
||||
billingPlan,
|
||||
cta,
|
||||
@@ -131,6 +141,9 @@ export const PricingCard: React.FC<PricingCardProps> = ({
|
||||
|
||||
{cta && (
|
||||
<div className="flex flex-col gap-3">
|
||||
{cta.type === "renew" && (
|
||||
<RenewSubscriptionButton teamId={teamId} getTeam={getTeam} />
|
||||
)}
|
||||
{billingPlanToSkuMap[billingPlan] && cta.type === "checkout" && (
|
||||
<CheckoutButton
|
||||
billingStatus={billingStatus}
|
||||
@@ -143,7 +156,7 @@ export const PricingCard: React.FC<PricingCardProps> = ({
|
||||
sku={billingPlanToSkuMap[billingPlan]}
|
||||
getBillingCheckoutUrl={getBillingCheckoutUrl}
|
||||
>
|
||||
{cta.title}
|
||||
{cta.label}
|
||||
</CheckoutButton>
|
||||
)}
|
||||
|
||||
@@ -154,7 +167,7 @@ export const PricingCard: React.FC<PricingCardProps> = ({
|
||||
asChild
|
||||
>
|
||||
<Link href={cta.href} target="_blank" onClick={handleCTAClick}>
|
||||
{cta.title}
|
||||
{cta.label}
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { useState } from "react";
|
||||
import { BadgeContainer } from "../../stories/utils";
|
||||
import { BadgeContainer } from "stories/utils";
|
||||
import { PaginationButtons } from "./pagination-buttons";
|
||||
|
||||
const meta = {
|
||||
|
||||
@@ -243,9 +243,7 @@ const ChartTooltipContent = React.forwardRef<
|
||||
<div className="grid gap-1.5">
|
||||
{nestLabel ? tooltipLabel : null}
|
||||
<span className="text-muted-foreground">
|
||||
{item.name === "maxLimit"
|
||||
? "Upper Limit"
|
||||
: itemConfig?.label || item.name}
|
||||
{itemConfig?.label || item.name}
|
||||
</span>
|
||||
</div>
|
||||
{item.value !== undefined && (
|
||||
|
||||
@@ -24,11 +24,7 @@ const TableHeader = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<thead
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"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,
|
||||
)}
|
||||
className={cn("relative border-b bg-background", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
@@ -4,6 +4,11 @@ export const DASHBOARD_THIRDWEB_CLIENT_ID =
|
||||
export const DASHBOARD_THIRDWEB_SECRET_KEY =
|
||||
process.env.DASHBOARD_SECRET_KEY || "";
|
||||
|
||||
export const NEXT_PUBLIC_NEBULA_APP_CLIENT_ID =
|
||||
process.env.NEXT_PUBLIC_NEBULA_APP_CLIENT_ID || "";
|
||||
|
||||
export const NEBULA_APP_SECRET_KEY = process.env.NEBULA_APP_SECRET_KEY || "";
|
||||
|
||||
export const THIRDWEB_API_SECRET = process.env.API_SERVER_SECRET || "";
|
||||
|
||||
export const IPFS_GATEWAY_URL =
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useMemo } from "react";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import type { GetAuthTokenResponse } from "../../app/api/auth/get-auth-token/route";
|
||||
import type { GetAuthTokenResponse } from "../../app/(app)/api/auth/get-auth-token/route";
|
||||
import { LAST_USED_TEAM_ID } from "../../constants/cookies";
|
||||
import { getCookie } from "../../lib/cookie";
|
||||
import { getThirdwebClient } from "./thirdweb.server";
|
||||
|
||||
// returns a thirdweb client with optional JWT passed i
|
||||
|
||||
export function useThirdwebClient(jwt?: string) {
|
||||
const account = useActiveAccount();
|
||||
const lastUsedTeamId = getCookie(LAST_USED_TEAM_ID);
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: ["jwt", account?.address],
|
||||
// only enable the query if there is an account and no JWT is passed in directly
|
||||
@@ -33,8 +37,12 @@ export function useThirdwebClient(jwt?: string) {
|
||||
|
||||
return useMemo(
|
||||
// prefer jwt from props over the one from the token query if it exists
|
||||
() => getThirdwebClient(jwt || query.data),
|
||||
[jwt, query.data],
|
||||
() =>
|
||||
getThirdwebClient({
|
||||
jwt: jwt || query.data,
|
||||
teamId: lastUsedTeamId,
|
||||
}),
|
||||
[jwt, query.data, lastUsedTeamId],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
import {
|
||||
THIRDWEB_BUNDLER_DOMAIN,
|
||||
THIRDWEB_INAPP_WALLET_DOMAIN,
|
||||
THIRDWEB_INSIGHT_API_DOMAIN,
|
||||
THIRDWEB_PAY_DOMAIN,
|
||||
THIRDWEB_RPC_DOMAIN,
|
||||
THIRDWEB_SOCIAL_API_DOMAIN,
|
||||
@@ -22,7 +23,14 @@ import { getZkPaymasterData } from "thirdweb/wallets/smart";
|
||||
import { getVercelEnv } from "../../lib/vercel-utils";
|
||||
|
||||
// returns a thirdweb client with optional JWT passed in
|
||||
export function getThirdwebClient(jwt?: string) {
|
||||
export function getThirdwebClient(
|
||||
options:
|
||||
| {
|
||||
jwt: string | null | undefined;
|
||||
teamId: string | undefined;
|
||||
}
|
||||
| undefined,
|
||||
) {
|
||||
if (getVercelEnv() !== "production") {
|
||||
// if not on production: run this when creating a client to set the domains
|
||||
setThirdwebDomains({
|
||||
@@ -32,6 +40,7 @@ export function getThirdwebClient(jwt?: string) {
|
||||
storage: THIRDWEB_STORAGE_DOMAIN,
|
||||
social: THIRDWEB_SOCIAL_API_DOMAIN,
|
||||
bundler: THIRDWEB_BUNDLER_DOMAIN,
|
||||
insight: THIRDWEB_INSIGHT_API_DOMAIN,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -69,7 +78,8 @@ export function getThirdwebClient(jwt?: string) {
|
||||
}
|
||||
|
||||
return createThirdwebClient({
|
||||
secretKey: jwt ? jwt : DASHBOARD_THIRDWEB_SECRET_KEY,
|
||||
teamId: options?.teamId,
|
||||
secretKey: options?.jwt ? options.jwt : DASHBOARD_THIRDWEB_SECRET_KEY,
|
||||
clientId: DASHBOARD_THIRDWEB_CLIENT_ID,
|
||||
config: {
|
||||
storage: {
|
||||
|
||||
@@ -1,75 +1,64 @@
|
||||
"use client";
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {
|
||||
Suspense,
|
||||
type TransitionStartFunction,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
useSyncExternalStore,
|
||||
useTransition,
|
||||
} from "react";
|
||||
import { createStore } from "./reactive";
|
||||
|
||||
// Using useDashboardRouter instead of useRouter gives us a nice progress bar on top of the page when navigating using router.push or router.replace
|
||||
|
||||
// using a store instead of context to avoid triggering re-renders on root component
|
||||
const LoadingRouteHref = createStore<string | undefined>(undefined);
|
||||
const RouteStartTransitionFn = createStore<TransitionStartFunction>((fn) =>
|
||||
fn(),
|
||||
);
|
||||
|
||||
export function useDashboardRouter() {
|
||||
const startTransition = useSyncExternalStore(
|
||||
RouteStartTransitionFn.subscribe,
|
||||
RouteStartTransitionFn.getValue,
|
||||
RouteStartTransitionFn.getValue,
|
||||
);
|
||||
|
||||
const router = useRouter();
|
||||
return useMemo(() => {
|
||||
return {
|
||||
...router,
|
||||
replace(href: string, options?: { scroll?: boolean }) {
|
||||
LoadingRouteHref.setValue(href);
|
||||
router.replace(href, options);
|
||||
startTransition(() => {
|
||||
router.replace(href, options);
|
||||
});
|
||||
},
|
||||
push(href: string, options?: { scroll?: boolean }) {
|
||||
LoadingRouteHref.setValue(href);
|
||||
router.push(href, options);
|
||||
startTransition(() => {
|
||||
router.push(href, options);
|
||||
});
|
||||
},
|
||||
};
|
||||
}, [router]);
|
||||
}
|
||||
|
||||
function useRouterLoadingStatus() {
|
||||
const loadingHref = useSyncExternalStore(
|
||||
LoadingRouteHref.subscribe,
|
||||
LoadingRouteHref.getValue,
|
||||
LoadingRouteHref.getValue,
|
||||
);
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const searchParamsStr = searchParams?.toString();
|
||||
|
||||
const routerHref = pathname + (searchParamsStr ? `?${searchParamsStr}` : "");
|
||||
const isLoading = loadingHref && loadingHref !== routerHref;
|
||||
|
||||
// reset loading on route load
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
useEffect(() => {
|
||||
if (!isLoading) {
|
||||
LoadingRouteHref.setValue(undefined);
|
||||
}
|
||||
}, [isLoading]);
|
||||
|
||||
return isLoading;
|
||||
}, [router, startTransition]);
|
||||
}
|
||||
|
||||
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
// Render this on root
|
||||
function DashboardRouterTopProgressBarInner() {
|
||||
const isLoading = useRouterLoadingStatus();
|
||||
const [progress, setProgress] = useState(0);
|
||||
export function DashboardRouterTopProgressBar() {
|
||||
const [isRouteLoading, startTransition] = useTransition();
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
useEffect(() => {
|
||||
RouteStartTransitionFn.setValue(startTransition);
|
||||
}, []);
|
||||
|
||||
const [progress, setProgress] = useState(0);
|
||||
const progressStartedRef = useRef(false);
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
useEffect(() => {
|
||||
if (!isLoading) {
|
||||
if (!isRouteLoading) {
|
||||
setProgress(0);
|
||||
progressStartedRef.current = false;
|
||||
return;
|
||||
@@ -85,7 +74,7 @@ function DashboardRouterTopProgressBarInner() {
|
||||
|
||||
async function updateProgressBar(progress: number, delay: number) {
|
||||
if (!isMounted) {
|
||||
if (!isLoading) {
|
||||
if (!isRouteLoading) {
|
||||
setProgress(100);
|
||||
}
|
||||
return;
|
||||
@@ -112,14 +101,14 @@ function DashboardRouterTopProgressBarInner() {
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}, [isLoading]);
|
||||
}, [isRouteLoading]);
|
||||
|
||||
const width = isLoading ? progress : 100;
|
||||
const width = isRouteLoading ? progress : 100;
|
||||
return (
|
||||
<span
|
||||
className="fixed top-0 block h-[3px] bg-foreground"
|
||||
style={{
|
||||
opacity: isLoading ? "100" : "0",
|
||||
opacity: isRouteLoading ? "100" : "0",
|
||||
width: `${width}%`,
|
||||
transition: width === 0 ? "none" : "width 0.2s ease, opacity 0.3s ease",
|
||||
zIndex: "100000000",
|
||||
@@ -128,12 +117,3 @@ function DashboardRouterTopProgressBarInner() {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// need to wrap with suspense because of useSearchParams usage
|
||||
export function DashboardRouterTopProgressBar() {
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<DashboardRouterTopProgressBarInner />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useThirdwebClient } from "@/constants/thirdweb.client";
|
||||
import { useStore } from "@/lib/reactive";
|
||||
import { getSDKTheme } from "app/components/sdk-component-theme";
|
||||
import { getSDKTheme } from "app/(app)/components/sdk-component-theme";
|
||||
import { CustomChainRenderer } from "components/selects/CustomChainRenderer";
|
||||
import { mapV4ChainToV5Chain } from "contexts/map-chains";
|
||||
import { useTrack } from "hooks/analytics/useTrack";
|
||||
@@ -19,8 +19,8 @@ import {
|
||||
useActiveAccount,
|
||||
useConnectModal,
|
||||
} from "thirdweb/react";
|
||||
import { useFavoriteChainIds } from "../../../../app/(dashboard)/(chain)/components/client/star-button";
|
||||
import { doLogout } from "../../../../app/login/auth-actions";
|
||||
import { useFavoriteChainIds } from "../../../../app/(app)/(dashboard)/(chain)/components/client/star-button";
|
||||
import { doLogout } from "../../../../app/(app)/login/auth-actions";
|
||||
import { LazyConfigureNetworkModal } from "../../../../components/configure-networks/LazyConfigureNetworkModal";
|
||||
import { useAllChainsData } from "../../../../hooks/chains/allChains";
|
||||
import {
|
||||
@@ -175,6 +175,7 @@ export const CustomConnectWallet = (props: {
|
||||
renderChain(props) {
|
||||
return (
|
||||
<CustomChainRenderer
|
||||
client={thirdwebClient}
|
||||
{...props}
|
||||
openEditChainModal={(c) => {
|
||||
setIsNetworkConfigModalOpen(true);
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import { type AbiEvent, formatAbiItem } from "abitype";
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
type PreparedEvent,
|
||||
type ThirdwebContract,
|
||||
prepareEvent,
|
||||
} from "thirdweb";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import { useContractEvents } from "thirdweb/react";
|
||||
import { useResolveContractAbi } from "./useResolveContractAbi";
|
||||
|
||||
export interface InternalTransaction {
|
||||
transactionHash: string;
|
||||
@@ -20,20 +14,8 @@ export interface InternalTransaction {
|
||||
}
|
||||
|
||||
export function useActivity(contract: ThirdwebContract, autoUpdate?: boolean) {
|
||||
const abiQuery = useResolveContractAbi(contract);
|
||||
|
||||
// Get all the Prepare Events from the contract abis
|
||||
const events: PreparedEvent<AbiEvent>[] = useMemo(() => {
|
||||
const eventsItems = (abiQuery.data || []).filter((o) => o.type === "event");
|
||||
const eventSignatures = eventsItems.map((event) => formatAbiItem(event));
|
||||
return eventSignatures.map((signature) =>
|
||||
prepareEvent({ signature: signature as `event ${string}` }),
|
||||
);
|
||||
}, [abiQuery.data]);
|
||||
|
||||
const eventsQuery = useContractEvents({
|
||||
contract,
|
||||
events,
|
||||
blockRange: 20000,
|
||||
watch: autoUpdate,
|
||||
});
|
||||
@@ -42,26 +24,30 @@ export function useActivity(contract: ThirdwebContract, autoUpdate?: boolean) {
|
||||
if (!eventsQuery.data) {
|
||||
return [];
|
||||
}
|
||||
const obj = eventsQuery.data.slice(0, 100).reduce(
|
||||
(acc, curr) => {
|
||||
const internalTx = acc[curr.transactionHash];
|
||||
if (internalTx) {
|
||||
internalTx.events.push(curr);
|
||||
internalTx.events.sort((a, b) => b.logIndex - a.logIndex);
|
||||
if (internalTx.blockNumber > curr.blockNumber) {
|
||||
internalTx.blockNumber = curr.blockNumber;
|
||||
const obj = eventsQuery.data
|
||||
.slice()
|
||||
.reverse()
|
||||
.slice(0, 100)
|
||||
.reduce(
|
||||
(acc, curr) => {
|
||||
const internalTx = acc[curr.transactionHash];
|
||||
if (internalTx) {
|
||||
internalTx.events.push(curr);
|
||||
internalTx.events.sort((a, b) => b.logIndex - a.logIndex);
|
||||
if (internalTx.blockNumber > curr.blockNumber) {
|
||||
internalTx.blockNumber = curr.blockNumber;
|
||||
}
|
||||
} else {
|
||||
acc[curr.transactionHash] = {
|
||||
transactionHash: curr.transactionHash,
|
||||
blockNumber: curr.blockNumber,
|
||||
events: [curr],
|
||||
};
|
||||
}
|
||||
} else {
|
||||
acc[curr.transactionHash] = {
|
||||
transactionHash: curr.transactionHash,
|
||||
blockNumber: curr.blockNumber,
|
||||
events: [curr],
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, InternalTransaction>,
|
||||
);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, InternalTransaction>,
|
||||
);
|
||||
return Object.values(obj).sort((a, b) =>
|
||||
a.blockNumber > b.blockNumber ? -1 : 1,
|
||||
);
|
||||
|
||||
@@ -7,12 +7,12 @@ import {
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from "@tanstack/react-query";
|
||||
import type { ResultItem } from "app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/metrics/components/StatusCodes";
|
||||
import type { ResultItem } from "app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/metrics/components/StatusCodes";
|
||||
import type { EngineBackendWalletType } from "lib/engine";
|
||||
import { useState } from "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 type { EngineStatus } from "../../../app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/transactions-table";
|
||||
import { engineKeys } from "../cache-keys";
|
||||
|
||||
// Engine instances
|
||||
|
||||
+8
-4
@@ -1,17 +1,21 @@
|
||||
"use client";
|
||||
import { getThirdwebClient } from "@/constants/thirdweb.server";
|
||||
import { getSDKTheme } from "app/components/sdk-component-theme";
|
||||
|
||||
import { getSDKTheme } from "app/(app)/components/sdk-component-theme";
|
||||
import { useV5DashboardChain } from "lib/v5-adapter";
|
||||
import { useTheme } from "next-themes";
|
||||
import type { ThirdwebClient } from "thirdweb";
|
||||
import { PayEmbed } from "thirdweb/react";
|
||||
|
||||
export function UniversalBridgeEmbed({ chainId }: { chainId?: number }) {
|
||||
export function UniversalBridgeEmbed({
|
||||
chainId,
|
||||
client,
|
||||
}: { chainId?: number; client: ThirdwebClient }) {
|
||||
const { theme } = useTheme();
|
||||
const chain = useV5DashboardChain(chainId || 1);
|
||||
|
||||
return (
|
||||
<PayEmbed
|
||||
client={getThirdwebClient()}
|
||||
client={client}
|
||||
payOptions={{
|
||||
mode: "fund_wallet",
|
||||
prefillBuy: {
|
||||
+6
-1
@@ -1,3 +1,4 @@
|
||||
import { getThirdwebClient } from "@/constants/thirdweb.server";
|
||||
import type { Metadata } from "next";
|
||||
import { UniversalBridgeEmbed } from "./components/client/UniversalBridgeEmbed";
|
||||
|
||||
@@ -18,10 +19,14 @@ export default async function RoutesPage({
|
||||
searchParams,
|
||||
}: { searchParams: Record<string, string | string[]> }) {
|
||||
const { chainId } = searchParams;
|
||||
const client = getThirdwebClient(undefined);
|
||||
return (
|
||||
<div className="relative mx-auto flex h-screen w-full flex-col items-center justify-center overflow-hidden border py-10">
|
||||
<main className="container z-10 flex justify-center">
|
||||
<UniversalBridgeEmbed chainId={chainId ? Number(chainId) : undefined} />
|
||||
<UniversalBridgeEmbed
|
||||
chainId={chainId ? Number(chainId) : undefined}
|
||||
client={client}
|
||||
/>
|
||||
</main>
|
||||
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
+5
-3
@@ -1,6 +1,6 @@
|
||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
import { getThirdwebClient } from "@/constants/thirdweb.server";
|
||||
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
|
||||
import type { ThirdwebClient } from "thirdweb";
|
||||
import { defineChain } from "thirdweb";
|
||||
import { getChainMetadata } from "thirdweb/chains";
|
||||
|
||||
@@ -15,6 +15,7 @@ type RouteListCardProps = {
|
||||
destinationTokenIconUri?: string | null;
|
||||
destinationTokenSymbol: string;
|
||||
destinationTokenName: string;
|
||||
client: ThirdwebClient;
|
||||
};
|
||||
|
||||
export async function RouteListCard({
|
||||
@@ -26,6 +27,7 @@ export async function RouteListCard({
|
||||
destinationTokenAddress,
|
||||
destinationTokenIconUri,
|
||||
destinationTokenName,
|
||||
client,
|
||||
}: RouteListCardProps) {
|
||||
const [
|
||||
originChain,
|
||||
@@ -40,13 +42,13 @@ export async function RouteListCard({
|
||||
originTokenIconUri
|
||||
? resolveSchemeWithErrorHandler({
|
||||
uri: originTokenIconUri,
|
||||
client: getThirdwebClient(),
|
||||
client,
|
||||
})
|
||||
: undefined,
|
||||
destinationTokenIconUri
|
||||
? resolveSchemeWithErrorHandler({
|
||||
uri: destinationTokenIconUri,
|
||||
client: getThirdwebClient(),
|
||||
client,
|
||||
})
|
||||
: undefined,
|
||||
]);
|
||||
+5
-3
@@ -1,7 +1,7 @@
|
||||
import { CopyTextButton } from "@/components/ui/CopyTextButton";
|
||||
import { TableCell, TableRow } from "@/components/ui/table";
|
||||
import { getThirdwebClient } from "@/constants/thirdweb.server";
|
||||
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
|
||||
import type { ThirdwebClient } from "thirdweb";
|
||||
import { defineChain, getChainMetadata } from "thirdweb/chains";
|
||||
|
||||
type RouteListRowProps = {
|
||||
@@ -15,6 +15,7 @@ type RouteListRowProps = {
|
||||
destinationTokenIconUri?: string | null;
|
||||
destinationTokenSymbol?: string;
|
||||
destinationTokenName?: string;
|
||||
client: ThirdwebClient;
|
||||
};
|
||||
|
||||
export async function RouteListRow({
|
||||
@@ -26,6 +27,7 @@ export async function RouteListRow({
|
||||
destinationTokenAddress,
|
||||
destinationTokenIconUri,
|
||||
destinationTokenSymbol,
|
||||
client,
|
||||
}: RouteListRowProps) {
|
||||
const [
|
||||
originChain,
|
||||
@@ -40,13 +42,13 @@ export async function RouteListRow({
|
||||
originTokenIconUri
|
||||
? resolveSchemeWithErrorHandler({
|
||||
uri: originTokenIconUri,
|
||||
client: getThirdwebClient(),
|
||||
client,
|
||||
})
|
||||
: undefined,
|
||||
destinationTokenIconUri
|
||||
? resolveSchemeWithErrorHandler({
|
||||
uri: destinationTokenIconUri,
|
||||
client: getThirdwebClient(),
|
||||
client,
|
||||
})
|
||||
: undefined,
|
||||
]);
|
||||
+6
-2
@@ -6,12 +6,13 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { getThirdwebClient } from "@/constants/thirdweb.server";
|
||||
import type { Address } from "thirdweb";
|
||||
import { checksumAddress } from "thirdweb/utils";
|
||||
import { getRoutes } from "../../../utils";
|
||||
import { ChainlistPagination } from "../client/pagination";
|
||||
import { RouteListCard } from "../server/routelist-card";
|
||||
import { RouteListRow } from "../server/routelist-row";
|
||||
import { RouteListCard } from "./routelist-card";
|
||||
import { RouteListRow } from "./routelist-row";
|
||||
|
||||
export type SearchParams = Partial<{
|
||||
includeDeprecated: boolean;
|
||||
@@ -136,6 +137,7 @@ export async function RoutesData(props: {
|
||||
const startIndex = (activePage - 1) * pageSize;
|
||||
const endIndex = startIndex + pageSize;
|
||||
const paginatedRoutes = routesToRender.slice(startIndex, endIndex);
|
||||
const client = getThirdwebClient(undefined);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -170,6 +172,7 @@ export async function RoutesData(props: {
|
||||
destinationTokenIconUri={route.destinationToken.iconUri}
|
||||
destinationTokenSymbol={route.destinationToken.symbol}
|
||||
destinationTokenName={route.destinationToken.name}
|
||||
client={client}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
@@ -193,6 +196,7 @@ export async function RoutesData(props: {
|
||||
destinationTokenIconUri={route.destinationToken.iconUri}
|
||||
destinationTokenSymbol={route.destinationToken.symbol}
|
||||
destinationTokenName={route.destinationToken.name}
|
||||
client={client}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import "server-only";
|
||||
|
||||
import { BRIDGE_URL, DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/env";
|
||||
import { getAuthToken } from "app/api/lib/getAuthToken";
|
||||
import { getAuthToken } from "app/(app)/api/lib/getAuthToken";
|
||||
import type { Route } from "./types/route";
|
||||
|
||||
export async function getRoutes({
|
||||
+5
-21
@@ -25,11 +25,10 @@ import {
|
||||
} from "@/constants/env";
|
||||
import { useThirdwebClient } from "@/constants/thirdweb.client";
|
||||
import { CustomConnectWallet } from "@3rdweb-sdk/react/components/connect-wallet";
|
||||
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Turnstile } from "@marsidev/react-turnstile";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import type { CanClaimResponseType } from "app/api/testnet-faucet/can-claim/CanClaimResponseType";
|
||||
import type { CanClaimResponseType } from "app/(app)/api/testnet-faucet/can-claim/CanClaimResponseType";
|
||||
import { mapV4ChainToV5Chain } from "contexts/map-chains";
|
||||
import { useTrack } from "hooks/analytics/useTrack";
|
||||
import Link from "next/link";
|
||||
@@ -52,7 +51,6 @@ import {
|
||||
useWalletBalance,
|
||||
} from "thirdweb/react";
|
||||
import { z } from "zod";
|
||||
import { isAccountOnboardingComplete } from "../../../../../../login/onboarding/isOnboardingRequired";
|
||||
|
||||
function formatTime(seconds: number) {
|
||||
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
|
||||
@@ -78,11 +76,11 @@ const claimFaucetSchema = z.object({
|
||||
export function FaucetButton({
|
||||
chain,
|
||||
amount,
|
||||
twAccount,
|
||||
isLoggedIn,
|
||||
}: {
|
||||
chain: ChainMetadata;
|
||||
amount: number;
|
||||
twAccount: Account | undefined;
|
||||
isLoggedIn: boolean;
|
||||
}) {
|
||||
const pathname = usePathname();
|
||||
const client = useThirdwebClient();
|
||||
@@ -186,7 +184,7 @@ export function FaucetButton({
|
||||
</div>
|
||||
<SendFundsToFaucetModalButton
|
||||
chain={definedChain}
|
||||
isLoggedIn={!!twAccount}
|
||||
isLoggedIn={isLoggedIn}
|
||||
client={client}
|
||||
chainMeta={chain}
|
||||
onFaucetRefill={() => {
|
||||
@@ -198,7 +196,7 @@ export function FaucetButton({
|
||||
}
|
||||
|
||||
// Force users to log in to claim the faucet
|
||||
if (!address || !twAccount) {
|
||||
if (!address || !isLoggedIn) {
|
||||
return (
|
||||
<Button variant="primary" className="w-full" asChild>
|
||||
<Link
|
||||
@@ -210,20 +208,6 @@ export function FaucetButton({
|
||||
);
|
||||
}
|
||||
|
||||
if (!isAccountOnboardingComplete(twAccount)) {
|
||||
return (
|
||||
<Button asChild className="w-full">
|
||||
<Link
|
||||
href={
|
||||
pathname ? `/login?next=${encodeURIComponent(pathname)}` : "/login"
|
||||
}
|
||||
>
|
||||
Verify your Email
|
||||
</Link>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
// Can not claim
|
||||
if (canClaimFaucetQuery.data && canClaimFaucetQuery.data.canClaim === false) {
|
||||
return (
|
||||
+6
-1
@@ -1,12 +1,16 @@
|
||||
import { ExternalLinkIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import type { ThirdwebClient } from "thirdweb";
|
||||
import type { ChainMetadata } from "thirdweb/chains";
|
||||
import { ChainIcon } from "../../../../components/server/chain-icon";
|
||||
import { PayModalButton } from "../client/PayModal";
|
||||
import { CreditCardIcon } from "../icons/CreditCardIcon";
|
||||
import { SectionTitle } from "./SectionTitle";
|
||||
|
||||
export function BuyFundsSection(props: { chain: ChainMetadata }) {
|
||||
export function BuyFundsSection(props: {
|
||||
chain: ChainMetadata;
|
||||
client: ThirdwebClient;
|
||||
}) {
|
||||
const sanitizedChainName = props.chain.name.replace("Mainnet", "").trim();
|
||||
|
||||
return (
|
||||
@@ -18,6 +22,7 @@ export function BuyFundsSection(props: { chain: ChainMetadata }) {
|
||||
<ChainIcon
|
||||
className="-mr-2 size-12 rounded-full border p-1"
|
||||
iconUrl={props.chain.icon?.url}
|
||||
client={props.client}
|
||||
/>
|
||||
<CreditCardIcon
|
||||
bg="hsl(var(--background))"
|
||||
+6
-4
@@ -1,4 +1,4 @@
|
||||
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import type { ThirdwebClient } from "thirdweb";
|
||||
import type { ChainMetadata } from "thirdweb/chains";
|
||||
import { getFaucetClaimAmount } from "../../../../../../api/testnet-faucet/claim/claim-amount";
|
||||
import { ChainIcon } from "../../../../components/server/chain-icon";
|
||||
@@ -8,9 +8,10 @@ import { SectionTitle } from "./SectionTitle";
|
||||
|
||||
export async function FaucetSection(props: {
|
||||
chain: ChainMetadata;
|
||||
twAccount: Account | undefined;
|
||||
isLoggedIn: boolean;
|
||||
client: ThirdwebClient;
|
||||
}) {
|
||||
const { chain, twAccount } = props;
|
||||
const { chain, isLoggedIn, client } = props;
|
||||
|
||||
// Check eligibility.
|
||||
const sanitizedChainName = chain.name.replace("Mainnet", "").trim();
|
||||
@@ -25,6 +26,7 @@ export async function FaucetSection(props: {
|
||||
<ChainIcon
|
||||
className="-mr-2 size-12 rounded-full border p-1"
|
||||
iconUrl={props.chain.icon?.url}
|
||||
client={client}
|
||||
/>
|
||||
<GiftIcon bg="hsl(var(--background))" className="-ml-2 size-12" />
|
||||
</div>
|
||||
@@ -47,7 +49,7 @@ export async function FaucetSection(props: {
|
||||
<FaucetButton
|
||||
chain={chain}
|
||||
amount={amountToGive}
|
||||
twAccount={twAccount}
|
||||
isLoggedIn={isLoggedIn}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
+4
-1
@@ -1,8 +1,9 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import Link from "next/link";
|
||||
import type { ThirdwebClient } from "thirdweb";
|
||||
import type { ChainMetadata } from "thirdweb/chains";
|
||||
import { mapV4ChainToV5Chain } from "../../../../../../../contexts/map-chains";
|
||||
import { mapV4ChainToV5Chain } from "../../../../../../../../contexts/map-chains";
|
||||
import { ChainIcon } from "../../../../components/server/chain-icon";
|
||||
import { AddChainToWallet } from "../client/add-chain-to-wallet";
|
||||
|
||||
@@ -10,6 +11,7 @@ type ChainHeaderProps = {
|
||||
headerImageUrl?: string;
|
||||
logoUrl?: string;
|
||||
chain: ChainMetadata;
|
||||
client: ThirdwebClient;
|
||||
};
|
||||
|
||||
// TODO: improve the behavior when clicking "Get started with thirdweb", currently just redirects to the dashboard
|
||||
@@ -39,6 +41,7 @@ export function ChainHeader(props: ChainHeaderProps) {
|
||||
{/* chain logo */}
|
||||
|
||||
<ChainIcon
|
||||
client={props.client}
|
||||
iconUrl={props.logoUrl}
|
||||
className={cn(
|
||||
"-translate-y-[50%] absolute top-0 left-0 size-20 overflow-hidden rounded-full border border-border bg-muted p-2 lg:size-36 lg:p-4",
|
||||
+10
-8
@@ -13,18 +13,17 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { getThirdwebClient } from "@/constants/thirdweb.server";
|
||||
import { ChevronDownIcon, TicketCheckIcon } from "lucide-react";
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import { mapV4ChainToV5Chain } from "../../../../../contexts/map-chains";
|
||||
import { getRawAccount } from "../../../../account/settings/getAccount";
|
||||
import { getThirdwebClient } from "../../../../../../@/constants/thirdweb.server";
|
||||
import { mapV4ChainToV5Chain } from "../../../../../../contexts/map-chains";
|
||||
import { NebulaFloatingChatButton } from "../../../../../nebula-app/(app)/components/FloatingChat/FloatingChat";
|
||||
import {
|
||||
getAuthToken,
|
||||
getAuthTokenWalletAddress,
|
||||
} from "../../../../api/lib/getAuthToken";
|
||||
import { NebulaFloatingChatButton } from "../../../../nebula-app/(app)/components/FloatingChat/FloatingChat";
|
||||
import { StarButton } from "../../components/client/star-button";
|
||||
import { getChain, getChainMetadata } from "../../utils";
|
||||
import { AddChainToWallet } from "./components/client/add-chain-to-wallet";
|
||||
@@ -61,10 +60,9 @@ export default async function ChainPageLayout(props: {
|
||||
}) {
|
||||
const params = await props.params;
|
||||
const { children } = props;
|
||||
const [chain, authToken, account, accountAddress] = await Promise.all([
|
||||
const [chain, authToken, accountAddress] = await Promise.all([
|
||||
getChain(params.chain_id),
|
||||
getAuthToken(),
|
||||
getRawAccount(),
|
||||
getAuthTokenWalletAddress(),
|
||||
]);
|
||||
|
||||
@@ -73,7 +71,10 @@ export default async function ChainPageLayout(props: {
|
||||
}
|
||||
|
||||
const chainMetadata = await getChainMetadata(chain.chainId);
|
||||
const client = getThirdwebClient(authToken ?? undefined);
|
||||
const client = getThirdwebClient({
|
||||
jwt: authToken,
|
||||
teamId: undefined,
|
||||
});
|
||||
|
||||
const chainPromptPrefix = `\
|
||||
You are assisting users exploring the chain ${chain.name} (Chain ID: ${chain.chainId}). Provide concise insights into the types of applications and activities prevalent on this chain, such as DeFi protocols, NFT marketplaces, or gaming platforms. Highlight notable projects or trends without delving into technical details like consensus mechanisms or gas fees.
|
||||
@@ -99,8 +100,8 @@ The following is the user's message:
|
||||
return (
|
||||
<>
|
||||
<NebulaFloatingChatButton
|
||||
pageType="chain"
|
||||
authToken={authToken ?? undefined}
|
||||
account={account}
|
||||
label="Ask AI about this chain"
|
||||
client={client}
|
||||
nebulaParams={{
|
||||
@@ -160,6 +161,7 @@ The following is the user's message:
|
||||
headerImageUrl={chainMetadata?.headerImgUrl}
|
||||
logoUrl={chain.icon?.url}
|
||||
chain={chain}
|
||||
client={client}
|
||||
/>
|
||||
|
||||
<div className="h-4 md:h-8" />
|
||||
+4
-3
@@ -77,9 +77,10 @@ export default async function Image({
|
||||
),
|
||||
// download the chain icon if there is one
|
||||
chain.icon?.url && hasWorkingChainIcon
|
||||
? download({ uri: chain.icon.url, client: getThirdwebClient() }).then(
|
||||
(res) => res.arrayBuffer(),
|
||||
)
|
||||
? download({
|
||||
uri: chain.icon.url,
|
||||
client: getThirdwebClient(undefined),
|
||||
}).then((res) => res.arrayBuffer())
|
||||
: undefined,
|
||||
// download the background image (based on chain)
|
||||
fetch(
|
||||
+4
-2
@@ -1,5 +1,6 @@
|
||||
import { CircleAlertIcon } from "lucide-react";
|
||||
import { getRawAccount } from "../../../../account/settings/getAccount";
|
||||
import { getUserThirdwebClient } from "../../../../api/lib/getAuthToken";
|
||||
import { getChain, getChainMetadata } from "../../utils";
|
||||
import NextSteps from "./components/client/NextSteps";
|
||||
import { BuyFundsSection } from "./components/server/BuyFundsSection";
|
||||
@@ -18,6 +19,7 @@ export default async function Page(props: {
|
||||
const chainMetadata = await getChainMetadata(chain.chainId);
|
||||
const isDeprecated = chain.status === "deprecated";
|
||||
|
||||
const client = await getUserThirdwebClient();
|
||||
const account = await getRawAccount();
|
||||
|
||||
return (
|
||||
@@ -42,9 +44,9 @@ export default async function Page(props: {
|
||||
|
||||
{/* Faucet / Buy Funds */}
|
||||
{chain.testnet ? (
|
||||
<FaucetSection chain={chain} twAccount={account} />
|
||||
<FaucetSection chain={chain} isLoggedIn={!!account} client={client} />
|
||||
) : chain.services.find((c) => c.service === "pay" && c.enabled) ? (
|
||||
<BuyFundsSection chain={chain} />
|
||||
<BuyFundsSection chain={chain} client={client} />
|
||||
) : null}
|
||||
|
||||
{/* Chain Overview */}
|
||||
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
+3
-5
@@ -1,6 +1,4 @@
|
||||
"use client";
|
||||
|
||||
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { TransactionButton } from "components/buttons/TransactionButton";
|
||||
import { useTrack } from "hooks/analytics/useTrack";
|
||||
import { useAllChainsData } from "hooks/chains/allChains";
|
||||
@@ -13,14 +11,14 @@ interface CancelTabProps {
|
||||
id: string;
|
||||
contract: ThirdwebContract;
|
||||
isAuction?: boolean;
|
||||
twAccount: Account | undefined;
|
||||
isLoggedIn: boolean;
|
||||
}
|
||||
|
||||
export const CancelTab: React.FC<CancelTabProps> = ({
|
||||
id,
|
||||
contract,
|
||||
isAuction,
|
||||
twAccount,
|
||||
isLoggedIn,
|
||||
}) => {
|
||||
const trackEvent = useTrack();
|
||||
const { idToChain } = useAllChainsData();
|
||||
@@ -32,7 +30,7 @@ export const CancelTab: React.FC<CancelTabProps> = ({
|
||||
return (
|
||||
<div className="flex flex-col gap-3 pt-3">
|
||||
<TransactionButton
|
||||
twAccount={twAccount}
|
||||
isLoggedIn={isLoggedIn}
|
||||
txChainID={contract.chain.id}
|
||||
transactionCount={1}
|
||||
isPending={cancelQuery.isPending}
|
||||
+6
-7
@@ -10,9 +10,8 @@ import {
|
||||
} from "@/components/ui/sheet";
|
||||
import { TabButtons } from "@/components/ui/tabs";
|
||||
import { ListerOnly } from "@3rdweb-sdk/react/components/roles/lister-only";
|
||||
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { isAlchemySupported } from "lib/wallet/nfts/alchemy";
|
||||
import { isMoralisSupported } from "lib/wallet/nfts/moralis";
|
||||
import { isAlchemySupported } from "lib/wallet/nfts/isAlchemySupported";
|
||||
import { isMoralisSupported } from "lib/wallet/nfts/isMoralisSupported";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
@@ -23,7 +22,7 @@ interface CreateListingButtonProps {
|
||||
contract: ThirdwebContract;
|
||||
createText?: string;
|
||||
type?: "direct-listings" | "english-auctions";
|
||||
twAccount: Account | undefined;
|
||||
isLoggedIn: boolean;
|
||||
isInsightSupported: boolean;
|
||||
}
|
||||
|
||||
@@ -33,7 +32,7 @@ export const CreateListingButton: React.FC<CreateListingButtonProps> = ({
|
||||
createText = "Create",
|
||||
type,
|
||||
contract,
|
||||
twAccount,
|
||||
isLoggedIn,
|
||||
isInsightSupported,
|
||||
...restButtonProps
|
||||
}) => {
|
||||
@@ -77,7 +76,7 @@ export const CreateListingButton: React.FC<CreateListingButtonProps> = ({
|
||||
/>
|
||||
<div className="mt-5">
|
||||
<CreateListingsForm
|
||||
twAccount={twAccount}
|
||||
isLoggedIn={isLoggedIn}
|
||||
contract={contract}
|
||||
type={type}
|
||||
actionText={createText}
|
||||
@@ -90,7 +89,7 @@ export const CreateListingButton: React.FC<CreateListingButtonProps> = ({
|
||||
) : (
|
||||
<div className="mt-5">
|
||||
<CreateListingsForm
|
||||
twAccount={twAccount}
|
||||
isLoggedIn={isLoggedIn}
|
||||
contract={contract}
|
||||
type={type}
|
||||
actionText={createText}
|
||||
+5
-6
@@ -1,7 +1,6 @@
|
||||
import { Alert, AlertTitle } from "@/components/ui/alert";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { useDashboardOwnedNFTs } from "@3rdweb-sdk/react/hooks/useDashboardOwnedNFTs";
|
||||
import { useWalletNFTs } from "@3rdweb-sdk/react/hooks/useWalletNFTs";
|
||||
import {
|
||||
@@ -19,8 +18,8 @@ import { SolidityInput } from "contract-ui/components/solidity-inputs";
|
||||
import { useTrack } from "hooks/analytics/useTrack";
|
||||
import { useAllChainsData } from "hooks/chains/allChains";
|
||||
import { useTxNotifications } from "hooks/useTxNotifications";
|
||||
import { isAlchemySupported } from "lib/wallet/nfts/alchemy";
|
||||
import { isMoralisSupported } from "lib/wallet/nfts/moralis";
|
||||
import { isAlchemySupported } from "lib/wallet/nfts/isAlchemySupported";
|
||||
import { isMoralisSupported } from "lib/wallet/nfts/isMoralisSupported";
|
||||
import type { WalletNFT } from "lib/wallet/nfts/types";
|
||||
import { CircleAlertIcon, InfoIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
@@ -85,7 +84,7 @@ type CreateListingsFormProps = {
|
||||
setOpen: Dispatch<SetStateAction<boolean>>;
|
||||
mode: "automatic" | "manual";
|
||||
type?: "direct-listings" | "english-auctions";
|
||||
twAccount: Account | undefined;
|
||||
isLoggedIn: boolean;
|
||||
isInsightSupported: boolean;
|
||||
};
|
||||
|
||||
@@ -104,7 +103,7 @@ export const CreateListingsForm: React.FC<CreateListingsFormProps> = ({
|
||||
type,
|
||||
actionText,
|
||||
setOpen,
|
||||
twAccount,
|
||||
isLoggedIn,
|
||||
mode,
|
||||
isInsightSupported,
|
||||
}) => {
|
||||
@@ -659,7 +658,7 @@ export const CreateListingsForm: React.FC<CreateListingsFormProps> = ({
|
||||
Cancel
|
||||
</Button>
|
||||
<TransactionButton
|
||||
twAccount={twAccount}
|
||||
isLoggedIn={isLoggedIn}
|
||||
txChainID={contract.chain.id}
|
||||
isPending={isFormLoading}
|
||||
transactionCount={2}
|
||||
+27
-12
@@ -3,17 +3,16 @@ import { CopyAddressButton } from "@/components/ui/CopyAddressButton";
|
||||
import { CopyTextButton } from "@/components/ui/CopyTextButton";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { CodeClient } from "@/components/ui/code/code.client";
|
||||
import { Sheet, SheetContent } from "@/components/ui/sheet";
|
||||
import { Flex, GridItem, SimpleGrid, usePrevious } from "@chakra-ui/react";
|
||||
import { toast } from "sonner";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import type {
|
||||
DirectListing,
|
||||
EnglishAuction,
|
||||
} from "thirdweb/extensions/marketplace";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
|
||||
import { CodeClient } from "@/components/ui/code/code.client";
|
||||
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { BuyDirectListingButton, useActiveAccount } from "thirdweb/react";
|
||||
import { NFTMediaWithEmptyState } from "tw-components/nft-media";
|
||||
import { CancelTab } from "./cancel-tab";
|
||||
import { LISTING_STATUS } from "./types";
|
||||
@@ -23,7 +22,7 @@ interface NFTDrawerProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
data: DirectListing | EnglishAuction | null;
|
||||
twAccount: Account | undefined;
|
||||
isLoggedIn: boolean;
|
||||
}
|
||||
|
||||
export const ListingDrawer: React.FC<NFTDrawerProps> = ({
|
||||
@@ -31,7 +30,7 @@ export const ListingDrawer: React.FC<NFTDrawerProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
data,
|
||||
twAccount,
|
||||
isLoggedIn,
|
||||
}) => {
|
||||
const address = useActiveAccount()?.address;
|
||||
const prevData = usePrevious(data);
|
||||
@@ -141,11 +140,6 @@ export const ListingDrawer: React.FC<NFTDrawerProps> = ({
|
||||
</GridItem>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/*
|
||||
Todo: Add a Buy button somewhere in this section once the Dashboard is fully migrated to v5 (?)
|
||||
Kien already shipped a prebuilt component for the Marketplace Buy Button in SDK v5
|
||||
*/}
|
||||
</SimpleGrid>
|
||||
</Card>
|
||||
{data?.asset.metadata.properties ? (
|
||||
@@ -165,9 +159,30 @@ export const ListingDrawer: React.FC<NFTDrawerProps> = ({
|
||||
contract={contract}
|
||||
id={renderData.id.toString()}
|
||||
isAuction={renderData.type === "english-auction"}
|
||||
twAccount={twAccount}
|
||||
isLoggedIn={isLoggedIn}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!isOwner && renderData.status === "ACTIVE" && (
|
||||
<BuyDirectListingButton
|
||||
listingId={renderData.id}
|
||||
contractAddress={renderData.assetContractAddress}
|
||||
chain={contract.chain}
|
||||
client={contract.client}
|
||||
className="w-full"
|
||||
quantity={1n}
|
||||
onError={(error) => {
|
||||
toast.error("Failed to buy listing", {
|
||||
description: error.message,
|
||||
});
|
||||
}}
|
||||
onTransactionConfirmed={() => {
|
||||
toast.success("Listing bought successfully");
|
||||
}}
|
||||
>
|
||||
Buy Listing
|
||||
</BuyDirectListingButton>
|
||||
)}
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
+3
-4
@@ -1,6 +1,5 @@
|
||||
import { WalletAddress } from "@/components/blocks/wallet-address";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import {
|
||||
IconButton,
|
||||
Select,
|
||||
@@ -99,7 +98,7 @@ interface MarketplaceTableProps {
|
||||
start: number;
|
||||
}>
|
||||
>;
|
||||
twAccount: Account | undefined;
|
||||
isLoggedIn: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_QUERY_STATE = { count: 50, start: 0 };
|
||||
@@ -111,7 +110,7 @@ export const MarketplaceTable: React.FC<MarketplaceTableProps> = ({
|
||||
totalCountQuery,
|
||||
queryParams,
|
||||
setQueryParams,
|
||||
twAccount,
|
||||
isLoggedIn,
|
||||
}) => {
|
||||
const [listingsToShow, setListingsToShow_] = useState<"all" | "valid">("all");
|
||||
|
||||
@@ -216,7 +215,7 @@ export const MarketplaceTable: React.FC<MarketplaceTableProps> = ({
|
||||
data={tokenRow}
|
||||
isOpen={!!tokenRow}
|
||||
onClose={() => setTokenRow(null)}
|
||||
twAccount={twAccount}
|
||||
isLoggedIn={isLoggedIn}
|
||||
/>
|
||||
<Table {...getTableProps()}>
|
||||
<Thead>
|
||||
+2
-3
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import { ErrorPage, LoadingPage } from "../../_components/page-skeletons";
|
||||
import { RedirectToContractOverview } from "../../_components/redirect-contract-overview.client";
|
||||
@@ -9,7 +8,7 @@ import { ContractDirectListingsPage } from "./ContractDirectListingsPage";
|
||||
|
||||
export function ContractDirectListingsPageClient(props: {
|
||||
contract: ThirdwebContract;
|
||||
twAccount: Account | undefined;
|
||||
isLoggedIn: boolean;
|
||||
}) {
|
||||
const metadataQuery = useContractPageMetadata(props.contract);
|
||||
|
||||
@@ -28,7 +27,7 @@ export function ContractDirectListingsPageClient(props: {
|
||||
return (
|
||||
<ContractDirectListingsPage
|
||||
contract={props.contract}
|
||||
twAccount={props.twAccount}
|
||||
isLoggedIn={props.isLoggedIn}
|
||||
isInsightSupported={metadataQuery.data.isInsightSupported}
|
||||
/>
|
||||
);
|
||||
+4
-5
@@ -1,19 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import { CreateListingButton } from "../components/list-button";
|
||||
import { DirectListingsTable } from "./components/table";
|
||||
|
||||
interface ContractDirectListingsPageProps {
|
||||
contract: ThirdwebContract;
|
||||
twAccount: Account | undefined;
|
||||
isLoggedIn: boolean;
|
||||
isInsightSupported: boolean;
|
||||
}
|
||||
|
||||
export const ContractDirectListingsPage: React.FC<
|
||||
ContractDirectListingsPageProps
|
||||
> = ({ contract, twAccount, isInsightSupported }) => {
|
||||
> = ({ contract, isLoggedIn, isInsightSupported }) => {
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
@@ -26,12 +25,12 @@ export const ContractDirectListingsPage: React.FC<
|
||||
contract={contract}
|
||||
type="direct-listings"
|
||||
createText="Create Direct Listing"
|
||||
twAccount={twAccount}
|
||||
isLoggedIn={isLoggedIn}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DirectListingsTable contract={contract} twAccount={twAccount} />
|
||||
<DirectListingsTable contract={contract} isLoggedIn={isLoggedIn} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+3
-4
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { useState } from "react";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import {
|
||||
@@ -13,14 +12,14 @@ import { MarketplaceTable } from "../../components/marketplace-table";
|
||||
|
||||
interface DirectListingsTableProps {
|
||||
contract: ThirdwebContract;
|
||||
twAccount: Account | undefined;
|
||||
isLoggedIn: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_QUERY_STATE = { count: 50, start: 0 };
|
||||
|
||||
export const DirectListingsTable: React.FC<DirectListingsTableProps> = ({
|
||||
contract,
|
||||
twAccount,
|
||||
isLoggedIn,
|
||||
}) => {
|
||||
const [queryParams, setQueryParams] = useState(DEFAULT_QUERY_STATE);
|
||||
const getAllQueryResult = useReadContract(getAllListings, {
|
||||
@@ -43,7 +42,7 @@ export const DirectListingsTable: React.FC<DirectListingsTableProps> = ({
|
||||
totalCountQuery={totalCountQuery}
|
||||
queryParams={queryParams}
|
||||
setQueryParams={setQueryParams}
|
||||
twAccount={twAccount}
|
||||
isLoggedIn={isLoggedIn}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+2
-2
@@ -24,7 +24,7 @@ export default async function Page(props: {
|
||||
return (
|
||||
<ContractDirectListingsPageClient
|
||||
contract={info.contract}
|
||||
twAccount={account}
|
||||
isLoggedIn={!!account}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -39,7 +39,7 @@ export default async function Page(props: {
|
||||
return (
|
||||
<ContractDirectListingsPage
|
||||
contract={info.contract}
|
||||
twAccount={account}
|
||||
isLoggedIn={!!account}
|
||||
isInsightSupported={isInsightSupported}
|
||||
/>
|
||||
);
|
||||
+2
-3
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import { ErrorPage, LoadingPage } from "../../_components/page-skeletons";
|
||||
import { RedirectToContractOverview } from "../../_components/redirect-contract-overview.client";
|
||||
@@ -9,7 +8,7 @@ import { ContractEnglishAuctionsPage } from "./ContractEnglishAuctionsPage";
|
||||
|
||||
export function ContractEnglishAuctionsPageClient(props: {
|
||||
contract: ThirdwebContract;
|
||||
twAccount: Account | undefined;
|
||||
isLoggedIn: boolean;
|
||||
}) {
|
||||
const metadataQuery = useContractPageMetadata(props.contract);
|
||||
|
||||
@@ -28,7 +27,7 @@ export function ContractEnglishAuctionsPageClient(props: {
|
||||
return (
|
||||
<ContractEnglishAuctionsPage
|
||||
contract={props.contract}
|
||||
twAccount={props.twAccount}
|
||||
isLoggedIn={props.isLoggedIn}
|
||||
isInsightSupported={metadataQuery.data.isInsightSupported}
|
||||
/>
|
||||
);
|
||||
+4
-5
@@ -1,19 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import { CreateListingButton } from "../components/list-button";
|
||||
import { EnglishAuctionsTable } from "./components/table";
|
||||
|
||||
interface ContractEnglishAuctionsProps {
|
||||
contract: ThirdwebContract;
|
||||
twAccount: Account | undefined;
|
||||
isLoggedIn: boolean;
|
||||
isInsightSupported: boolean;
|
||||
}
|
||||
|
||||
export const ContractEnglishAuctionsPage: React.FC<
|
||||
ContractEnglishAuctionsProps
|
||||
> = ({ contract, twAccount, isInsightSupported }) => {
|
||||
> = ({ contract, isLoggedIn, isInsightSupported }) => {
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
@@ -25,13 +24,13 @@ export const ContractEnglishAuctionsPage: React.FC<
|
||||
contract={contract}
|
||||
type="english-auctions"
|
||||
createText="Create English Auction"
|
||||
twAccount={twAccount}
|
||||
isLoggedIn={isLoggedIn}
|
||||
isInsightSupported={isInsightSupported}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<EnglishAuctionsTable contract={contract} twAccount={twAccount} />
|
||||
<EnglishAuctionsTable contract={contract} isLoggedIn={isLoggedIn} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+3
-4
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { useState } from "react";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import {
|
||||
@@ -13,14 +12,14 @@ import { MarketplaceTable } from "../../components/marketplace-table";
|
||||
|
||||
interface EnglishAuctionsTableProps {
|
||||
contract: ThirdwebContract;
|
||||
twAccount: Account | undefined;
|
||||
isLoggedIn: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_QUERY_STATE = { count: 50, start: 0 };
|
||||
|
||||
export const EnglishAuctionsTable: React.FC<EnglishAuctionsTableProps> = ({
|
||||
contract,
|
||||
twAccount,
|
||||
isLoggedIn,
|
||||
}) => {
|
||||
const [queryParams, setQueryParams] = useState(DEFAULT_QUERY_STATE);
|
||||
const getAllQueryResult = useReadContract(getAllAuctions, {
|
||||
@@ -37,7 +36,7 @@ export const EnglishAuctionsTable: React.FC<EnglishAuctionsTableProps> = ({
|
||||
|
||||
return (
|
||||
<MarketplaceTable
|
||||
twAccount={twAccount}
|
||||
isLoggedIn={isLoggedIn}
|
||||
contract={contract}
|
||||
getAllQueryResult={getAllQueryResult}
|
||||
getValidQueryResult={getValidQueryResult}
|
||||
+2
-2
@@ -25,7 +25,7 @@ export default async function Page(props: {
|
||||
return (
|
||||
<ContractEnglishAuctionsPageClient
|
||||
contract={info.contract}
|
||||
twAccount={twAccount}
|
||||
isLoggedIn={!!twAccount}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -40,7 +40,7 @@ export default async function Page(props: {
|
||||
return (
|
||||
<ContractEnglishAuctionsPage
|
||||
contract={info.contract}
|
||||
twAccount={twAccount}
|
||||
isLoggedIn={!!twAccount}
|
||||
isInsightSupported={isInsightSupported}
|
||||
/>
|
||||
);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user