Compare commits
56
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b0040bb92 | ||
|
|
c320b189f6 | ||
|
|
6bb2904936 | ||
|
|
5bde07508a | ||
|
|
ac4f68d5c8 | ||
|
|
3961ef477e | ||
|
|
085ea2bf00 | ||
|
|
2b6a2fda3d | ||
|
|
a5e605c65e | ||
|
|
85ddb175d7 | ||
|
|
c373ea571a | ||
|
|
fdae05fc00 | ||
|
|
7c1ea4a1e2 | ||
|
|
95b3c787a3 | ||
|
|
468067e47f | ||
|
|
1d0291fb9e | ||
|
|
6e5d89bf2a | ||
|
|
2bf9fc598a | ||
|
|
18239dd5df | ||
|
|
b837b690ae | ||
|
|
7b94bf55fd | ||
|
|
3009a0f6e5 | ||
|
|
07b949dd8c | ||
|
|
575c3e3141 | ||
|
|
a4a41364cd | ||
|
|
eba1c60895 | ||
|
|
1168bc702f | ||
|
|
06ef6f0a07 | ||
|
|
390aedcc8a | ||
|
|
ebe4975aa3 | ||
|
|
9c8beac7fe | ||
|
|
dc250225d2 | ||
|
|
11437524e4 | ||
|
|
1a56ed6214 | ||
|
|
b2a2c9784a | ||
|
|
5abc652871 | ||
|
|
fb0daabbf6 | ||
|
|
b142b8a893 | ||
|
|
e80a87a0d8 | ||
|
|
ce45cb2f24 | ||
|
|
de362ba2f5 | ||
|
|
ece86ccdc3 | ||
|
|
d115307db1 | ||
|
|
5d3b39587b | ||
|
|
53015711fe | ||
|
|
5058fdb470 | ||
|
|
a47bc2233c | ||
|
|
390180592a | ||
|
|
285321b193 | ||
|
|
8035923cda | ||
|
|
c0778cb930 | ||
|
|
db1f79678c | ||
|
|
113468e361 | ||
|
|
a24de21f00 | ||
|
|
2d2e6cb380 | ||
|
|
03c21903c9 |
+1
-27
@@ -10,33 +10,7 @@
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": [
|
||||
"bundlers-esbuild",
|
||||
"bundlers-vite",
|
||||
"bundlers-webpack",
|
||||
"bundlers-swc",
|
||||
"runtimes-node-commonjs",
|
||||
"runtimes-node-module",
|
||||
"playground-web",
|
||||
"thirdweb-dashboard",
|
||||
"wallet-ui",
|
||||
"portal",
|
||||
"@thirdweb-dev/auth",
|
||||
"@thirdweb-dev/chains",
|
||||
"@thirdweb-dev/cli",
|
||||
"@thirdweb-dev/contracts-js",
|
||||
"@thirdweb-dev/crypto",
|
||||
"@thirdweb-dev/generated-abis",
|
||||
"@thirdweb-dev/merkletree",
|
||||
"@thirdweb-dev/pay",
|
||||
"@thirdweb-dev/payments",
|
||||
"@thirdweb-dev/react",
|
||||
"@thirdweb-dev/react-core",
|
||||
"@thirdweb-dev/sdk",
|
||||
"@thirdweb-dev/storage",
|
||||
"@thirdweb-dev/unity-js-bridge",
|
||||
"@thirdweb-dev/wallets"
|
||||
],
|
||||
"ignore": ["playground-web", "thirdweb-dashboard", "wallet-ui", "portal"],
|
||||
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
|
||||
"updateInternalDependents": "always",
|
||||
"onlyUpdatePeerDependentsWhenOutOfRange": true
|
||||
|
||||
+2
-26
@@ -3,42 +3,18 @@
|
||||
# default ownership group
|
||||
- @jnsdls @joaquim-verges @MananTank @gregfromstl
|
||||
|
||||
# legacy packages
|
||||
|
||||
legacy_packages/auth/ @jnsdls @joaquim-verges
|
||||
legacy_packages/chains/ @jnsdls @joaquim-verges
|
||||
legacy_packages/cli/ @jnsdls @joaquim-verges
|
||||
legacy_packages/contracts-js/ @jnsdls @joaquim-verges
|
||||
legacy_packages/crypto/ @jnsdls @joaquim-verges
|
||||
legacy_packages/generated-abis/ @jnsdls @joaquim-verges
|
||||
legacy_packages/merkletree/ @jnsdls @joaquim-verges
|
||||
legacy_packages/pay/ @jnsdls @joaquim-verges
|
||||
legacy_packages/payments/ @jnsdls @joaquim-verges
|
||||
legacy_packages/react/ @MananTank @jnsdls @joaquim-verges
|
||||
legacy_packages/react-core/ @MananTank @jnsdls @joaquim-verges
|
||||
legacy_packages/react-native/ @jnsdls @joaquim-verges
|
||||
legacy_packages/react-native-compat/ @jnsdls @joaquim-verges
|
||||
legacy_packages/sdk/ @joaquim-verges @jnsdls
|
||||
legacy_packages/storage/ @jnsdls @joaquim-verges
|
||||
legacy_packages/wallets/ @MananTank @jnsdls @joaquim-verges
|
||||
|
||||
# legacy e2e
|
||||
|
||||
legacy_e2e/ @jnsdls
|
||||
|
||||
# packages
|
||||
packages/thirdweb/ @joaquim-verges @gregfromstl @jnsdls
|
||||
|
||||
## specific thirdweb pieces
|
||||
packages/thirdweb/src/react/ @joaquim-verges @gregfromstl @MananTank @jnsdls
|
||||
packages/thirdweb/src/react/ @joaquim-verges @gregfromstl @MananTank @jnsdls @edwardysun
|
||||
packages/thirdweb/src/reactive/ @joaquim-verges @gregfromstl @MananTank @jnsdls
|
||||
packages/thirdweb/src/gas/ @joaquim-verges @jnsdls
|
||||
packages/thirdweb/src/pay/ @joaquim-verges @gregfromstl @MananTank @jnsdls
|
||||
packages/thirdweb/src/pay/ @joaquim-verges @gregfromstl @MananTank @jnsdls @edwardysun
|
||||
packages/typedoc-gen/ @MananTank @jnsdls @joaquim-verges
|
||||
packages/service-utils/ @arcoraven @jnsdls @joaquim-verges
|
||||
packages/eslint-config-thirdweb/ @jnsdls @joaquim-verges
|
||||
packages/tw-tsconfig/ @jnsdls @joaquim-verges
|
||||
packages/unity-js-bridge/ @joaquim-verges @0xFirekeeper @jnsdls
|
||||
|
||||
# apps
|
||||
apps/ @jnsdls @joaquim-verges
|
||||
|
||||
@@ -5,9 +5,6 @@ dashboard:
|
||||
playground:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "apps/playground-web/**/*"
|
||||
legacy_packages:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "legacy-packages/**/*"
|
||||
packages:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "packages/**/*"
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
name: CI (Legacy Packages)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
paths:
|
||||
- "legacy_packages/**"
|
||||
- "legacy_e2e/**"
|
||||
- "package.json"
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
paths:
|
||||
- "legacy_packages/**"
|
||||
- "legacy_e2e/**"
|
||||
- "package.json"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
# To use Remote Caching, uncomment the next lines and follow the steps below.
|
||||
env:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
TW_SECRET_KEY: ${{ secrets.TW_SECRET_KEY }}
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
jobs:
|
||||
optimize_ci:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
skip: ${{ steps.check_skip.outputs.skip }}
|
||||
steps:
|
||||
- name: Optimize CI
|
||||
id: check_skip
|
||||
uses: withgraphite/graphite-ci-action@main
|
||||
with:
|
||||
graphite_token: ${{ secrets.GRAPHITE_OMTIMIZE_TOKEN }}
|
||||
|
||||
build:
|
||||
needs: optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
runs-on: ubuntu-latest
|
||||
name: Build Packages
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup & Install
|
||||
uses: ./.github/composite-actions/install
|
||||
|
||||
- name: Build Packages
|
||||
run: pnpm build:legacy_packages
|
||||
|
||||
lint:
|
||||
needs: optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
timeout-minutes: 15
|
||||
name: Lint Packages
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup & Install
|
||||
uses: ./.github/composite-actions/install
|
||||
|
||||
- run: pnpm lint:legacy
|
||||
|
||||
test:
|
||||
needs: optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
timeout-minutes: 15
|
||||
name: Unit Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup & Install
|
||||
uses: ./.github/composite-actions/install
|
||||
|
||||
- run: pnpm test:legacy
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
directory: legacy_packages/
|
||||
flags: legacy_packages
|
||||
verbose: true
|
||||
|
||||
e2e:
|
||||
needs: optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
timeout-minutes: 15
|
||||
name: E2E Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup & Install
|
||||
uses: ./.github/composite-actions/install
|
||||
|
||||
- name: Install Playwright
|
||||
run: npx playwright install --with-deps
|
||||
|
||||
- name: Run e2e tests
|
||||
run: pnpm e2e:legacy
|
||||
env:
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
CLI_E2E_API_KEY: ${{ secrets.CLI_E2E_API_KEY }}
|
||||
@@ -8,7 +8,6 @@ on:
|
||||
paths:
|
||||
- ".changeset/**"
|
||||
- "packages/**"
|
||||
- "legacy_packages/**"
|
||||
branches:
|
||||
- main
|
||||
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
name: Sync chains every hour
|
||||
|
||||
on:
|
||||
# temporarily disabled chain syncing
|
||||
# schedule:
|
||||
# - cron: "0 * * * *" # Runs every hour
|
||||
workflow_dispatch: # Allows you to manually run the workflow from the Actions tab
|
||||
|
||||
jobs:
|
||||
sync-chains:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: 1.0.35
|
||||
|
||||
- name: Run chains sync
|
||||
run: bun run db:sync
|
||||
working-directory: ./legacy_packages/chains
|
||||
|
||||
- name: Check for Changes
|
||||
id: git-check
|
||||
run: |
|
||||
if git diff --quiet; then
|
||||
echo "CHANGES_FOUND=false" >> $GITHUB_ENV
|
||||
else
|
||||
echo "CHANGES_FOUND=true" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Create or Update sync-chains.md
|
||||
if: env.CHANGES_FOUND == 'true'
|
||||
run: |
|
||||
echo '---' > .changeset/sync-chains.md
|
||||
echo '"@thirdweb-dev/chains": patch' >> .changeset/sync-chains.md
|
||||
echo '---' >> .changeset/sync-chains.md
|
||||
echo '' >> .changeset/sync-chains.md
|
||||
echo 'Synced Chains Package' >> .changeset/sync-chains.md
|
||||
|
||||
- name: Commit Changes
|
||||
if: env.CHANGES_FOUND == 'true'
|
||||
run: |
|
||||
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git add -A
|
||||
git commit -m "chore(chains): sync from db"
|
||||
|
||||
- name: Create or Update Pull Request
|
||||
if: env.CHANGES_FOUND == 'true'
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
title: "chore(chains): update chains"
|
||||
branch: "sync-chains"
|
||||
body: "This PR was automatically created by a GitHub Action. It updates the chains package with the latest changes from the database."
|
||||
commit-message: "chore(chains): sync from db"
|
||||
@@ -10,7 +10,6 @@ dist-ssr
|
||||
.cache
|
||||
server/dist
|
||||
public/dist
|
||||
./legacy_packages/contracts-js/abis
|
||||
.idea/
|
||||
.yalc/
|
||||
yalc.lock
|
||||
@@ -18,9 +17,6 @@ yalc.lock
|
||||
playwright-report/
|
||||
.env/
|
||||
|
||||
# Artifacts
|
||||
legacy_packages/cli/artifacts/
|
||||
|
||||
# codecov binary
|
||||
codecov
|
||||
|
||||
@@ -28,7 +24,6 @@ codecov
|
||||
sitemap*.xml
|
||||
|
||||
# typedoc
|
||||
legacy_packages/*/typedoc/*
|
||||
packages/*/typedoc/*
|
||||
|
||||
*storybook.log
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@thirdweb-dev/cli": minor
|
||||
"thirdweb": minor
|
||||
---
|
||||
|
||||
Adds --expo option to cli
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@thirdweb-dev/sdk": patch
|
||||
---
|
||||
|
||||
ZkCandy addresses
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@thirdweb-dev/cli": patch
|
||||
"@thirdweb-dev/sdk": patch
|
||||
---
|
||||
|
||||
Override contract publish id on CLI
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
"@thirdweb-dev/merkletree": patch
|
||||
"@thirdweb-dev/react-core": patch
|
||||
"@thirdweb-dev/payments": patch
|
||||
"@thirdweb-dev/storage": patch
|
||||
"@thirdweb-dev/wallets": patch
|
||||
"@thirdweb-dev/chains": patch
|
||||
"@thirdweb-dev/crypto": patch
|
||||
"@thirdweb-dev/service-utils": patch
|
||||
"@thirdweb-dev/react": patch
|
||||
"@thirdweb-dev/auth": patch
|
||||
"@thirdweb-dev/cli": patch
|
||||
"@thirdweb-dev/pay": patch
|
||||
"@thirdweb-dev/sdk": patch
|
||||
---
|
||||
|
||||
update dependencies
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@thirdweb-dev/react": patch
|
||||
---
|
||||
|
||||
Fix error when doing buy with Crypto transaction with InApp wallet
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@thirdweb-dev/sdk": patch
|
||||
---
|
||||
|
||||
Fix EIP155 check for create2 factory deployment
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@thirdweb-dev/wallets": patch
|
||||
"@thirdweb-dev/sdk": patch
|
||||
---
|
||||
|
||||
respect gas overrides if they are passed in
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@thirdweb-dev/react": patch
|
||||
---
|
||||
|
||||
Fix wallet app not opening when using ConnectButton inside an iframe
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@thirdweb-dev/chains": patch
|
||||
---
|
||||
|
||||
Synced Chains Package
|
||||
@@ -115,31 +115,6 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: "src/core-ui/**/*",
|
||||
rules: {
|
||||
// no restricted imports
|
||||
"@typescript-eslint/no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
paths: [
|
||||
{
|
||||
name: "@thirdweb-dev/sdk",
|
||||
message:
|
||||
"core-ui should not import from @thirdweb-dev/sdk. (except for types)",
|
||||
allowTypeImports: true,
|
||||
},
|
||||
{
|
||||
name: "@thirdweb-dev/react",
|
||||
message:
|
||||
"core-ui should not import from @thirdweb-dev/react. (except for types)",
|
||||
allowTypeImports: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// disable restricted imports in tw-components
|
||||
{
|
||||
files: "src/tw-components/**/*",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/knip@5/schema.json",
|
||||
"next": true,
|
||||
"ignore": ["src/graphql/**", "src/theme/*"],
|
||||
"ignore": ["src/@/components/ui/**"],
|
||||
"project": ["src/**"],
|
||||
"ignoreBinaries": ["only-allow"]
|
||||
"ignoreBinaries": ["only-allow", "knip", "biome"],
|
||||
"ignoreDependencies": ["@storybook/blocks", "@thirdweb-dev/service-utils"]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// @ts-check
|
||||
/**
|
||||
*
|
||||
* @returns {Promise<import("@thirdweb-dev/chains").Chain[]>}
|
||||
*/
|
||||
async function fetchChainsFromApi() {
|
||||
const res = await fetch(`https://api.thirdweb.com/v1/chains`, {
|
||||
@@ -21,7 +20,6 @@ async function fetchChainsFromApi() {
|
||||
/**
|
||||
*
|
||||
* @param {number|string} chainIdOrSlug
|
||||
* @returns {Promise<import("@thirdweb-dev/chains").Chain>}
|
||||
*/
|
||||
async function getSingleChain(chainIdOrSlug) {
|
||||
const res = await fetch(
|
||||
|
||||
@@ -144,7 +144,6 @@ const moduleExports = {
|
||||
reactStrictMode: true,
|
||||
experimental: {
|
||||
scrollRestoration: true,
|
||||
esmExternals: "loose",
|
||||
webpackBuildWorker: true,
|
||||
serverSourceMaps: false,
|
||||
},
|
||||
|
||||
+22
-57
@@ -4,15 +4,14 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"dev": "next dev",
|
||||
"dev": "next dev --turbo",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"format": "biome format ./src --write",
|
||||
"lint": "biome check ./src && eslint ./src",
|
||||
"lint": "biome check ./src && knip && eslint ./src",
|
||||
"fix": "biome check ./src --fix && eslint ./src --fix",
|
||||
"gen:theme-typings": "chakra-cli tokens src/theme/index.ts",
|
||||
"postinstall": "pnpm run gen:theme-typings",
|
||||
"find:unused": "next-unused",
|
||||
"postbuild": "next-sitemap",
|
||||
"build:analyze": "ANALYZE=true pnpm run build",
|
||||
"knip": "knip",
|
||||
@@ -45,20 +44,15 @@
|
||||
"@radix-ui/react-separator": "^1.1.0",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-switch": "^1.1.0",
|
||||
"@radix-ui/react-toggle": "^1.1.0",
|
||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "1.1.2",
|
||||
"@react-icons/all-files": "^4.1.0",
|
||||
"@sentry/nextjs": "8.30.0",
|
||||
"@shazow/whatsabi": "^0.14.1",
|
||||
"@stripe/react-stripe-js": "^2.7.3",
|
||||
"@stripe/stripe-js": "^3.5.0",
|
||||
"@tanstack/react-query": "^4.36.1",
|
||||
"@tanstack/react-query": "5.56.2",
|
||||
"@tanstack/react-table": "^8.17.3",
|
||||
"@thirdweb-dev/react": "workspace:*",
|
||||
"@thirdweb-dev/sdk": "workspace:*",
|
||||
"@thirdweb-dev/service-utils": "workspace:*",
|
||||
"@thirdweb-dev/storage": "workspace:*",
|
||||
"@vercel/og": "^0.6.2",
|
||||
"abitype": "1.0.5",
|
||||
"chakra-react-select": "^4.7.6",
|
||||
@@ -67,15 +61,14 @@
|
||||
"color": "^4.2.3",
|
||||
"compare-versions": "^6.1.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"ethers": "5.7.2",
|
||||
"flat": "^6.0.1",
|
||||
"framer-motion": "11.5.4",
|
||||
"fuse.js": "7.0.0",
|
||||
"ioredis": "^5.4.1",
|
||||
"ipaddr.js": "^2.2.0",
|
||||
"lottie-react": "^2.4.0",
|
||||
"lucide-react": "0.439.0",
|
||||
"next": "14.2.9",
|
||||
"lucide-react": "0.441.0",
|
||||
"next": "14.2.11",
|
||||
"next-plausible": "^3.12.0",
|
||||
"next-seo": "^6.5.0",
|
||||
"next-themes": "^0.3.0",
|
||||
@@ -97,7 +90,6 @@
|
||||
"react-intersection-observer": "^9.10.3",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-otp-input": "^3.1.1",
|
||||
"react-query-v5": "npm:@tanstack/[email protected]",
|
||||
"react-responsive-carousel": "^3.2.23",
|
||||
"react-table": "^7.8.0",
|
||||
"recharts": "^2.12.7",
|
||||
@@ -111,25 +103,23 @@
|
||||
"thirdweb": "workspace:*",
|
||||
"tiny-invariant": "^1.3.3",
|
||||
"use-debounce": "^10.0.1",
|
||||
"vaul": "^0.9.1",
|
||||
"zksync-ethers": "5.9.0",
|
||||
"zod": "3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chakra-ui/cli": "^2.4.1",
|
||||
"@chromatic-com/storybook": "^1.5.0",
|
||||
"@next/bundle-analyzer": "14.2.9",
|
||||
"@next/eslint-plugin-next": "^14.2.9",
|
||||
"@playwright/test": "1.47.0",
|
||||
"@storybook/addon-essentials": "^8.2.9",
|
||||
"@storybook/addon-interactions": "^8.2.9",
|
||||
"@storybook/addon-links": "^8.2.9",
|
||||
"@storybook/addon-onboarding": "^8.2.9",
|
||||
"@storybook/addon-viewport": "^8.2.9",
|
||||
"@storybook/blocks": "^8.2.9",
|
||||
"@storybook/nextjs": "^8.2.9",
|
||||
"@storybook/react": "^8.2.9",
|
||||
"@storybook/test": "^8.2.9",
|
||||
"@chromatic-com/storybook": "2.0.2",
|
||||
"@next/bundle-analyzer": "14.2.11",
|
||||
"@next/eslint-plugin-next": "14.2.11",
|
||||
"@playwright/test": "1.47.1",
|
||||
"@storybook/addon-essentials": "8.3.0",
|
||||
"@storybook/addon-interactions": "8.3.0",
|
||||
"@storybook/addon-links": "8.3.0",
|
||||
"@storybook/addon-onboarding": "8.3.0",
|
||||
"@storybook/addon-viewport": "8.3.0",
|
||||
"@storybook/blocks": "8.3.0",
|
||||
"@storybook/nextjs": "8.3.0",
|
||||
"@storybook/react": "8.3.0",
|
||||
"@storybook/test": "8.3.0",
|
||||
"@types/color": "^3.0.6",
|
||||
"@types/node": "20.14.9",
|
||||
"@types/papaparse": "^5.3.14",
|
||||
@@ -151,34 +141,9 @@
|
||||
"eslint-plugin-react-compiler": "0.0.0-experimental-9ed098e-20240725",
|
||||
"eslint-plugin-storybook": "^0.8.0",
|
||||
"next-sitemap": "^4.2.3",
|
||||
"next-unused": "^0.0.6",
|
||||
"storybook": "^8.2.9",
|
||||
"tailwindcss": "^3.4.10",
|
||||
"postcss": "8.4.47",
|
||||
"storybook": "8.3.0",
|
||||
"tailwindcss": "3.4.11",
|
||||
"typescript": "5.6.2"
|
||||
},
|
||||
"next-unused": {
|
||||
"alias": {},
|
||||
"include": [
|
||||
"components",
|
||||
"hooks",
|
||||
"schema",
|
||||
"theme",
|
||||
"utils",
|
||||
"@3rdweb-sdk",
|
||||
"constants",
|
||||
"tw-components",
|
||||
"contract-ui"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"types",
|
||||
"tw-components"
|
||||
],
|
||||
"entrypoints": []
|
||||
},
|
||||
"browserslist": [
|
||||
"defaults",
|
||||
"unreleased versions",
|
||||
"not UCAndroid > 0"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -3,10 +3,13 @@ import { Button } from "@/components/ui/button";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { DownloadIcon } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { cn } from "../../lib/utils";
|
||||
|
||||
export function ExportToCSVButton(props: {
|
||||
getData: () => Promise<{ header: string[]; rows: string[][] }>;
|
||||
fileName: string;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
}) {
|
||||
const exportMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
@@ -26,12 +29,13 @@ export function ExportToCSVButton(props: {
|
||||
return (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="border flex gap-2 items-center text-xs"
|
||||
disabled={props.disabled}
|
||||
className={cn("border flex gap-2 items-center text-xs", props.className)}
|
||||
onClick={async () => {
|
||||
exportMutation.mutate();
|
||||
}}
|
||||
>
|
||||
{exportMutation.isLoading ? (
|
||||
{exportMutation.isPending ? (
|
||||
<>
|
||||
Downloading
|
||||
<Spinner className="size-3" />
|
||||
@@ -14,7 +14,7 @@ export type SidebarLink = {
|
||||
};
|
||||
};
|
||||
|
||||
export type SidebarContentProps = {
|
||||
type SidebarContentProps = {
|
||||
header?: React.ReactNode;
|
||||
links: SidebarLink[];
|
||||
className?: string;
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
} from "@/components/ui/chart";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export type ThirdwebAreaChartProps<TConfig extends ChartConfig> = {
|
||||
type ThirdwebAreaChartProps<TConfig extends ChartConfig> = {
|
||||
// metadata
|
||||
title: string;
|
||||
description?: string;
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
} from "@/components/ui/chart";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export type ThirdwebBarChartProps<TConfig extends ChartConfig> = {
|
||||
type ThirdwebBarChartProps<TConfig extends ChartConfig> = {
|
||||
// metadata
|
||||
title: string;
|
||||
description?: string;
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from "@/components/ui/hover-card";
|
||||
import { Check, Copy, ExternalLinkIcon } from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { isAddress } from "thirdweb";
|
||||
import { ZERO_ADDRESS } from "thirdweb";
|
||||
import {
|
||||
Blobbie,
|
||||
@@ -28,6 +29,7 @@ export function WalletAddress(props: {
|
||||
}) {
|
||||
// default back to zero address if no address provided
|
||||
const address = useMemo(() => props.address || ZERO_ADDRESS, [props.address]);
|
||||
|
||||
const [shortenedAddress, lessShortenedAddress] = useMemo(() => {
|
||||
return [
|
||||
props.shortenAddress !== false
|
||||
@@ -44,6 +46,15 @@ export function WalletAddress(props: {
|
||||
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
|
||||
if (!isAddress(address)) {
|
||||
return <span>Invalid Address ({address})</span>;
|
||||
}
|
||||
|
||||
// special case for zero address
|
||||
if (address === ZERO_ADDRESS) {
|
||||
return <span className="font-mono cursor-pointer">{shortenedAddress}</span>;
|
||||
}
|
||||
|
||||
const copyToClipboard = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(address);
|
||||
|
||||
@@ -113,6 +113,7 @@ const ChartTooltipContent = React.forwardRef<
|
||||
indicator?: "line" | "dot" | "dashed";
|
||||
nameKey?: string;
|
||||
labelKey?: string;
|
||||
valueFormatter?: (v: unknown) => string | undefined;
|
||||
}
|
||||
>(
|
||||
(
|
||||
@@ -130,6 +131,7 @@ const ChartTooltipContent = React.forwardRef<
|
||||
color,
|
||||
nameKey,
|
||||
labelKey,
|
||||
valueFormatter,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
@@ -243,7 +245,9 @@ const ChartTooltipContent = React.forwardRef<
|
||||
</div>
|
||||
{item.value && (
|
||||
<span className="font-mono font-medium tabular-nums text-foreground">
|
||||
{item.value}
|
||||
{valueFormatter
|
||||
? valueFormatter(item.value) || item.value
|
||||
: item.value}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -41,7 +41,7 @@ const DialogContent = React.forwardRef<
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed grid w-full max-w-lg gap-4 border bg-background p-6 shadow-lg duration-300 rounded-t-xl md:rounded-lg border-border",
|
||||
"fixed grid w-full md:max-w-lg gap-4 border bg-background p-6 shadow-lg duration-300 rounded-t-xl md:rounded-lg border-border",
|
||||
// on mobile - put the dialog at the bottom of the screen, animate - slide up and fade in
|
||||
"bottom-0 left-0 right-0",
|
||||
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { Drawer as DrawerPrimitive } from "vaul";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Drawer = ({
|
||||
shouldScaleBackground = true,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
|
||||
<DrawerPrimitive.Root
|
||||
shouldScaleBackground={shouldScaleBackground}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
Drawer.displayName = "Drawer";
|
||||
|
||||
const DrawerTrigger = DrawerPrimitive.Trigger;
|
||||
|
||||
const DrawerPortal = DrawerPrimitive.Portal;
|
||||
|
||||
const DrawerClose = DrawerPrimitive.Close;
|
||||
|
||||
const DrawerOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof DrawerPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DrawerPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn("fixed inset-0 z-50 bg-black/80", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
|
||||
|
||||
const DrawerContent = React.forwardRef<
|
||||
React.ElementRef<typeof DrawerPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DrawerPortal>
|
||||
<DrawerOverlay />
|
||||
<DrawerPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
|
||||
{children}
|
||||
</DrawerPrimitive.Content>
|
||||
</DrawerPortal>
|
||||
));
|
||||
DrawerContent.displayName = "DrawerContent";
|
||||
|
||||
const DrawerHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
DrawerHeader.displayName = "DrawerHeader";
|
||||
|
||||
const DrawerFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
DrawerFooter.displayName = "DrawerFooter";
|
||||
|
||||
const DrawerTitle = React.forwardRef<
|
||||
React.ElementRef<typeof DrawerPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DrawerPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-lg font-semibold leading-none tracking-tight",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
|
||||
|
||||
const DrawerDescription = React.forwardRef<
|
||||
React.ElementRef<typeof DrawerPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DrawerPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
|
||||
|
||||
export {
|
||||
Drawer,
|
||||
DrawerPortal,
|
||||
DrawerOverlay,
|
||||
DrawerTrigger,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerHeader,
|
||||
DrawerFooter,
|
||||
DrawerTitle,
|
||||
DrawerDescription,
|
||||
};
|
||||
@@ -1,61 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
|
||||
import type { VariantProps } from "class-variance-authority";
|
||||
import * as React from "react";
|
||||
|
||||
import { toggleVariants } from "@/components/ui/toggle";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const ToggleGroupContext = React.createContext<
|
||||
VariantProps<typeof toggleVariants>
|
||||
>({
|
||||
size: "default",
|
||||
variant: "default",
|
||||
});
|
||||
|
||||
const ToggleGroup = React.forwardRef<
|
||||
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
|
||||
VariantProps<typeof toggleVariants>
|
||||
>(({ className, variant, size, children, ...props }, ref) => (
|
||||
<ToggleGroupPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("flex items-center justify-center gap-1", className)}
|
||||
{...props}
|
||||
>
|
||||
<ToggleGroupContext.Provider value={{ variant, size }}>
|
||||
{children}
|
||||
</ToggleGroupContext.Provider>
|
||||
</ToggleGroupPrimitive.Root>
|
||||
));
|
||||
|
||||
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
|
||||
|
||||
const ToggleGroupItem = React.forwardRef<
|
||||
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
|
||||
VariantProps<typeof toggleVariants>
|
||||
>(({ className, children, variant, size, ...props }, ref) => {
|
||||
const context = React.useContext(ToggleGroupContext);
|
||||
|
||||
return (
|
||||
<ToggleGroupPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
toggleVariants({
|
||||
variant: context.variant || variant,
|
||||
size: context.size || size,
|
||||
}),
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</ToggleGroupPrimitive.Item>
|
||||
);
|
||||
});
|
||||
|
||||
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
|
||||
|
||||
export { ToggleGroup, ToggleGroupItem };
|
||||
@@ -1,45 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import * as TogglePrimitive from "@radix-ui/react-toggle";
|
||||
import { type VariantProps, cva } from "class-variance-authority";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const toggleVariants = cva(
|
||||
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-transparent",
|
||||
outline:
|
||||
"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-3",
|
||||
sm: "h-9 px-2.5",
|
||||
lg: "h-11 px-5",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const Toggle = React.forwardRef<
|
||||
React.ElementRef<typeof TogglePrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
|
||||
VariantProps<typeof toggleVariants>
|
||||
>(({ className, variant, size, ...props }, ref) => (
|
||||
<TogglePrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(toggleVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
Toggle.displayName = TogglePrimitive.Root.displayName;
|
||||
|
||||
export { Toggle, toggleVariants };
|
||||
@@ -6,8 +6,8 @@ const LOGGED_IN_ONLY_PATHS = [
|
||||
// anything that _starts_ with /cli is logged in only
|
||||
"/cli",
|
||||
"/support",
|
||||
|
||||
// TODO: add any other logged in only paths here
|
||||
// publish page
|
||||
"/contracts/publish",
|
||||
];
|
||||
|
||||
export function isLoginRequired(pathname: string) {
|
||||
|
||||
@@ -16,3 +16,7 @@ export const isProd =
|
||||
"production";
|
||||
|
||||
export const PROD_OR_DEV_URL = isProd ? "thirdweb.com" : "thirdweb-dev.com";
|
||||
|
||||
export const DASHBOARD_STORAGE_URL =
|
||||
process.env.NEXT_PUBLIC_DASHBOARD_UPLOAD_SERVER ||
|
||||
"https://storage.thirdweb.com";
|
||||
|
||||
@@ -15,7 +15,7 @@ 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
|
||||
export const LoadingRouteHref = createStore<string | undefined>(undefined);
|
||||
const LoadingRouteHref = createStore<string | undefined>(undefined);
|
||||
|
||||
export function useDashboardRouter() {
|
||||
const router = useRouter();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type Store<T> = {
|
||||
type Store<T> = {
|
||||
getValue(): T;
|
||||
setValue(newValue: T): void;
|
||||
subscribe(listener: () => void): () => void;
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
--muted-foreground: 0 0% 40%;
|
||||
--accent-foreground: 0 0% 9%;
|
||||
--destructive-foreground: 0 0% 100%;
|
||||
--inverted-foreground: var(--background);
|
||||
--inverted-foreground: 0 0% 100%;
|
||||
--link-foreground: 221.21deg 83.19% 53.33%;
|
||||
--success-text: 142.09 70.56% 35.29%;
|
||||
--warning-text: 38 92% 40%;
|
||||
@@ -72,6 +72,7 @@
|
||||
--warning-text: 38 92% 50%;
|
||||
--destructive-text: 360 72% 55%;
|
||||
--success-text: 142 75% 50%;
|
||||
--inverted-foreground: 0 0% 0%;
|
||||
|
||||
/* Borders */
|
||||
--border: 0 0% 15%;
|
||||
@@ -138,3 +139,8 @@
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
}
|
||||
|
||||
body,
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { ContractType } from "constants/contracts";
|
||||
import { ZERO_ADDRESS } from "thirdweb";
|
||||
|
||||
export const networkKeys = {
|
||||
@@ -109,23 +108,6 @@ export const engineKeys = {
|
||||
[...engineKeys.all, engineId, "queueMetrics"] as const,
|
||||
};
|
||||
|
||||
export const contractKeys = {
|
||||
all: ["contract"] as const,
|
||||
lists: () => [...contractKeys.all, "list"] as const,
|
||||
list: (address = ZERO_ADDRESS) => [...contractKeys.lists(), address] as const,
|
||||
listWithFilters: (address = ZERO_ADDRESS, filters?: ContractType[]) =>
|
||||
[...contractKeys.list(address), { filters }] as const,
|
||||
details: () => [...contractKeys.all, "detail"] as const,
|
||||
detail: (address: string = ZERO_ADDRESS) =>
|
||||
[...contractKeys.details(), address] as const,
|
||||
};
|
||||
|
||||
export const walletKeys = {
|
||||
all: ["balance"] as const,
|
||||
balances: (walletAddress: string) =>
|
||||
[...splitsKeys.all, walletAddress] as const,
|
||||
};
|
||||
|
||||
export const splitsKeys = {
|
||||
all: ["splits"] as const,
|
||||
lists: () => [...splitsKeys.all, "list"] as const,
|
||||
|
||||
@@ -7,7 +7,10 @@ import { getSDKTheme } from "app/components/sdk-component-theme";
|
||||
import { CustomChainRenderer } from "components/selects/CustomChainRenderer";
|
||||
import { mapV4ChainToV5Chain } from "contexts/map-chains";
|
||||
import { useTrack } from "hooks/analytics/useTrack";
|
||||
import { useSupportedChains } from "hooks/chains/configureChains";
|
||||
import {
|
||||
useSupportedChains,
|
||||
useSupportedChainsRecord,
|
||||
} from "hooks/chains/configureChains";
|
||||
import {
|
||||
useAddRecentlyUsedChainId,
|
||||
useRecentlyUsedChains,
|
||||
@@ -37,12 +40,17 @@ export const CustomConnectWallet = (props: {
|
||||
// const setIsNetworkConfigModalOpen = useSetIsNetworkConfigModalOpen();
|
||||
const t = theme === "light" ? "light" : "dark";
|
||||
const allv4Chains = useSupportedChains();
|
||||
const chainsRecord = useSupportedChainsRecord();
|
||||
const favChainsQuery = useFavoriteChains();
|
||||
const setIsNetworkConfigModalOpen = useSetIsNetworkConfigModalOpen();
|
||||
const allChains = useMemo(() => {
|
||||
return allv4Chains.map(mapV4ChainToV5Chain);
|
||||
}, [allv4Chains]);
|
||||
|
||||
const popularChainsWithMeta = useMemo(() => {
|
||||
return popularChains.map((x) => chainsRecord[x.id] || x);
|
||||
}, [chainsRecord]);
|
||||
|
||||
const chainSections = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
@@ -51,14 +59,14 @@ export const CustomConnectWallet = (props: {
|
||||
},
|
||||
{
|
||||
label: "Popular",
|
||||
chains: popularChains,
|
||||
chains: popularChainsWithMeta.map(mapV4ChainToV5Chain),
|
||||
},
|
||||
{
|
||||
label: "Recent",
|
||||
chains: recentChainsv4.map(mapV4ChainToV5Chain),
|
||||
},
|
||||
];
|
||||
}, [recentChainsv4, favChainsQuery.data]);
|
||||
}, [recentChainsv4, favChainsQuery.data, popularChainsWithMeta]);
|
||||
|
||||
// ensures login status on pages that need it
|
||||
const { isLoading, isLoggedIn } = useLoggedInUser();
|
||||
@@ -140,7 +148,7 @@ export const CustomConnectWallet = (props: {
|
||||
);
|
||||
};
|
||||
|
||||
export function ConnectWalletWelcomeScreen(props: {
|
||||
function ConnectWalletWelcomeScreen(props: {
|
||||
theme: "light" | "dark";
|
||||
subtitle?: string;
|
||||
}) {
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from "./useActiveChainId";
|
||||
export * from "./useCommon";
|
||||
export * from "./useWalletNFTs";
|
||||
|
||||
@@ -39,7 +39,11 @@ export function useQueryWithNetwork<
|
||||
networkKeys.chain(activeChainId) as readonly unknown[]
|
||||
).concat(queryKey) as unknown as TQueryKey;
|
||||
|
||||
return useQuery(combinedQueryKey, queryFn, mergedOptions);
|
||||
return useQuery({
|
||||
...mergedOptions,
|
||||
queryKey: combinedQueryKey,
|
||||
queryFn,
|
||||
});
|
||||
}
|
||||
export function useMutationWithInvalidate<
|
||||
TData = unknown,
|
||||
@@ -71,19 +75,20 @@ export function useMutationWithInvalidate<
|
||||
(cacheKeysToInvalidate: TQueryKey[]) => {
|
||||
return Promise.all(
|
||||
cacheKeysToInvalidate.map((cacheKey) => {
|
||||
return queryClient.invalidateQueries(
|
||||
(networkKeys.chain(activeChainId) as readonly unknown[]).concat(
|
||||
cacheKey,
|
||||
) as unknown[],
|
||||
);
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: (
|
||||
networkKeys.chain(activeChainId) as readonly unknown[]
|
||||
).concat(cacheKey) as unknown[],
|
||||
});
|
||||
}),
|
||||
);
|
||||
},
|
||||
[queryClient, activeChainId],
|
||||
);
|
||||
|
||||
return useMutation(mutationFn, {
|
||||
return useMutation({
|
||||
...options,
|
||||
mutationFn,
|
||||
onSuccess: (...args) => {
|
||||
if (options?.onSuccess) {
|
||||
options.onSuccess(...args, invalidate);
|
||||
|
||||
@@ -172,7 +172,7 @@ interface UpdateKeyServiceInput {
|
||||
actions?: string[];
|
||||
}
|
||||
|
||||
interface CreateKeyInput {
|
||||
export interface CreateKeyInput {
|
||||
name?: string;
|
||||
domains?: string[];
|
||||
bundleIds?: string[];
|
||||
@@ -254,15 +254,16 @@ export interface BillingCredit {
|
||||
interface UseAccountInput {
|
||||
refetchInterval?:
|
||||
| number
|
||||
| false
|
||||
| ((
|
||||
data: Account | undefined,
|
||||
query: Query<
|
||||
Account,
|
||||
unknown,
|
||||
Error,
|
||||
Account,
|
||||
readonly ["account", string, "me"]
|
||||
>,
|
||||
) => number | false);
|
||||
) => number | false | undefined)
|
||||
| undefined;
|
||||
}
|
||||
|
||||
export function useAccount({ refetchInterval }: UseAccountInput = {}) {
|
||||
@@ -286,7 +287,7 @@ export function useAccount({ refetchInterval }: UseAccountInput = {}) {
|
||||
return json.data as Account;
|
||||
},
|
||||
enabled: !!user?.address && isLoggedIn,
|
||||
refetchInterval: refetchInterval ?? false,
|
||||
refetchInterval,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -444,8 +445,8 @@ export function useUpdateAccount() {
|
||||
const { user } = useLoggedInUser();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutationWithInvalidate(
|
||||
async (input: UpdateAccountInput) => {
|
||||
return useMutation({
|
||||
mutationFn: async (input: UpdateAccountInput) => {
|
||||
invariant(user?.address, "walletAddress is required");
|
||||
|
||||
const res = await fetch(`${THIRDWEB_API_HOST}/v1/account`, {
|
||||
@@ -465,14 +466,13 @@ export function useUpdateAccount() {
|
||||
|
||||
return json.data;
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(
|
||||
accountKeys.me(user?.address as string),
|
||||
);
|
||||
},
|
||||
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: accountKeys.me(user?.address as string),
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateAccountPlan(waitForWebhook?: boolean) {
|
||||
@@ -507,13 +507,15 @@ export function useUpdateAccountPlan(waitForWebhook?: boolean) {
|
||||
return json.data;
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
onSuccess: async () => {
|
||||
// invalidate usage data as limits are different
|
||||
queryClient.invalidateQueries(accountKeys.me(user?.address as string));
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: accountKeys.me(user?.address as string),
|
||||
});
|
||||
|
||||
return queryClient.invalidateQueries(
|
||||
accountKeys.usage(user?.address as string),
|
||||
);
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: accountKeys.usage(user?.address as string),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -545,9 +547,9 @@ export function useUpdateNotifications() {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(
|
||||
accountKeys.me(user?.address as string),
|
||||
);
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: accountKeys.me(user?.address as string),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -608,16 +610,19 @@ export function useConfirmEmail() {
|
||||
return json.data;
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
onSuccess: async () => {
|
||||
// invalidate related cache, since could be relinking account
|
||||
queryClient.invalidateQueries(apiKeys.keys(user?.address as string));
|
||||
queryClient.invalidateQueries(
|
||||
accountKeys.usage(user?.address as string),
|
||||
);
|
||||
|
||||
return queryClient.invalidateQueries(
|
||||
accountKeys.me(user?.address as string),
|
||||
);
|
||||
return Promise.all([
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: apiKeys.keys(user?.address as string),
|
||||
}),
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: accountKeys.usage(user?.address as string),
|
||||
}),
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: accountKeys.me(user?.address as string),
|
||||
}),
|
||||
]);
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -652,9 +657,9 @@ export function useResendEmailConfirmation() {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(
|
||||
accountKeys.me(user?.address as string),
|
||||
);
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: accountKeys.me(user?.address as string),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -688,9 +693,9 @@ export function useCreatePaymentMethod() {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(
|
||||
accountKeys.me(user?.address as string),
|
||||
);
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: accountKeys.me(user?.address as string),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -723,8 +728,8 @@ export function useCreateApiKey() {
|
||||
const { user } = useLoggedInUser();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation(
|
||||
async (input: CreateKeyInput) => {
|
||||
return useMutation({
|
||||
mutationFn: async (input: CreateKeyInput) => {
|
||||
invariant(user?.address, "walletAddress is required");
|
||||
|
||||
const res = await fetch(`${THIRDWEB_API_HOST}/v1/keys`, {
|
||||
@@ -743,14 +748,13 @@ export function useCreateApiKey() {
|
||||
|
||||
return json.data as ApiKey;
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(
|
||||
apiKeys.keys(user?.address as string),
|
||||
);
|
||||
},
|
||||
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: apiKeys.keys(user?.address as string),
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateApiKey() {
|
||||
@@ -780,9 +784,9 @@ export function useUpdateApiKey() {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(
|
||||
apiKeys.keys(user?.address as string),
|
||||
);
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: apiKeys.keys(user?.address as string),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -814,9 +818,9 @@ export function useRevokeApiKey() {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(
|
||||
apiKeys.keys(user?.address as string),
|
||||
);
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: apiKeys.keys(user?.address as string),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -869,41 +873,14 @@ export const useUpdatePolicies = () => {
|
||||
},
|
||||
{
|
||||
onSuccess: (_, variables) => {
|
||||
return queryClient.invalidateQueries(["policies", variables.serviceId]);
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: ["policies", variables.serviceId],
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export function useAuthorizeWalletWithAccount() {
|
||||
const { user } = useLoggedInUser();
|
||||
|
||||
return useMutationWithInvalidate(
|
||||
async (variables: { token: string; deviceName?: string }) => {
|
||||
invariant(user?.address, "walletAddress is required");
|
||||
|
||||
const res = await fetch(`${THIRDWEB_API_HOST}/v1/jwt/authorize-wallet`, {
|
||||
method: "POST",
|
||||
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
authorization: `Bearer ${variables.token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
deviceName: variables.deviceName,
|
||||
}),
|
||||
});
|
||||
const json = await res.json();
|
||||
|
||||
if (json.error) {
|
||||
throw new Error(json.error.message);
|
||||
}
|
||||
|
||||
return json.data;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function useRevokeAuthorizedWallet() {
|
||||
const { user } = useLoggedInUser();
|
||||
const queryClient = useQueryClient();
|
||||
@@ -935,9 +912,11 @@ export function useRevokeAuthorizedWallet() {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(
|
||||
authorizedWallets.authorizedWallets(user?.address as string),
|
||||
);
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: authorizedWallets.authorizedWallets(
|
||||
user?.address as string,
|
||||
),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -965,14 +944,14 @@ export function useAuthorizedWallets() {
|
||||
return json.data as AuthorizedWallet[];
|
||||
},
|
||||
enabled: !!user?.address && isLoggedIn,
|
||||
cacheTime: 0,
|
||||
gcTime: 0,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export async function fetchChainsFromApi() {
|
||||
async function fetchChainsFromApi() {
|
||||
// always fetch from prod for chains for now
|
||||
// TODO: re-visit this
|
||||
const res = await fetch("https://api.thirdweb.com/v1/chains");
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
export function usePascalCaseContractName(contractName: string) {
|
||||
if (!contractName) {
|
||||
return null;
|
||||
}
|
||||
if (contractName === "nft-drop") {
|
||||
return "NFTDrop";
|
||||
}
|
||||
if (contractName === "edition-drop") {
|
||||
return "EditionDrop";
|
||||
}
|
||||
if (contractName === "nft-collection") {
|
||||
return "NFTCollection";
|
||||
}
|
||||
if (contractName === "edition") {
|
||||
return "Edition";
|
||||
}
|
||||
if (contractName === "token") {
|
||||
return "Token";
|
||||
}
|
||||
if (contractName === "marketplace") {
|
||||
return "Marketplace";
|
||||
}
|
||||
if (contractName === "vote") {
|
||||
return "Vote";
|
||||
}
|
||||
if (contractName === "split") {
|
||||
return "Split";
|
||||
}
|
||||
if (contractName === "token-drop") {
|
||||
return "TokenDrop";
|
||||
}
|
||||
if (contractName === "signature-drop") {
|
||||
return "SignatureDrop";
|
||||
}
|
||||
if (contractName === "pack") {
|
||||
return "Pack";
|
||||
}
|
||||
if (contractName === "multiwrap") {
|
||||
return "Multiwrap";
|
||||
}
|
||||
if (contractName === "marketplace-v3") {
|
||||
return "MarketplaceV3";
|
||||
}
|
||||
return contractName;
|
||||
}
|
||||
@@ -8,12 +8,6 @@ import { engineKeys } from "../cache-keys";
|
||||
import { useMutationWithInvalidate } from "./query/useQueryWithNetwork";
|
||||
import { useLoggedInUser } from "./useLoggedInUser";
|
||||
|
||||
export function useEngineConnectedInstance() {
|
||||
const [instance, setInstance] = useState<EngineInstance | null>(null);
|
||||
|
||||
return { instance, setInstance };
|
||||
}
|
||||
|
||||
export type EngineTier = "STARTER" | "PREMIUM" | "ENTERPRISE";
|
||||
|
||||
// Engine instances
|
||||
@@ -116,7 +110,7 @@ export function useEngineBackendWallets(instance: string) {
|
||||
});
|
||||
}
|
||||
|
||||
export interface EngineSystemHealth {
|
||||
interface EngineSystemHealth {
|
||||
status: string;
|
||||
engineVersion?: string;
|
||||
features?: string[];
|
||||
@@ -143,7 +137,7 @@ export function useEngineSystemHealth(
|
||||
});
|
||||
}
|
||||
|
||||
export interface EngineSystemQueueMetrics {
|
||||
interface EngineSystemQueueMetrics {
|
||||
result: {
|
||||
queued: number;
|
||||
pending: number;
|
||||
@@ -197,24 +191,26 @@ interface UpdateVersionInput {
|
||||
}
|
||||
|
||||
export function useEngineUpdateVersion() {
|
||||
return useMutation(async (input: UpdateVersionInput) => {
|
||||
invariant(input.engineId, "engineId is required");
|
||||
return useMutation({
|
||||
mutationFn: async (input: UpdateVersionInput) => {
|
||||
invariant(input.engineId, "engineId is required");
|
||||
|
||||
const res = await fetch(`${THIRDWEB_API_HOST}/v1/engine/update-version`, {
|
||||
method: "POST",
|
||||
const res = await fetch(`${THIRDWEB_API_HOST}/v1/engine/update-version`, {
|
||||
method: "POST",
|
||||
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
engineId: input.engineId,
|
||||
}),
|
||||
});
|
||||
// we never use the response body
|
||||
res.body?.cancel();
|
||||
if (!res.ok) {
|
||||
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);
|
||||
}
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
engineId: input.engineId,
|
||||
}),
|
||||
});
|
||||
// we never use the response body
|
||||
res.body?.cancel();
|
||||
if (!res.ok) {
|
||||
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -222,8 +218,8 @@ export function useEngineRemoveFromDashboard() {
|
||||
const { user } = useLoggedInUser();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation(
|
||||
async (instanceId: string) => {
|
||||
return useMutation({
|
||||
mutationFn: async (instanceId: string) => {
|
||||
invariant(instanceId, "instance is required");
|
||||
|
||||
const res = await fetch(`${THIRDWEB_API_HOST}/v1/engine/${instanceId}`, {
|
||||
@@ -235,14 +231,13 @@ export function useEngineRemoveFromDashboard() {
|
||||
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);
|
||||
}
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(
|
||||
engineKeys.instances(user?.address as string),
|
||||
);
|
||||
},
|
||||
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: engineKeys.instances(user?.address as string),
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export interface RemoveCloudHostedInput {
|
||||
@@ -255,8 +250,12 @@ export function useEngineRemoveCloudHosted() {
|
||||
const { user } = useLoggedInUser();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation(
|
||||
async ({ instanceId, reason, feedback }: RemoveCloudHostedInput) => {
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
instanceId,
|
||||
reason,
|
||||
feedback,
|
||||
}: RemoveCloudHostedInput) => {
|
||||
const res = await fetch(
|
||||
`${THIRDWEB_API_HOST}/v1/engine/${instanceId}/remove-cloud-hosted`,
|
||||
{
|
||||
@@ -274,14 +273,13 @@ export function useEngineRemoveCloudHosted() {
|
||||
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);
|
||||
}
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(
|
||||
engineKeys.instances(user?.address as string),
|
||||
);
|
||||
},
|
||||
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: engineKeys.instances(user?.address as string),
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export interface EditEngineInstanceInput {
|
||||
@@ -294,8 +292,8 @@ export function useEngineEditInstance() {
|
||||
const { user } = useLoggedInUser();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation(
|
||||
async ({ instanceId, name, url }: EditEngineInstanceInput) => {
|
||||
return useMutation({
|
||||
mutationFn: async ({ instanceId, name, url }: EditEngineInstanceInput) => {
|
||||
const res = await fetch(`${THIRDWEB_API_HOST}/v1/engine/${instanceId}`, {
|
||||
method: "PUT",
|
||||
|
||||
@@ -310,14 +308,13 @@ export function useEngineEditInstance() {
|
||||
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);
|
||||
}
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(
|
||||
engineKeys.instances(user?.address as string),
|
||||
);
|
||||
},
|
||||
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: engineKeys.instances(user?.address as string),
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export type Transaction = {
|
||||
@@ -592,7 +589,9 @@ export function useEngineAddKeypair(instance: string) {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(engineKeys.keypairs(instance));
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: engineKeys.keypairs(instance),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -625,7 +624,9 @@ export function useEngineRemoveKeypair(instance: string) {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(engineKeys.keypairs(instance));
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: engineKeys.keypairs(instance),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -690,7 +691,9 @@ export function useEngineCreateRelayer(instance: string) {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(engineKeys.relayers(instance));
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: engineKeys.relayers(instance),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -723,7 +726,9 @@ export function useEngineRevokeRelayer(instance: string) {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(engineKeys.relayers(instance));
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: engineKeys.relayers(instance),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -761,7 +766,9 @@ export function useEngineUpdateRelayer(instance: string) {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(engineKeys.relayers(instance));
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: engineKeys.relayers(instance),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -839,7 +846,9 @@ export function useEngineSetWalletConfig(instance: string) {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(engineKeys.walletConfig(instance));
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: engineKeys.walletConfig(instance),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -872,9 +881,9 @@ export function useEngineCreateBackendWallet(instance: string) {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(
|
||||
engineKeys.backendWallets(instance),
|
||||
);
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: engineKeys.backendWallets(instance),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -908,9 +917,9 @@ export function useEngineUpdateBackendWallet(instance: string) {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(
|
||||
engineKeys.backendWallets(instance),
|
||||
);
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: engineKeys.backendWallets(instance),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -959,9 +968,9 @@ export function useEngineImportBackendWallet(instance: string) {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(
|
||||
engineKeys.backendWallets(instance),
|
||||
);
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: engineKeys.backendWallets(instance),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -990,7 +999,9 @@ export function useEngineGrantPermissions(instance: string) {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(engineKeys.permissions(instance));
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: engineKeys.permissions(instance),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -1023,7 +1034,9 @@ export function useEngineRevokePermissions(instance: string) {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(engineKeys.permissions(instance));
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: engineKeys.permissions(instance),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -1056,7 +1069,9 @@ export function useEngineCreateAccessToken(instance: string) {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(engineKeys.accessTokens(instance));
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: engineKeys.accessTokens(instance),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -1089,7 +1104,9 @@ export function useEngineRevokeAccessToken(instance: string) {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(engineKeys.accessTokens(instance));
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: engineKeys.accessTokens(instance),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -1123,7 +1140,9 @@ export function useEngineUpdateAccessToken(instance: string) {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(engineKeys.accessTokens(instance));
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: engineKeys.accessTokens(instance),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -1158,7 +1177,9 @@ export function useEngineCreateWebhook(instance: string) {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(engineKeys.webhooks(instance));
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: engineKeys.webhooks(instance),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -1191,7 +1212,9 @@ export function useEngineRevokeWebhook(instance: string) {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(engineKeys.webhooks(instance));
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: engineKeys.webhooks(instance),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -1208,31 +1231,33 @@ type SendTokensInput = {
|
||||
export function useEngineSendTokens(instance: string) {
|
||||
const token = useLoggedInUser().user?.jwt ?? null;
|
||||
|
||||
return useMutation(async (input: SendTokensInput) => {
|
||||
invariant(instance, "instance is required");
|
||||
return useMutation({
|
||||
mutationFn: async (input: SendTokensInput) => {
|
||||
invariant(instance, "instance is required");
|
||||
|
||||
const res = await fetch(
|
||||
`${instance}backend-wallet/${input.chainId}/transfer`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
...getEngineRequestHeaders(token),
|
||||
"x-backend-wallet-address": input.fromAddress,
|
||||
const res = await fetch(
|
||||
`${instance}backend-wallet/${input.chainId}/transfer`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
...getEngineRequestHeaders(token),
|
||||
"x-backend-wallet-address": input.fromAddress,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
to: input.toAddress,
|
||||
amount: input.amount.toString(),
|
||||
currencyAddress: input.currencyAddress,
|
||||
}),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
to: input.toAddress,
|
||||
amount: input.amount.toString(),
|
||||
currencyAddress: input.currencyAddress,
|
||||
}),
|
||||
},
|
||||
);
|
||||
const json = await res.json();
|
||||
);
|
||||
const json = await res.json();
|
||||
|
||||
if (json.error) {
|
||||
throw new Error(json.error.message);
|
||||
}
|
||||
if (json.error) {
|
||||
throw new Error(json.error.message);
|
||||
}
|
||||
|
||||
return json.result;
|
||||
return json.result;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1263,8 +1288,8 @@ export function useEngineSetCorsConfiguration(instance: string) {
|
||||
const queryClient = useQueryClient();
|
||||
const token = useLoggedInUser().user?.jwt ?? null;
|
||||
|
||||
return useMutation(
|
||||
async (input: SetCorsUrlInput) => {
|
||||
return useMutation({
|
||||
mutationFn: async (input: SetCorsUrlInput) => {
|
||||
invariant(instance, "instance is required");
|
||||
|
||||
const res = await fetch(`${instance}configuration/cors`, {
|
||||
@@ -1280,12 +1305,13 @@ export function useEngineSetCorsConfiguration(instance: string) {
|
||||
|
||||
return json.result;
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(engineKeys.corsUrls(instance));
|
||||
},
|
||||
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: engineKeys.corsUrls(instance),
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function useEngineIpAllowlistConfiguration(instance: string) {
|
||||
@@ -1320,8 +1346,8 @@ export function useEngineSetIpAllowlistConfiguration(instance: string) {
|
||||
const queryClient = useQueryClient();
|
||||
const token = useLoggedInUser().user?.jwt ?? null;
|
||||
|
||||
return useMutation(
|
||||
async (input: SetIpAllowlistInput) => {
|
||||
return useMutation({
|
||||
mutationFn: async (input: SetIpAllowlistInput) => {
|
||||
invariant(instance, "instance is required");
|
||||
|
||||
const res = await fetch(`${instance}configuration/ip-allowlist`, {
|
||||
@@ -1337,12 +1363,13 @@ export function useEngineSetIpAllowlistConfiguration(instance: string) {
|
||||
|
||||
return json.result;
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(engineKeys.ipAllowlist(instance));
|
||||
},
|
||||
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: engineKeys.ipAllowlist(instance),
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export interface EngineContractSubscription {
|
||||
@@ -1392,8 +1419,8 @@ export function useEngineAddContractSubscription(instance: string) {
|
||||
const queryClient = useQueryClient();
|
||||
const token = useLoggedInUser().user?.jwt ?? null;
|
||||
|
||||
return useMutation(
|
||||
async (input: AddContractSubscriptionInput) => {
|
||||
return useMutation({
|
||||
mutationFn: async (input: AddContractSubscriptionInput) => {
|
||||
invariant(instance, "instance is required");
|
||||
|
||||
const res = await fetch(`${instance}contract-subscriptions/add`, {
|
||||
@@ -1409,17 +1436,16 @@ export function useEngineAddContractSubscription(instance: string) {
|
||||
|
||||
return json.result;
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(
|
||||
engineKeys.contractSubscriptions(instance),
|
||||
);
|
||||
},
|
||||
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: engineKeys.contractSubscriptions(instance),
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export interface RemoveContractSubscriptionInput {
|
||||
interface RemoveContractSubscriptionInput {
|
||||
contractSubscriptionId: string;
|
||||
}
|
||||
|
||||
@@ -1427,8 +1453,8 @@ export function useEngineRemoveContractSubscription(instance: string) {
|
||||
const queryClient = useQueryClient();
|
||||
const token = useLoggedInUser().user?.jwt ?? null;
|
||||
|
||||
return useMutation(
|
||||
async (input: RemoveContractSubscriptionInput) => {
|
||||
return useMutation({
|
||||
mutationFn: async (input: RemoveContractSubscriptionInput) => {
|
||||
invariant(instance, "instance is required");
|
||||
|
||||
const res = await fetch(`${instance}contract-subscriptions/remove`, {
|
||||
@@ -1444,14 +1470,13 @@ export function useEngineRemoveContractSubscription(instance: string) {
|
||||
|
||||
return json.result;
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(
|
||||
engineKeys.contractSubscriptions(instance),
|
||||
);
|
||||
},
|
||||
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: engineKeys.contractSubscriptions(instance),
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function useEngineSubscriptionsLastBlock(
|
||||
@@ -1481,7 +1506,7 @@ export function useEngineSubscriptionsLastBlock(
|
||||
});
|
||||
}
|
||||
|
||||
export interface EngineResourceMetrics {
|
||||
interface EngineResourceMetrics {
|
||||
error: string;
|
||||
data: {
|
||||
cpu: number;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useDashboardRouter } from "@/lib/DashboardRouter";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import type { EnsureLoginResponse } from "app/api/auth/ensure-login/route";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useRef } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import {
|
||||
useActiveAccount,
|
||||
useActiveWalletConnectionStatus,
|
||||
@@ -63,20 +63,23 @@ export function useLoggedInUser(): {
|
||||
|
||||
return (await res.json()) as EnsureLoginResponse;
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
if (data.redirectTo) {
|
||||
router.replace(data.redirectTo);
|
||||
}
|
||||
if (data.jwt) {
|
||||
// necessary for legacy things for now (SDK picks it up from there)
|
||||
// eslint-disable-next-line react-compiler/react-compiler
|
||||
window.TW_AUTH_TOKEN = data.jwt;
|
||||
} else {
|
||||
window.TW_AUTH_TOKEN = undefined;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// legit use-case for now
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
useEffect(() => {
|
||||
if (query.data?.redirectTo) {
|
||||
router.replace(query.data.redirectTo);
|
||||
}
|
||||
if (query.data?.jwt) {
|
||||
// necessary for legacy things for now (SDK picks it up from there)
|
||||
// eslint-disable-next-line react-compiler/react-compiler
|
||||
window.TW_AUTH_TOKEN = query.data.jwt;
|
||||
} else {
|
||||
window.TW_AUTH_TOKEN = undefined;
|
||||
}
|
||||
}, [query.data?.redirectTo, query.data?.jwt, router]);
|
||||
|
||||
// if we are "disconnected" we are not logged in
|
||||
if (connectionStatus === "disconnected") {
|
||||
return {
|
||||
|
||||
@@ -15,7 +15,7 @@ import { remove } from "thirdweb/extensions/thirdweb";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
function useMultiChainRegContractList(walletAddress?: string) {
|
||||
export function useMultiChainRegContractList(walletAddress?: string) {
|
||||
return useQuery({
|
||||
queryKey: ["dashboard-registry", walletAddress, "multichain-contract-list"],
|
||||
queryFn: async () => {
|
||||
@@ -88,8 +88,8 @@ export function useRemoveContractMutation() {
|
||||
const { chainIdToChainRecord } = useAllChainsData();
|
||||
const queryClient = useQueryClient();
|
||||
const account = useActiveAccount();
|
||||
return useMutation(
|
||||
async (data: RemoveContractParams) => {
|
||||
return useMutation({
|
||||
mutationFn: async (data: RemoveContractParams) => {
|
||||
invariant(chainIdToChainRecord, "chains not initialzed yet");
|
||||
invariant(data.chainId, "chainId not provided");
|
||||
invariant(account, "No wallet connected");
|
||||
@@ -111,12 +111,13 @@ export function useRemoveContractMutation() {
|
||||
},
|
||||
});
|
||||
},
|
||||
{
|
||||
onSettled: () => {
|
||||
return queryClient.invalidateQueries(["dashboard-registry"]);
|
||||
},
|
||||
|
||||
onSettled: () => {
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: ["dashboard-registry"],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
type AddContractParams = {
|
||||
@@ -129,8 +130,8 @@ export function useAddContractMutation() {
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation(
|
||||
async (data: AddContractParams) => {
|
||||
return useMutation({
|
||||
mutationFn: async (data: AddContractParams) => {
|
||||
invariant(account, "cannot add a contract without an address");
|
||||
|
||||
return await addContractToMultiChainRegistry(
|
||||
@@ -142,10 +143,11 @@ export function useAddContractMutation() {
|
||||
300000n,
|
||||
);
|
||||
},
|
||||
{
|
||||
onSettled: () => {
|
||||
return queryClient.invalidateQueries(["dashboard-registry"]);
|
||||
},
|
||||
|
||||
onSettled: () => {
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: ["dashboard-registry"],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
+4
-5
@@ -92,10 +92,9 @@ export function FaucetButton({
|
||||
});
|
||||
},
|
||||
onSettled: () => {
|
||||
return queryClient.invalidateQueries([
|
||||
"testnet-faucet-can-claim",
|
||||
chainId,
|
||||
]);
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: ["testnet-faucet-can-claim", chainId],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -175,7 +174,7 @@ export function FaucetButton({
|
||||
});
|
||||
}}
|
||||
>
|
||||
{claimMutation.isLoading ? (
|
||||
{claimMutation.isPending ? (
|
||||
<>
|
||||
Claiming <Spinner className="size-3" />
|
||||
</>
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ export const AddChainToWallet: React.FC<AddChainToWalletProps> = (props) => {
|
||||
},
|
||||
});
|
||||
|
||||
const [debouncedLoading] = useDebounce(switchChainMutation.isLoading, 50);
|
||||
const [debouncedLoading] = useDebounce(switchChainMutation.isPending, 50);
|
||||
|
||||
const disabled = debouncedLoading || activeWalletChainId === props.chain?.id;
|
||||
|
||||
|
||||
+6
-7
@@ -7,13 +7,10 @@ import { thirdwebClient } from "@/constants/client";
|
||||
import { isProd } from "@/constants/env";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { CircleCheck, XIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { hostnameEndsWith } from "utils/url";
|
||||
import { PrimaryInfoItem } from "../server/primary-info-item";
|
||||
|
||||
function useChainStatswithRPC(_rpcUrl: string) {
|
||||
const [shouldRefetch, setShouldRefetch] = useState(true);
|
||||
|
||||
let rpcUrl = _rpcUrl.replace(
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
"${THIRDWEB_API_KEY}",
|
||||
@@ -49,12 +46,14 @@ function useChainStatswithRPC(_rpcUrl: string) {
|
||||
blockNumber: Number.parseInt(json.result, 16),
|
||||
};
|
||||
},
|
||||
refetchInterval: shouldRefetch ? 5 * 1000 : undefined,
|
||||
refetchInterval: (query) => {
|
||||
if (query.state.error) {
|
||||
return false;
|
||||
}
|
||||
return 5 * 1000;
|
||||
},
|
||||
enabled: !!rpcUrl,
|
||||
refetchOnWindowFocus: false,
|
||||
onError: () => {
|
||||
setShouldRefetch(false);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
-2
@@ -20,7 +20,6 @@ export function ChainHeader(props: ChainHeaderProps) {
|
||||
// force the banner image to be 4:1 aspect ratio and full-width on mobile devices
|
||||
<div className="flex flex-col">
|
||||
{!props.headerImageUrl && <div className="h-8 md:hidden" />}
|
||||
|
||||
<AspectRatio
|
||||
ratio={props.headerImageUrl ? 4 : 8}
|
||||
className="border-b border-border max-sm:-mx-4"
|
||||
@@ -34,7 +33,6 @@ export function ChainHeader(props: ChainHeaderProps) {
|
||||
/>
|
||||
)}
|
||||
</AspectRatio>
|
||||
|
||||
{/* below header */}
|
||||
<div className="relative flex flex-row justify-end items-end">
|
||||
{/* chain logo */}
|
||||
|
||||
+28
-2
@@ -88,7 +88,7 @@ export async function ChainListRow({
|
||||
key={p.name}
|
||||
icon={p.icon}
|
||||
label={p.name}
|
||||
href={`/${chainSlug}/${p.id === "contracts" ? "" : p.id}`}
|
||||
href={pidToHref(p.id)}
|
||||
isEnabled={enabledServices.includes(p.id)}
|
||||
/>
|
||||
);
|
||||
@@ -100,6 +100,29 @@ export async function ChainListRow({
|
||||
);
|
||||
}
|
||||
|
||||
function pidToHref(pid: (typeof products)[number]["id"]) {
|
||||
switch (pid) {
|
||||
case "account-abstraction": {
|
||||
return "https://portal.thirdweb.com/connect/account-abstraction/overview";
|
||||
}
|
||||
case "contracts": {
|
||||
return "https://portal.thirdweb.com/contracts";
|
||||
}
|
||||
case "connect-sdk": {
|
||||
return "https://portal.thirdweb.com/connect";
|
||||
}
|
||||
case "engine": {
|
||||
return "https://portal.thirdweb.com/engine";
|
||||
}
|
||||
case "pay": {
|
||||
return "https://portal.thirdweb.com/connect/pay/overview";
|
||||
}
|
||||
case "rpc-edge": {
|
||||
return "https://portal.thirdweb.com/infrastructure/rpc-edge/overview";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ProductIcon(props: {
|
||||
icon: React.FC<{ className?: string }>;
|
||||
label: string;
|
||||
@@ -117,7 +140,10 @@ function ProductIcon(props: {
|
||||
)
|
||||
}
|
||||
>
|
||||
<Link href={props.href}>
|
||||
<Link
|
||||
href={props.href}
|
||||
target={props.href.startsWith("http") ? "_blank" : undefined}
|
||||
>
|
||||
<props.icon className={cn("size-8", !props.isEnabled && "grayscale")} />
|
||||
</Link>
|
||||
</ToolTipLabel>
|
||||
|
||||
@@ -91,7 +91,7 @@ export function StarButton(props: {
|
||||
}}
|
||||
disabled={
|
||||
!loggedInUser.user?.address ||
|
||||
mutation.isLoading ||
|
||||
mutation.isPending ||
|
||||
favChainsQuery.isLoading
|
||||
}
|
||||
>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "server-only";
|
||||
|
||||
import { redirect } from "next/navigation";
|
||||
import { notFound } from "next/navigation";
|
||||
import type { ChainCTAProps } from "./[chain_id]/(chainPage)/components/server/cta-card";
|
||||
import zeroGCTA from "./temp-assets/0gCTA.png";
|
||||
import zeroGBanner from "./temp-assets/0gLabsBanner.png";
|
||||
@@ -89,7 +89,7 @@ export async function getChain(
|
||||
]);
|
||||
|
||||
if (!chain.data) {
|
||||
redirect("/404");
|
||||
notFound();
|
||||
}
|
||||
return {
|
||||
...chain.data,
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { setOverrides } from "lib/vercel-utils";
|
||||
import { fetchDeployMetadata } from "thirdweb/contract";
|
||||
import { thirdwebClient } from "../../../../../@/constants/client";
|
||||
import { DeployContractInfo } from "../../../published-contract/components/contract-info";
|
||||
import { DeployFormForUri } from "../../../published-contract/components/uri-based-deploy";
|
||||
|
||||
setOverrides();
|
||||
|
||||
type DirectDeployPageProps = {
|
||||
params: {
|
||||
compiler_uri: string;
|
||||
};
|
||||
};
|
||||
|
||||
export default async function DirectDeployPage(props: DirectDeployPageProps) {
|
||||
const parsedUri = decodeURIComponent(props.params.compiler_uri);
|
||||
const metadata = await fetchDeployMetadata({
|
||||
client: thirdwebClient,
|
||||
// force `ipfs://` prefix
|
||||
uri: parsedUri.startsWith("ipfs://") ? parsedUri : `ipfs://${parsedUri}`,
|
||||
});
|
||||
return (
|
||||
<div className="container py-8 flex flex-col gap-4">
|
||||
<DeployContractInfo
|
||||
name={metadata.name}
|
||||
displayName={metadata.displayName}
|
||||
description={metadata.description}
|
||||
logo={metadata.logo}
|
||||
/>
|
||||
<DeployFormForUri contractMetadata={metadata} modules={null} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { ChakraProviderSetup } from "@/components/ChakraProviderSetup";
|
||||
import { thirdwebClient } from "@/constants/client";
|
||||
import { ContractPublishForm } from "components/contract-components/contract-publish-form";
|
||||
import { setOverrides } from "lib/vercel-utils";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { fetchDeployMetadata } from "thirdweb/contract";
|
||||
|
||||
setOverrides();
|
||||
|
||||
type DirectDeployPageProps = {
|
||||
params: {
|
||||
publish_uri: string;
|
||||
};
|
||||
};
|
||||
|
||||
export default async function PublishContractPage(
|
||||
props: DirectDeployPageProps,
|
||||
) {
|
||||
const decodedPublishUri = decodeURIComponent(props.params.publish_uri);
|
||||
const publishUri = decodedPublishUri.startsWith("ipfs://")
|
||||
? decodedPublishUri
|
||||
: `ipfs://${decodedPublishUri}`;
|
||||
|
||||
const publishMetadata = await fetchDeployMetadata({
|
||||
uri: publishUri,
|
||||
client: thirdwebClient,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="container py-8 flex flex-col gap-8">
|
||||
<ChakraProviderSetup>
|
||||
<ContractPublishForm
|
||||
publishMetadata={publishMetadata}
|
||||
onPublishSuccess={async () => {
|
||||
"use server";
|
||||
// we are pretty brutal here and simply invalidate ALL published contracts (for everyone!) and versions no matter what
|
||||
// TODO: we can be more granular here and only invalidate the specific contract and version etc
|
||||
revalidatePath(
|
||||
"/(dashboard)/published-contract/[publisher]/[contract_id]",
|
||||
"layout",
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</ChakraProviderSetup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+2
-2
@@ -31,7 +31,7 @@ export function AddPartnerForm({ ecosystem }: { ecosystem: Ecosystem }) {
|
||||
resolver: zodResolver(partnerFormSchema),
|
||||
});
|
||||
|
||||
const { addPartner, isLoading } = useAddPartner({
|
||||
const { mutateAsync: addPartner, isPending } = useAddPartner({
|
||||
onSuccess: () => {
|
||||
form.reset();
|
||||
},
|
||||
@@ -182,7 +182,7 @@ export function AddPartnerForm({ ecosystem }: { ecosystem: Ecosystem }) {
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
disabled={isLoading}
|
||||
disabled={isPending}
|
||||
type="submit"
|
||||
variant="outline"
|
||||
className="w-full lg:w-auto"
|
||||
|
||||
+8
-4
@@ -20,7 +20,11 @@ export function IntegrationPermissionsToggle({
|
||||
}
|
||||
| undefined
|
||||
>();
|
||||
const { updateEcosystem, variables, isLoading } = useUpdateEcosystem({
|
||||
const {
|
||||
mutateAsync: updateEcosystem,
|
||||
variables,
|
||||
isPending,
|
||||
} = useUpdateEcosystem({
|
||||
onError: (error) => {
|
||||
const message =
|
||||
error instanceof Error ? error.message : "Failed to create ecosystem";
|
||||
@@ -31,14 +35,14 @@ export function IntegrationPermissionsToggle({
|
||||
return (
|
||||
<RadioGroup
|
||||
defaultValue={ecosystem.permission}
|
||||
value={isLoading ? variables?.permission : ecosystem.permission}
|
||||
value={isPending ? variables?.permission : ecosystem.permission}
|
||||
className="flex flex-col gap-2 py-2 md:flex-row md:gap-4"
|
||||
>
|
||||
<RadioGroupItemButton
|
||||
value="PARTNER_WHITELIST"
|
||||
id="PARTNER_WHITELIST"
|
||||
className={cn(
|
||||
isLoading &&
|
||||
isPending &&
|
||||
variables?.permission === "PARTNER_WHITELIST" &&
|
||||
"animate-pulse",
|
||||
)}
|
||||
@@ -59,7 +63,7 @@ export function IntegrationPermissionsToggle({
|
||||
value="ANYONE"
|
||||
id="ANYONE"
|
||||
className={cn(
|
||||
isLoading && variables?.permission === "ANYONE" && "animate-pulse",
|
||||
isPending && variables?.permission === "ANYONE" && "animate-pulse",
|
||||
)}
|
||||
onClick={() => {
|
||||
if (ecosystem.permission === "ANYONE") return;
|
||||
|
||||
+3
-3
@@ -42,7 +42,7 @@ export function UpdatePartnerForm({
|
||||
},
|
||||
});
|
||||
|
||||
const { updatePartner, isLoading } = useUpdatePartner({
|
||||
const { mutateAsync: updatePartner, isPending } = useUpdatePartner({
|
||||
onSuccess: () => {
|
||||
form.reset();
|
||||
onSuccess();
|
||||
@@ -195,12 +195,12 @@ export function UpdatePartnerForm({
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
disabled={isLoading}
|
||||
disabled={isPending}
|
||||
type="submit"
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
>
|
||||
{isLoading && <Loader2 className="size-4 mr-1 animate-spin" />}
|
||||
{isPending && <Loader2 className="size-4 mr-1 animate-spin" />}
|
||||
Update
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
+8
-7
@@ -69,13 +69,14 @@ function EcosystemRow(props: {
|
||||
partner: Partner;
|
||||
ecosystem: Ecosystem;
|
||||
}) {
|
||||
const { deletePartner, isLoading: isDeleting } = useDeletePartner({
|
||||
onError: (error) => {
|
||||
const message =
|
||||
error instanceof Error ? error.message : "Failed to delete partner";
|
||||
toast.error(message);
|
||||
},
|
||||
});
|
||||
const { mutateAsync: deletePartner, isPending: isDeleting } =
|
||||
useDeletePartner({
|
||||
onError: (error) => {
|
||||
const message =
|
||||
error instanceof Error ? error.message : "Failed to delete partner";
|
||||
toast.error(message);
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import z from "zod";
|
||||
import type { PartnerPermission } from "../../types";
|
||||
import { isValidPermission } from "./utils";
|
||||
|
||||
export const isDomainRegex =
|
||||
const isDomainRegex =
|
||||
/^(?:\*|(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*(?:\*(?:\.[a-z0-9][a-z0-9-]{0,61}[a-z0-9](?:\.[a-z0-9][a-z0-9-]{0,61}[a-z0-9])*)?)|(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]|localhost(?::\d{1,5})?)$/;
|
||||
|
||||
export const partnerFormSchema = z.object({
|
||||
|
||||
+5
-9
@@ -6,7 +6,7 @@ import {
|
||||
} from "@tanstack/react-query";
|
||||
import type { Ecosystem, Partner } from "../../../types";
|
||||
|
||||
export type AddPartnerParams = {
|
||||
type AddPartnerParams = {
|
||||
ecosystem: Ecosystem;
|
||||
name: string;
|
||||
allowlistedDomains: string[];
|
||||
@@ -24,7 +24,7 @@ export function useAddPartner(
|
||||
const { isLoggedIn, user } = useLoggedInUser();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutateAsync: addPartner, isLoading } = useMutation({
|
||||
return useMutation({
|
||||
// Returns the created partner object
|
||||
mutationFn: async (params: AddPartnerParams): Promise<Partner> => {
|
||||
if (!isLoggedIn || !user?.jwt) {
|
||||
@@ -74,11 +74,9 @@ export function useAddPartner(
|
||||
return data;
|
||||
},
|
||||
onSuccess: async (partner, variables, context) => {
|
||||
await queryClient.invalidateQueries([
|
||||
"ecosystem",
|
||||
variables.ecosystem.id,
|
||||
"partners",
|
||||
]);
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ["ecosystem", variables.ecosystem.id, "partners"],
|
||||
});
|
||||
if (onSuccess) {
|
||||
return onSuccess(partner, variables, context);
|
||||
}
|
||||
@@ -86,6 +84,4 @@ export function useAddPartner(
|
||||
},
|
||||
...queryOptions,
|
||||
});
|
||||
|
||||
return { addPartner, isLoading };
|
||||
}
|
||||
|
||||
+5
-9
@@ -6,7 +6,7 @@ import {
|
||||
} from "@tanstack/react-query";
|
||||
import type { Ecosystem } from "../../../types";
|
||||
|
||||
export type DeletePartnerParams = {
|
||||
type DeletePartnerParams = {
|
||||
ecosystem: Ecosystem;
|
||||
partnerId: string;
|
||||
};
|
||||
@@ -21,7 +21,7 @@ export function useDeletePartner(
|
||||
const { isLoggedIn, user } = useLoggedInUser();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutateAsync: deletePartner, isLoading } = useMutation({
|
||||
return useMutation({
|
||||
// Returns true on success
|
||||
mutationFn: async (params: DeletePartnerParams): Promise<boolean> => {
|
||||
if (!isLoggedIn || !user?.jwt) {
|
||||
@@ -58,11 +58,9 @@ export function useDeletePartner(
|
||||
return true;
|
||||
},
|
||||
onSuccess: async (partner, variables, context) => {
|
||||
await queryClient.invalidateQueries([
|
||||
"ecosystem",
|
||||
variables.ecosystem.id,
|
||||
"partners",
|
||||
]);
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ["ecosystem", variables.ecosystem.id, "partners"],
|
||||
});
|
||||
if (onSuccess) {
|
||||
return onSuccess(partner, variables, context);
|
||||
}
|
||||
@@ -70,6 +68,4 @@ export function useDeletePartner(
|
||||
},
|
||||
...queryOptions,
|
||||
});
|
||||
|
||||
return { deletePartner, isLoading };
|
||||
}
|
||||
|
||||
+5
-9
@@ -6,7 +6,7 @@ import {
|
||||
} from "@tanstack/react-query";
|
||||
import type { Ecosystem } from "../../../types";
|
||||
|
||||
export type UpdateEcosystemParams = {
|
||||
type UpdateEcosystemParams = {
|
||||
ecosystem: Ecosystem;
|
||||
permission: "PARTNER_WHITELIST" | "ANYONE";
|
||||
};
|
||||
@@ -21,11 +21,7 @@ export function useUpdateEcosystem(
|
||||
const { isLoggedIn, user } = useLoggedInUser();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
mutateAsync: updateEcosystem,
|
||||
isLoading,
|
||||
variables,
|
||||
} = useMutation({
|
||||
return useMutation({
|
||||
// Returns true if the update was successful
|
||||
mutationFn: async (params: UpdateEcosystemParams): Promise<boolean> => {
|
||||
if (!isLoggedIn || !user?.jwt) {
|
||||
@@ -65,7 +61,9 @@ export function useUpdateEcosystem(
|
||||
return true;
|
||||
},
|
||||
onSuccess: async (partner, variables, context) => {
|
||||
await queryClient.invalidateQueries(["ecosystems"]);
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ["ecosystems"],
|
||||
});
|
||||
if (onSuccess) {
|
||||
return onSuccess(partner, variables, context);
|
||||
}
|
||||
@@ -73,6 +71,4 @@ export function useUpdateEcosystem(
|
||||
},
|
||||
...queryOptions,
|
||||
});
|
||||
|
||||
return { updateEcosystem, isLoading, variables };
|
||||
}
|
||||
|
||||
+5
-9
@@ -6,7 +6,7 @@ import {
|
||||
} from "@tanstack/react-query";
|
||||
import type { Ecosystem, Partner } from "../../../types";
|
||||
|
||||
export type UpdatePartnerParams = {
|
||||
type UpdatePartnerParams = {
|
||||
partnerId: string;
|
||||
ecosystem: Ecosystem;
|
||||
name: string;
|
||||
@@ -25,7 +25,7 @@ export function useUpdatePartner(
|
||||
const { isLoggedIn, user } = useLoggedInUser();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutateAsync: updatePartner, isLoading } = useMutation({
|
||||
return useMutation({
|
||||
// Returns the created partner object
|
||||
mutationFn: async (params: UpdatePartnerParams): Promise<Partner> => {
|
||||
if (!isLoggedIn || !user?.jwt) {
|
||||
@@ -73,11 +73,9 @@ export function useUpdatePartner(
|
||||
return data;
|
||||
},
|
||||
onSuccess: async (partner, variables, context) => {
|
||||
await queryClient.invalidateQueries([
|
||||
"ecosystem",
|
||||
variables.ecosystem.id,
|
||||
"partners",
|
||||
]);
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ["ecosystem", variables.ecosystem.id, "partners"],
|
||||
});
|
||||
if (onSuccess) {
|
||||
return onSuccess(partner, variables, context);
|
||||
}
|
||||
@@ -85,6 +83,4 @@ export function useUpdatePartner(
|
||||
},
|
||||
...queryOptions,
|
||||
});
|
||||
|
||||
return { updatePartner, isLoading };
|
||||
}
|
||||
|
||||
+3
-3
@@ -59,7 +59,7 @@ export function CreateEcosystemForm(props: {
|
||||
},
|
||||
});
|
||||
|
||||
const { createEcosystem, isLoading } = useCreateEcosystem({
|
||||
const { mutateAsync: createEcosystem, isPending } = useCreateEcosystem({
|
||||
onError: (error) => {
|
||||
const message =
|
||||
error instanceof Error ? error.message : "Failed to create ecosystem";
|
||||
@@ -181,9 +181,9 @@ export function CreateEcosystemForm(props: {
|
||||
type="submit"
|
||||
variant="primary"
|
||||
className="w-full"
|
||||
disabled={isLoading}
|
||||
disabled={isPending}
|
||||
>
|
||||
{isLoading && <Loader2 className="w-4 h-4 mr-1 animate-spin" />}
|
||||
{isPending && <Loader2 className="w-4 h-4 mr-1 animate-spin" />}
|
||||
Create
|
||||
</Button>
|
||||
)}
|
||||
|
||||
+5
-5
@@ -8,7 +8,7 @@ import {
|
||||
import { THIRDWEB_API_HOST } from "constants/urls";
|
||||
import { upload } from "thirdweb/storage";
|
||||
|
||||
export type CreateEcosystemParams = {
|
||||
type CreateEcosystemParams = {
|
||||
name: string;
|
||||
logo: File;
|
||||
permission: "PARTNER_WHITELIST" | "ANYONE";
|
||||
@@ -24,7 +24,7 @@ export function useCreateEcosystem(
|
||||
const { isLoggedIn } = useLoggedInUser();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutateAsync: createEcosystem, isLoading } = useMutation({
|
||||
return useMutation({
|
||||
// Returns the created ecosystem slug
|
||||
mutationFn: async (params: CreateEcosystemParams): Promise<string> => {
|
||||
if (!isLoggedIn) {
|
||||
@@ -75,10 +75,10 @@ export function useCreateEcosystem(
|
||||
if (onSuccess) {
|
||||
await onSuccess(id, variables, context);
|
||||
}
|
||||
return queryClient.invalidateQueries(["ecosystems"]);
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: ["ecosystems"],
|
||||
});
|
||||
},
|
||||
...queryOptions,
|
||||
});
|
||||
|
||||
return { createEcosystem, isLoading };
|
||||
}
|
||||
|
||||
+9
-5
@@ -55,7 +55,7 @@ type Webhook = {
|
||||
secret: string;
|
||||
};
|
||||
|
||||
export type WebhooksPageProps = {
|
||||
type WebhooksPageProps = {
|
||||
clientId: string;
|
||||
};
|
||||
|
||||
@@ -177,7 +177,9 @@ function CreateWebhookButton(props: PropsWithChildren<WebhooksPageProps>) {
|
||||
return json.result as string;
|
||||
},
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(["webhooks", props.clientId]);
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: ["webhooks", props.clientId],
|
||||
});
|
||||
},
|
||||
});
|
||||
return (
|
||||
@@ -264,7 +266,7 @@ function CreateWebhookButton(props: PropsWithChildren<WebhooksPageProps>) {
|
||||
</FormItem>
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="submit" disabled={createMutation.isLoading}>
|
||||
<Button type="submit" disabled={createMutation.isPending}>
|
||||
Create
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
@@ -293,7 +295,9 @@ function DeleteWebhookButton(
|
||||
return json.result as string;
|
||||
},
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(["webhooks", props.clientId]);
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: ["webhooks", props.clientId],
|
||||
});
|
||||
},
|
||||
});
|
||||
return (
|
||||
@@ -325,7 +329,7 @@ function DeleteWebhookButton(
|
||||
},
|
||||
);
|
||||
}}
|
||||
disabled={deleteMutation.isLoading}
|
||||
disabled={deleteMutation.isPending}
|
||||
>
|
||||
Yes, Delete Webhook
|
||||
</Button>
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { AppFooter } from "@/components/blocks/app-footer";
|
||||
import { DashboardHeader } from "app/components/Header/DashboardHeader";
|
||||
import { ErrorProvider } from "../../contexts/error-handler";
|
||||
|
||||
export default function DashboardLayout(props: { children: React.ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<ErrorProvider>
|
||||
<div className="flex flex-col h-full bg-background">
|
||||
<DashboardHeader />
|
||||
<main className="grow">{props.children}</main>
|
||||
<AppFooter />
|
||||
</div>
|
||||
</>
|
||||
</ErrorProvider>
|
||||
);
|
||||
}
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { setOverrides } from "lib/vercel-utils";
|
||||
import { DeployFormForPublishInfo } from "../../../../components/publish-based-deploy";
|
||||
import { moduleFromBase64 } from "../../../../utils/module-base-64";
|
||||
|
||||
setOverrides();
|
||||
|
||||
export default function PublishedContractVersionDeployPage({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: {
|
||||
publisher: string;
|
||||
contract_id: string;
|
||||
version: string;
|
||||
};
|
||||
searchParams: {
|
||||
module?: string[];
|
||||
};
|
||||
}) {
|
||||
const modules = searchParams.module
|
||||
?.map((m) => moduleFromBase64(m))
|
||||
.filter((m) => m !== null);
|
||||
return <DeployFormForPublishInfo {...params} modules={modules} />;
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
import { fetchPublisherProfile } from "components/contract-components/fetch-contracts-with-versions";
|
||||
import { format } from "date-fns/format";
|
||||
import { correctAndUniqueLicenses } from "lib/licenses";
|
||||
import { setOverrides } from "lib/vercel-utils";
|
||||
import { getPublishedContractsWithPublisherMapping } from "../utils/getPublishedContractsWithPublisherMapping";
|
||||
import { publishedContractOGImageTemplate } from "../utils/publishedContractOGImageTemplate";
|
||||
|
||||
export const runtime = "edge";
|
||||
|
||||
export const size = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
};
|
||||
|
||||
export default async function Image(props: {
|
||||
params: {
|
||||
publisher: string;
|
||||
contract_id: string;
|
||||
version: string;
|
||||
};
|
||||
}) {
|
||||
setOverrides();
|
||||
const { publisher, contract_id } = props.params;
|
||||
|
||||
const [publishedContracts, publisherProfile] = await Promise.all([
|
||||
getPublishedContractsWithPublisherMapping({
|
||||
publisher: publisher,
|
||||
contract_id: contract_id,
|
||||
}),
|
||||
fetchPublisherProfile(publisher),
|
||||
]);
|
||||
|
||||
const publishedContract =
|
||||
publishedContracts.find((p) => p.version === props.params.version) ||
|
||||
publishedContracts[0];
|
||||
|
||||
const publishedContractName =
|
||||
publishedContract?.displayName || publishedContract?.name;
|
||||
|
||||
return publishedContractOGImageTemplate({
|
||||
title: publishedContractName,
|
||||
description: publishedContract.description,
|
||||
version: publishedContract.version || "latest",
|
||||
publisher: publisherProfile?.name || publisher,
|
||||
license: correctAndUniqueLicenses(publishedContract.licenses),
|
||||
publishDate: format(
|
||||
new Date(
|
||||
Number.parseInt(publishedContract.publishTimestamp.toString() || "0") *
|
||||
1000,
|
||||
),
|
||||
"MMM dd, yyyy",
|
||||
),
|
||||
logo: publishedContract.logo,
|
||||
publisherAvatar: publisherProfile?.avatar,
|
||||
});
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { thirdwebClient } from "@/constants/client";
|
||||
import { SimpleGrid } from "@chakra-ui/react";
|
||||
import { isAddress } from "thirdweb";
|
||||
import { resolveAddress } from "thirdweb/extensions/ens";
|
||||
import { ChakraProviderSetup } from "../../../../../../@/components/ChakraProviderSetup";
|
||||
import { fetchPublishedContractVersions } from "../../../../../../components/contract-components/fetch-contracts-with-versions";
|
||||
import { PublishedContract } from "../../../../../../components/contract-components/published-contract";
|
||||
import { setOverrides } from "../../../../../../lib/vercel-utils";
|
||||
import { PublishedActions } from "../../../components/contract-actions-published.client";
|
||||
import { DeployContractHeader } from "../../../components/contract-header";
|
||||
|
||||
setOverrides();
|
||||
|
||||
function mapThirdwebPublisher(publisher: string) {
|
||||
if (publisher === "thirdweb.eth") {
|
||||
return "deployer.thirdweb.eth";
|
||||
}
|
||||
return publisher;
|
||||
}
|
||||
|
||||
type PublishedContractDeployPageProps = {
|
||||
params: {
|
||||
publisher: string;
|
||||
contract_id: string;
|
||||
version: string;
|
||||
};
|
||||
};
|
||||
|
||||
export default async function PublishedContractPage(
|
||||
props: PublishedContractDeployPageProps,
|
||||
) {
|
||||
// resolve ENS if required
|
||||
const publisherAddress = isAddress(props.params.publisher)
|
||||
? props.params.publisher
|
||||
: await resolveAddress({
|
||||
client: thirdwebClient,
|
||||
name: mapThirdwebPublisher(props.params.publisher),
|
||||
});
|
||||
|
||||
// get all the published versions of the contract
|
||||
const publishedContractVersions = await fetchPublishedContractVersions(
|
||||
publisherAddress,
|
||||
props.params.contract_id,
|
||||
);
|
||||
|
||||
// determine the "active" version of the contract based on the version that is passed
|
||||
const publishedContract =
|
||||
publishedContractVersions.find((v) => v.version === props.params.version) ||
|
||||
publishedContractVersions[0];
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeployContractHeader
|
||||
{...props.params}
|
||||
allVersions={publishedContractVersions}
|
||||
activeVersion={publishedContract}
|
||||
>
|
||||
<PublishedActions
|
||||
{...props.params}
|
||||
dispayName={publishedContract.displayName || publishedContract.name}
|
||||
/>
|
||||
</DeployContractHeader>
|
||||
<Separator />
|
||||
{/* TODO: remove the chakra things :) */}
|
||||
<ChakraProviderSetup>
|
||||
<SimpleGrid columns={12} gap={{ base: 6, md: 10 }} w="full">
|
||||
<PublishedContract
|
||||
publishedContract={publishedContract}
|
||||
walletOrEns={props.params.publisher}
|
||||
/>
|
||||
</SimpleGrid>
|
||||
</ChakraProviderSetup>
|
||||
</>
|
||||
);
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/components/ui/breadcrumb";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { Fragment } from "react";
|
||||
import { isAddress } from "thirdweb";
|
||||
import { shortenAddress } from "thirdweb/utils";
|
||||
|
||||
export function PublishedContractBreadcrumbs() {
|
||||
const pathname = usePathname() || "";
|
||||
const segments = pathname.split("/").filter((segment) => segment !== "");
|
||||
|
||||
return (
|
||||
<Breadcrumb className="py-4 px-6 border-b border-border">
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink aria-label="Explore" asChild>
|
||||
<Link href="/explore">Explore</Link>
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
|
||||
{segments.map((segment, index) => {
|
||||
const href = `/${segments.slice(0, index + 1).join("/")}`;
|
||||
const isLast = index === segments.length - 1;
|
||||
const renderedSegment = isAddress(segment)
|
||||
? shortenAddress(segment)
|
||||
: segment;
|
||||
|
||||
return (
|
||||
<Fragment key={href}>
|
||||
<BreadcrumbItem>
|
||||
{isLast ? (
|
||||
<BreadcrumbPage>{renderedSegment}</BreadcrumbPage>
|
||||
) : (
|
||||
<BreadcrumbLink asChild aria-label={segment}>
|
||||
<Link href={href}>{renderedSegment}</Link>
|
||||
</BreadcrumbLink>
|
||||
)}
|
||||
</BreadcrumbItem>
|
||||
{!isLast && <BreadcrumbSeparator />}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
);
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
"use client";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useMemo } from "react";
|
||||
import { moduleFromBase64 } from "../../../utils/module-base-64";
|
||||
|
||||
function useModules() {
|
||||
const modules = useSearchParams()?.getAll("module") || [];
|
||||
return useMemo(
|
||||
() => modules.map(moduleFromBase64).filter((m) => m !== null),
|
||||
[modules],
|
||||
);
|
||||
}
|
||||
|
||||
export function ModuleList() {
|
||||
const modules = useModules();
|
||||
|
||||
// if nothing, render nothing
|
||||
if (!modules.length) {
|
||||
return null;
|
||||
}
|
||||
// else render stuff
|
||||
return (
|
||||
<div className="flex flex-row flex-wrap gap-2">
|
||||
{modules.map((m) => (
|
||||
<Badge
|
||||
key={`${m.publisher}/${m.moduleId}/${m.version}`}
|
||||
variant="default"
|
||||
>
|
||||
{m.displayName}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { setOverrides } from "lib/vercel-utils";
|
||||
import { DeployFormForPublishInfo } from "../../../components/publish-based-deploy";
|
||||
import { moduleFromBase64 } from "../../../utils/module-base-64";
|
||||
|
||||
setOverrides();
|
||||
|
||||
type Props = {
|
||||
params: {
|
||||
publisher: string;
|
||||
contract_id: string;
|
||||
};
|
||||
searchParams: {
|
||||
module?: string[];
|
||||
};
|
||||
};
|
||||
|
||||
export default function PublishedContractDeployPage({
|
||||
params,
|
||||
searchParams,
|
||||
}: Props) {
|
||||
const modules = searchParams.module
|
||||
?.map((m) => moduleFromBase64(m))
|
||||
.filter((m) => m !== null);
|
||||
return <DeployFormForPublishInfo {...params} modules={modules} />;
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { PublishedContractBreadcrumbs } from "./components/breadcrumbs.client";
|
||||
import { getPublishedContractsWithPublisherMapping } from "./utils/getPublishedContractsWithPublisherMapping";
|
||||
|
||||
type Params = { publisher: string; contract_id: string };
|
||||
|
||||
export default function PublishedContractLayout(props: {
|
||||
children: React.ReactNode;
|
||||
params: Params;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<PublishedContractBreadcrumbs />
|
||||
<div className="container flex flex-col gap-8 py-8">{props.children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: { params: Params }) {
|
||||
const { publisher, contract_id } = params;
|
||||
|
||||
const publishedContracts = await getPublishedContractsWithPublisherMapping({
|
||||
publisher: publisher,
|
||||
contract_id: contract_id,
|
||||
});
|
||||
|
||||
const publishedContract = publishedContracts[0];
|
||||
|
||||
const publishedContractName =
|
||||
publishedContract?.displayName || publishedContract?.name;
|
||||
|
||||
return {
|
||||
title: `${publishedContractName} | Published Smart Contract`,
|
||||
description: `${publishedContract.description}${publishedContract.description ? ". " : ""}Deploy ${publishedContractName} in one click with thirdweb.`,
|
||||
};
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
import { fetchPublisherProfile } from "components/contract-components/fetch-contracts-with-versions";
|
||||
import { format } from "date-fns/format";
|
||||
import { correctAndUniqueLicenses } from "lib/licenses";
|
||||
import { setOverrides } from "lib/vercel-utils";
|
||||
import { getPublishedContractsWithPublisherMapping } from "./utils/getPublishedContractsWithPublisherMapping";
|
||||
import { publishedContractOGImageTemplate } from "./utils/publishedContractOGImageTemplate";
|
||||
|
||||
export const runtime = "edge";
|
||||
|
||||
export const size = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
};
|
||||
|
||||
export default async function Image(props: {
|
||||
params: {
|
||||
publisher: string;
|
||||
contract_id: string;
|
||||
};
|
||||
}) {
|
||||
setOverrides();
|
||||
const { publisher, contract_id } = props.params;
|
||||
|
||||
const [publishedContracts, publisherProfile] = await Promise.all([
|
||||
getPublishedContractsWithPublisherMapping({
|
||||
publisher: publisher,
|
||||
contract_id: contract_id,
|
||||
}),
|
||||
fetchPublisherProfile(publisher),
|
||||
]);
|
||||
|
||||
const publishedContract = publishedContracts[0];
|
||||
|
||||
const publishedContractName =
|
||||
publishedContract?.displayName || publishedContract?.name;
|
||||
|
||||
return publishedContractOGImageTemplate({
|
||||
title: publishedContractName,
|
||||
description: publishedContract.description,
|
||||
version: publishedContract.version || "latest",
|
||||
publisher: publisherProfile?.name || publisher,
|
||||
license: correctAndUniqueLicenses(publishedContract.licenses),
|
||||
publishDate: format(
|
||||
new Date(
|
||||
Number.parseInt(publishedContract.publishTimestamp.toString() || "0") *
|
||||
1000,
|
||||
),
|
||||
"MMM dd, yyyy",
|
||||
),
|
||||
logo: publishedContract.logo,
|
||||
publisherAvatar: publisherProfile?.avatar,
|
||||
});
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { ChakraProviderSetup } from "../../../../../@/components/ChakraProviderSetup";
|
||||
import { PublishedContract } from "../../../../../components/contract-components/published-contract";
|
||||
import { setOverrides } from "../../../../../lib/vercel-utils";
|
||||
import { PublishedActions } from "../../components/contract-actions-published.client";
|
||||
import { DeployContractHeader } from "../../components/contract-header";
|
||||
import { getPublishedContractsWithPublisherMapping } from "./utils/getPublishedContractsWithPublisherMapping";
|
||||
|
||||
setOverrides();
|
||||
|
||||
type PublishedContractDeployPageProps = {
|
||||
params: {
|
||||
publisher: string;
|
||||
contract_id: string;
|
||||
};
|
||||
};
|
||||
|
||||
export default async function PublishedContractPage(
|
||||
props: PublishedContractDeployPageProps,
|
||||
) {
|
||||
const publishedContractVersions =
|
||||
await getPublishedContractsWithPublisherMapping({
|
||||
publisher: props.params.publisher,
|
||||
contract_id: props.params.contract_id,
|
||||
});
|
||||
|
||||
const publishedContract = publishedContractVersions[0];
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeployContractHeader
|
||||
{...props.params}
|
||||
allVersions={publishedContractVersions}
|
||||
activeVersion={publishedContract}
|
||||
>
|
||||
<PublishedActions
|
||||
{...props.params}
|
||||
dispayName={publishedContract.displayName || publishedContract.name}
|
||||
/>
|
||||
</DeployContractHeader>
|
||||
<Separator />
|
||||
{/* TODO: remove the chakra things :) */}
|
||||
<ChakraProviderSetup>
|
||||
<div className="grid grid-cols-12 gap-6 md:gap-10 w-full">
|
||||
<PublishedContract
|
||||
publishedContract={publishedContract}
|
||||
walletOrEns={props.params.publisher}
|
||||
/>
|
||||
</div>
|
||||
</ChakraProviderSetup>
|
||||
</>
|
||||
);
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import { thirdwebClient } from "@/constants/client";
|
||||
import { isAddress } from "thirdweb";
|
||||
import { resolveAddress } from "thirdweb/extensions/ens";
|
||||
import { fetchPublishedContractVersions } from "../../../../../../components/contract-components/fetch-contracts-with-versions";
|
||||
|
||||
function mapThirdwebPublisher(publisher: string) {
|
||||
if (publisher === "thirdweb.eth") {
|
||||
return "deployer.thirdweb.eth";
|
||||
}
|
||||
return publisher;
|
||||
}
|
||||
|
||||
export async function getPublishedContractsWithPublisherMapping(options: {
|
||||
publisher: string;
|
||||
contract_id: string;
|
||||
}) {
|
||||
const { publisher, contract_id } = options;
|
||||
|
||||
// resolve ENS
|
||||
const publisherAddress = isAddress(publisher)
|
||||
? publisher
|
||||
: await resolveAddress({
|
||||
client: thirdwebClient,
|
||||
name: mapThirdwebPublisher(publisher),
|
||||
});
|
||||
|
||||
// get all the published versions of the contract
|
||||
const publishedContractVersions = await fetchPublishedContractVersions(
|
||||
publisherAddress,
|
||||
contract_id,
|
||||
);
|
||||
|
||||
return publishedContractVersions;
|
||||
}
|
||||
+85
-86
@@ -1,37 +1,7 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { isProd } from "@/constants/env";
|
||||
import { ImageResponse } from "@vercel/og";
|
||||
import type { NextRequest } from "next/server";
|
||||
import { PublishedContractOG } from "og-lib/url-utils";
|
||||
|
||||
// Make sure the font exists in the specified path:
|
||||
export const config = {
|
||||
runtime: "edge",
|
||||
};
|
||||
|
||||
const image = fetch(
|
||||
new URL("og-lib/assets/publish/background.png", import.meta.url),
|
||||
).then((res) => res.arrayBuffer());
|
||||
|
||||
const inter400_ = fetch(
|
||||
new URL("og-lib/fonts/inter/400.ttf", import.meta.url),
|
||||
).then((res) => res.arrayBuffer());
|
||||
const inter500_ = fetch(
|
||||
new URL("og-lib/fonts/inter/500.ttf", import.meta.url),
|
||||
).then((res) => res.arrayBuffer());
|
||||
const inter700_ = fetch(
|
||||
new URL("og-lib/fonts/inter/700.ttf", import.meta.url),
|
||||
).then((res) => res.arrayBuffer());
|
||||
|
||||
const ibmPlexMono400_ = fetch(
|
||||
new URL("og-lib/fonts/ibm-plex-mono/400.ttf", import.meta.url),
|
||||
).then((res) => res.arrayBuffer());
|
||||
const ibmPlexMono500_ = fetch(
|
||||
new URL("og-lib/fonts/ibm-plex-mono/500.ttf", import.meta.url),
|
||||
).then((res) => res.arrayBuffer());
|
||||
const ibmPlexMono700_ = fetch(
|
||||
new URL("og-lib/fonts/ibm-plex-mono/700.ttf", import.meta.url),
|
||||
).then((res) => res.arrayBuffer());
|
||||
import { thirdwebClient } from "@/constants/client";
|
||||
import { ImageResponse } from "next/og";
|
||||
import { download } from "thirdweb/storage";
|
||||
|
||||
const OgBrandIcon: React.FC = () => (
|
||||
// biome-ignore lint/a11y/noSvgWithoutTitle: not needed
|
||||
@@ -160,27 +130,49 @@ function descriptionShortener(description: string) {
|
||||
return `${words.join(" ")} ...`;
|
||||
}
|
||||
|
||||
const IPFS_GATEWAY = process.env.API_ROUTES_CLIENT_ID
|
||||
? `https://${process.env.API_ROUTES_CLIENT_ID}.${isProd ? "ipfscdn.io/ipfs/" : "thirdwebstorage-dev.com/ipfs/"}`
|
||||
: "https://ipfs.io/ipfs/";
|
||||
export async function publishedContractOGImageTemplate(params: {
|
||||
title: string;
|
||||
description?: string;
|
||||
version: string;
|
||||
publisher: string;
|
||||
license: string[];
|
||||
publishDate: string;
|
||||
logo?: string;
|
||||
publisherAvatar?: string;
|
||||
extension?: string[];
|
||||
}) {
|
||||
const image = fetch(
|
||||
new URL("og-lib/assets/publish/background.png", import.meta.url),
|
||||
).then((res) => res.arrayBuffer());
|
||||
|
||||
function replaceAnyIpfsUrlWithGateway(url: string) {
|
||||
if (url.startsWith("ipfs://")) {
|
||||
return `${IPFS_GATEWAY}${url.slice(7)}`;
|
||||
}
|
||||
if (url.includes("/ipfs/")) {
|
||||
const [, after] = url.split("/ipfs/");
|
||||
return `${IPFS_GATEWAY}${after}`;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
const inter400_ = fetch(
|
||||
new URL("../../../../../../og-lib/fonts/inter/400.ttf", import.meta.url),
|
||||
).then((res) => res.arrayBuffer());
|
||||
const inter500_ = fetch(
|
||||
new URL("../../../../../../og-lib/fonts/inter/500.ttf", import.meta.url),
|
||||
).then((res) => res.arrayBuffer());
|
||||
const inter700_ = fetch(
|
||||
new URL("../../../../../../og-lib/fonts/inter/700.ttf", import.meta.url),
|
||||
).then((res) => res.arrayBuffer());
|
||||
|
||||
export default async function handler(req: NextRequest) {
|
||||
if (req.method !== "GET") {
|
||||
return new Response("Method not allowed", { status: 405 });
|
||||
}
|
||||
|
||||
const publishedContractData = PublishedContractOG.fromUrl(new URL(req.url));
|
||||
const ibmPlexMono400_ = fetch(
|
||||
new URL(
|
||||
"../../../../../../og-lib/fonts/ibm-plex-mono/400.ttf",
|
||||
import.meta.url,
|
||||
),
|
||||
).then((res) => res.arrayBuffer());
|
||||
const ibmPlexMono500_ = fetch(
|
||||
new URL(
|
||||
"../../../../../../og-lib/fonts/ibm-plex-mono/500.ttf",
|
||||
import.meta.url,
|
||||
),
|
||||
).then((res) => res.arrayBuffer());
|
||||
const ibmPlexMono700_ = fetch(
|
||||
new URL(
|
||||
"../../../../../../og-lib/fonts/ibm-plex-mono/700.ttf",
|
||||
import.meta.url,
|
||||
),
|
||||
).then((res) => res.arrayBuffer());
|
||||
|
||||
const [
|
||||
inter400,
|
||||
@@ -190,6 +182,8 @@ export default async function handler(req: NextRequest) {
|
||||
ibmPlexMono500,
|
||||
ibmPlexMono700,
|
||||
imageData,
|
||||
logo,
|
||||
avatar,
|
||||
] = await Promise.all([
|
||||
inter400_,
|
||||
inter500_,
|
||||
@@ -198,6 +192,18 @@ export default async function handler(req: NextRequest) {
|
||||
ibmPlexMono500_,
|
||||
ibmPlexMono700_,
|
||||
image,
|
||||
params.logo
|
||||
? download({
|
||||
uri: params.logo,
|
||||
client: thirdwebClient,
|
||||
}).then((res) => res.arrayBuffer())
|
||||
: undefined,
|
||||
params.publisherAvatar
|
||||
? download({
|
||||
uri: params.publisherAvatar,
|
||||
client: thirdwebClient,
|
||||
}).then((res) => res.arrayBuffer())
|
||||
: undefined,
|
||||
]);
|
||||
|
||||
return new ImageResponse(
|
||||
@@ -223,37 +229,30 @@ export default async function handler(req: NextRequest) {
|
||||
<div tw="flex justify-between items-start w-full">
|
||||
<div tw="flex flex-col flex-shrink">
|
||||
<div tw="flex items-center">
|
||||
{publishedContractData.logo && (
|
||||
<img
|
||||
src={replaceAnyIpfsUrlWithGateway(publishedContractData.logo)}
|
||||
tw="w-16 h-16 rounded-xl mr-4"
|
||||
alt=""
|
||||
/>
|
||||
)}
|
||||
<h1 tw="text-6xl font-bold text-white">
|
||||
{publishedContractData.name}
|
||||
</h1>
|
||||
{/* @ts-expect-error - this works fine */}
|
||||
{logo && <img src={logo} tw="w-16 h-16 rounded-xl mr-4" alt="" />}
|
||||
<h1 tw="text-6xl font-bold text-white">{params.title}</h1>
|
||||
</div>
|
||||
{publishedContractData?.description && (
|
||||
{params?.description && (
|
||||
<p tw="text-3xl font-medium text-gray-400 max-w-4xl">
|
||||
{descriptionShortener(publishedContractData.description)}
|
||||
{descriptionShortener(params.description)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div tw="flex flex-col shrink-0 items-center">
|
||||
<img
|
||||
alt=""
|
||||
tw="w-32 h-32 rounded-full"
|
||||
src={
|
||||
publishedContractData.publisherAvatar
|
||||
? replaceAnyIpfsUrlWithGateway(
|
||||
publishedContractData.publisherAvatar,
|
||||
)
|
||||
: `https://source.boringavatars.com/marble/120/${publishedContractData.publisher}?colors=264653,2a9d8f,e9c46a,f4a261,e76f51&square=true`
|
||||
}
|
||||
/>
|
||||
{avatar && (
|
||||
<img
|
||||
alt=""
|
||||
width={100}
|
||||
height={100}
|
||||
tw="w-32 h-32 rounded-full"
|
||||
// @ts-expect-error - this works fine
|
||||
src={avatar}
|
||||
/>
|
||||
)}
|
||||
|
||||
<h2 tw="text-2xl text-white font-medium max-w-full">
|
||||
{publishedContractData.publisher}
|
||||
{params.publisher}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
@@ -264,15 +263,15 @@ export default async function handler(req: NextRequest) {
|
||||
fontFamily: "IBM Plex Mono",
|
||||
}}
|
||||
>
|
||||
{publishedContractData.extension?.length ? (
|
||||
{params.extension?.length ? (
|
||||
<li
|
||||
tw="flex flex-row items-center overflow-hidden w-full"
|
||||
style={{ whiteSpace: "nowrap" }}
|
||||
>
|
||||
<PackageIcon />
|
||||
<span tw="ml-2">
|
||||
{Array.isArray(publishedContractData.extension)
|
||||
? categorizeExtensions(publishedContractData.extension).map(
|
||||
{Array.isArray(params.extension)
|
||||
? categorizeExtensions(params.extension).map(
|
||||
([ext, count]) => (
|
||||
<span key={ext} tw="flex flex-row items-center mr-3">
|
||||
{ext}
|
||||
@@ -282,27 +281,27 @@ export default async function handler(req: NextRequest) {
|
||||
</span>
|
||||
),
|
||||
)
|
||||
: publishedContractData.extension}
|
||||
: params.extension}
|
||||
</span>
|
||||
</li>
|
||||
) : null}
|
||||
{publishedContractData.license?.length ? (
|
||||
{params.license?.length ? (
|
||||
<li tw="flex flex-row items-center">
|
||||
<FileTextIcon />
|
||||
<span tw="ml-2">
|
||||
{Array.isArray(publishedContractData.license)
|
||||
? publishedContractData.license.join(", ")
|
||||
: publishedContractData.license}
|
||||
{Array.isArray(params.license)
|
||||
? params.license.join(", ")
|
||||
: params.license}
|
||||
</span>
|
||||
</li>
|
||||
) : null}
|
||||
<li tw="flex flex-row items-center">
|
||||
<VersionIcon />
|
||||
<span tw="ml-2">{publishedContractData.version}</span>
|
||||
<span tw="ml-2">{params.version}</span>
|
||||
</li>
|
||||
<li tw="flex flex-row items-center">
|
||||
<CalendarIcon />
|
||||
<span tw="ml-2">{publishedContractData.publishDate}</span>
|
||||
<span tw="ml-2">{params.publishDate}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div tw="flex flex-shrink-0">
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { shareLink } from "@/lib/shareLink";
|
||||
import Link from "next/link";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
|
||||
export function DeployActions(props: {
|
||||
publisher: string;
|
||||
contract_id: string;
|
||||
version?: string;
|
||||
dispayName: string;
|
||||
}) {
|
||||
const searchparams = useSearchParams();
|
||||
|
||||
const stringifiedSearchParams = searchparams?.toString();
|
||||
|
||||
return (
|
||||
<div className="flex gap-3">
|
||||
<Button asChild variant="outline">
|
||||
<Link
|
||||
href={`/${props.publisher}/${props.contract_id}${props.version ? `/${props.version}` : ""}${stringifiedSearchParams ? `?${stringifiedSearchParams}` : ""}`}
|
||||
target="_blank"
|
||||
>
|
||||
Contract
|
||||
</Link>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
shareLink({
|
||||
title: `Deploy ${props.dispayName}`,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Share
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { shareLink } from "@/lib/shareLink";
|
||||
import { ChevronsRightIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
|
||||
export function PublishedActions(props: {
|
||||
publisher: string;
|
||||
contract_id: string;
|
||||
version?: string;
|
||||
dispayName: string;
|
||||
}) {
|
||||
const searchparams = useSearchParams();
|
||||
const stringifiedSearchParams = searchparams?.toString();
|
||||
|
||||
return (
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
shareLink({
|
||||
title: `Deploy ${props.dispayName}`,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Share
|
||||
</Button>
|
||||
<Button asChild variant="primary" className="gap-2">
|
||||
<Link
|
||||
href={`/${props.publisher}/${props.contract_id}${props.version ? `/${props.version}` : ""}/deploy${stringifiedSearchParams ? `?${stringifiedSearchParams}` : ""}`}
|
||||
>
|
||||
Deploy Now
|
||||
<ChevronsRightIcon className="size-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
"use client";
|
||||
import type { PropsWithChildren } from "react";
|
||||
import type { PublishedContractWithVersion } from "../../../../components/contract-components/fetch-contracts-with-versions";
|
||||
import { ModuleList } from "../[publisher]/[contract_id]/components/module-list.client";
|
||||
import { DeployContractInfo } from "./contract-info";
|
||||
import { DeployContractVersionSelector } from "./version-selector";
|
||||
|
||||
type DeployContractHeaderProps = {
|
||||
publisher: string;
|
||||
contract_id: string;
|
||||
version?: string;
|
||||
allVersions: PublishedContractWithVersion[];
|
||||
activeVersion: PublishedContractWithVersion;
|
||||
};
|
||||
|
||||
export function DeployContractHeader(
|
||||
props: PropsWithChildren<DeployContractHeaderProps>,
|
||||
) {
|
||||
return (
|
||||
<div className="py-2 flex flex-col gap-4">
|
||||
<div className="flex flex-col md:flex-row justify-between gap-6 md:items-center">
|
||||
<DeployContractInfo
|
||||
name={props.activeVersion.name}
|
||||
displayName={props.activeVersion.displayName}
|
||||
description={props.activeVersion.description}
|
||||
logo={props.activeVersion.logo}
|
||||
/>
|
||||
<div className="flex flex-col-reverse md:flex-row gap-3">
|
||||
{
|
||||
<DeployContractVersionSelector
|
||||
availableVersions={
|
||||
props.allVersions
|
||||
?.map((v) => v.version)
|
||||
.filter((v) => v !== undefined) || []
|
||||
}
|
||||
version={props.activeVersion.version || "latest"}
|
||||
/>
|
||||
}
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
<ModuleList />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { thirdwebClient } from "@/constants/client";
|
||||
import { resolveScheme } from "thirdweb/storage";
|
||||
|
||||
export function DeployContractInfo(props: {
|
||||
name: string;
|
||||
displayName?: string;
|
||||
description?: string;
|
||||
logo?: string;
|
||||
}) {
|
||||
const contractNameDisplay = props.displayName || props.name;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex gap-4 items-center flex-1">
|
||||
{props.logo && (
|
||||
<div className="rounded-xl p-2 border border-border shrink-0 items-center justify-center hidden md:flex">
|
||||
{/*eslint-disable-next-line @next/next/no-img-element*/}
|
||||
<img
|
||||
className="size-12"
|
||||
alt={props.name}
|
||||
src={resolveScheme({
|
||||
client: thirdwebClient,
|
||||
uri: props.logo,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<h1 className="text-2xl md:text-3xl font-bold tracking-tight">
|
||||
{contractNameDisplay}
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-sm">{props.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { thirdwebClient } from "@/constants/client";
|
||||
import { isAddress } from "thirdweb";
|
||||
import { fetchDeployMetadata } from "thirdweb/contract";
|
||||
import { resolveAddress } from "thirdweb/extensions/ens";
|
||||
import {
|
||||
fetchPublishedContractVersion,
|
||||
fetchPublishedContractVersions,
|
||||
} from "../../../../components/contract-components/fetch-contracts-with-versions";
|
||||
import { DeployActions } from "./contract-actions-deploy.client";
|
||||
import { DeployContractHeader } from "./contract-header";
|
||||
import { DeployFormForUri } from "./uri-based-deploy";
|
||||
|
||||
type PublishBasedDeployProps = {
|
||||
publisher: string;
|
||||
contract_id: string;
|
||||
version?: string;
|
||||
modules?: Array<{ publisher: string; moduleId: string; version?: string }>;
|
||||
};
|
||||
|
||||
function mapThirdwebPublisher(publisher: string) {
|
||||
if (publisher === "thirdweb.eth") {
|
||||
return "deployer.thirdweb.eth";
|
||||
}
|
||||
return publisher;
|
||||
}
|
||||
|
||||
export async function DeployFormForPublishInfo(props: PublishBasedDeployProps) {
|
||||
// resolve ENS if required
|
||||
const publisherAddress = isAddress(props.publisher)
|
||||
? props.publisher
|
||||
: await resolveAddress({
|
||||
client: thirdwebClient,
|
||||
name: mapThirdwebPublisher(props.publisher),
|
||||
});
|
||||
|
||||
// get all the published versions of the contract
|
||||
const [publishedContractVersions, ...modules] = await Promise.all([
|
||||
fetchPublishedContractVersions(publisherAddress, props.contract_id),
|
||||
...(props.modules || []).map((m) =>
|
||||
fetchPublishedContractVersion(m.publisher, m.moduleId, m.version),
|
||||
),
|
||||
]);
|
||||
|
||||
// determine the "active" version of the contract based on the version that is passed
|
||||
const publishedContract =
|
||||
publishedContractVersions.find((v) => v.version === props.version) ||
|
||||
publishedContractVersions[0];
|
||||
|
||||
const moduleUris = modules
|
||||
.filter((m) => m !== null)
|
||||
.map((m) => m.publishMetadataUri);
|
||||
const [contractMetadata, ...fetchedModules] = await Promise.all([
|
||||
fetchDeployMetadata({
|
||||
client: thirdwebClient,
|
||||
// force `ipfs://` prefix
|
||||
uri: publishedContract.publishMetadataUri.startsWith("ipfs://")
|
||||
? publishedContract.publishMetadataUri
|
||||
: `ipfs://${publishedContract.publishMetadataUri}`,
|
||||
}).catch(() => null),
|
||||
...(moduleUris || []).map((uri) =>
|
||||
fetchDeployMetadata({
|
||||
client: thirdwebClient,
|
||||
// force `ipfs://` prefix
|
||||
uri: uri.startsWith("ipfs://") ? uri : `ipfs://${uri}`,
|
||||
}).catch(() => null),
|
||||
),
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeployContractHeader
|
||||
{...props}
|
||||
allVersions={publishedContractVersions}
|
||||
activeVersion={publishedContract}
|
||||
>
|
||||
<DeployActions
|
||||
{...props}
|
||||
dispayName={publishedContract.displayName || publishedContract.name}
|
||||
/>
|
||||
</DeployContractHeader>
|
||||
<Separator />
|
||||
<DeployFormForUri
|
||||
contractMetadata={contractMetadata}
|
||||
modules={fetchedModules.filter((m) => m !== null)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ChakraProviderSetup } from "@/components/ChakraProviderSetup";
|
||||
import { CustomContractForm } from "components/contract-components/contract-deploy-form/custom-contract";
|
||||
import type { FetchDeployMetadataResult } from "thirdweb/contract";
|
||||
|
||||
type DeployFormForUriProps = {
|
||||
contractMetadata: FetchDeployMetadataResult | null;
|
||||
modules: FetchDeployMetadataResult[] | null;
|
||||
};
|
||||
|
||||
export async function DeployFormForUri(props: DeployFormForUriProps) {
|
||||
const { contractMetadata, modules } = props;
|
||||
|
||||
if (!contractMetadata) {
|
||||
return <div>Could not fetch metadata</div>;
|
||||
}
|
||||
|
||||
// TODO: remove the `ChakraProviderSetup` wrapper once the form is updated to no longer use chrakra
|
||||
return (
|
||||
<ChakraProviderSetup>
|
||||
<CustomContractForm
|
||||
metadata={contractMetadata}
|
||||
modules={modules?.filter((m) => m !== null)}
|
||||
/>
|
||||
</ChakraProviderSetup>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { useDashboardRouter } from "@/lib/DashboardRouter";
|
||||
import { usePathname, useSearchParams } from "next/navigation";
|
||||
import { useMemo } from "react";
|
||||
|
||||
type DeployContractVersionSelectorProps = {
|
||||
availableVersions: string[];
|
||||
version: string;
|
||||
};
|
||||
|
||||
export function DeployContractVersionSelector({
|
||||
availableVersions,
|
||||
version,
|
||||
}: DeployContractVersionSelectorProps) {
|
||||
const router = useDashboardRouter();
|
||||
const pathname = usePathname() || "";
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const { isDeploy, rootPathName } = useMemo(() => {
|
||||
return {
|
||||
isDeploy: pathname.endsWith("/deploy"),
|
||||
rootPathName: pathname
|
||||
.replace(/\/deploy$/, "")
|
||||
.replace(`/${version}`, ""),
|
||||
};
|
||||
}, [pathname, version]);
|
||||
|
||||
return (
|
||||
<Select
|
||||
value={version}
|
||||
onValueChange={(val) => {
|
||||
if (availableVersions.includes(val)) {
|
||||
const stringifiedSearchParams = searchParams?.toString();
|
||||
const isLatestVersion = val === availableVersions[0];
|
||||
let pathName = isLatestVersion
|
||||
? rootPathName
|
||||
: `${rootPathName}/${val}`;
|
||||
|
||||
if (isDeploy) {
|
||||
// append the /deploy to the path
|
||||
pathName = `${pathName}/deploy`;
|
||||
}
|
||||
|
||||
// if any search params, add them to the path
|
||||
if (stringifiedSearchParams) {
|
||||
pathName = `${pathName}?${stringifiedSearchParams}`;
|
||||
}
|
||||
|
||||
router.push(pathName);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="min-w-[180px] bg-transparent hover:bg-accent">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availableVersions.map((v, idx) => {
|
||||
return (
|
||||
<SelectItem value={v || "latest"} key={v}>
|
||||
{v}
|
||||
{idx === 0 && (
|
||||
<span className="text-muted-foreground"> (latest) </span>
|
||||
)}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { thirdwebClient } from "@/constants/client";
|
||||
import { redirect } from "next/navigation";
|
||||
import { fetchDeployMetadata } from "thirdweb/contract";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: {
|
||||
uri?: string;
|
||||
};
|
||||
}) {
|
||||
if (!props.searchParams?.uri) {
|
||||
// redirect back out if we do not have a uri
|
||||
return redirect("/dashboard");
|
||||
}
|
||||
|
||||
const contractMetadata = await fetchDeployMetadata({
|
||||
client: thirdwebClient,
|
||||
// force `ipfs://` prefix
|
||||
uri: props.searchParams.uri.startsWith("ipfs://")
|
||||
? props.searchParams.uri
|
||||
: `ipfs://${props.searchParams.uri}`,
|
||||
}).catch(() => null);
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<pre>{JSON.stringify(contractMetadata, null, 2)}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
type Module = {
|
||||
publisher: string;
|
||||
moduleId: string;
|
||||
version?: string;
|
||||
};
|
||||
|
||||
function assertModule(val: unknown): asserts val is Module {
|
||||
if (
|
||||
typeof val !== "object" ||
|
||||
val === null ||
|
||||
typeof (val as Module).publisher !== "string" ||
|
||||
typeof (val as Module).moduleId !== "string" ||
|
||||
((val as Module).version !== undefined &&
|
||||
typeof (val as Module).version !== "string")
|
||||
) {
|
||||
throw new Error("Invalid module");
|
||||
}
|
||||
}
|
||||
|
||||
export function moduleFromBase64(base64: string) {
|
||||
try {
|
||||
const mod = JSON.parse(atob(base64));
|
||||
assertModule(mod);
|
||||
return {
|
||||
...mod,
|
||||
version: mod.version || "latest",
|
||||
displayName: mod.moduleId.split("ERC")[0],
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function moduleToBase64(mod: Module) {
|
||||
return btoa(JSON.stringify(mod));
|
||||
}
|
||||
@@ -24,14 +24,6 @@ function isValidPlan(plan: string): plan is keyof typeof planToCustomerId {
|
||||
return plan in planToCustomerId;
|
||||
}
|
||||
|
||||
// const SUPPORT_EMAIL = "[email protected]";
|
||||
|
||||
export type CreateTicketInput = {
|
||||
markdown: string;
|
||||
product: string;
|
||||
files?: File[];
|
||||
} & Record<string, string>;
|
||||
|
||||
function prepareEmailTitle(
|
||||
product: string,
|
||||
problemArea: string,
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
export const PRODUCTS = [
|
||||
"connect",
|
||||
"engine",
|
||||
"contracts",
|
||||
"account",
|
||||
"other",
|
||||
] as const;
|
||||
export type Product = (typeof PRODUCTS)[number];
|
||||
|
||||
export function isProduct(value: string): value is Product {
|
||||
return PRODUCTS.includes(value as Product);
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { ChevronDown } from "lucide-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import type { SortBy, TimeRange } from "../../../../lib/search";
|
||||
|
||||
export type SortingHeaderProps = {
|
||||
type SortingHeaderProps = {
|
||||
sortBy: string;
|
||||
title: string;
|
||||
searchParams:
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
"use client";
|
||||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
||||
import { useDashboardRouter } from "@/lib/DashboardRouter";
|
||||
import type { TimeRange } from "lib/search";
|
||||
import { usePathname, useSearchParams } from "next/navigation";
|
||||
|
||||
export function TimeRangeSwitcher() {
|
||||
const router = useDashboardRouter();
|
||||
const path = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const currentSort = searchParams?.get("sortBy") || "transactionCount";
|
||||
return (
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={searchParams?.get("timeRange") || "day"}
|
||||
onValueChange={async (value: TimeRange) => {
|
||||
router.replace(
|
||||
`${path}?timeRange=${value}${currentSort ? `&sortBy=${currentSort}` : ""}`,
|
||||
{
|
||||
scroll: false,
|
||||
},
|
||||
);
|
||||
}}
|
||||
>
|
||||
<ToggleGroupItem value="day">Last 24h</ToggleGroupItem>
|
||||
<ToggleGroupItem value="week">Last 7 Days</ToggleGroupItem>
|
||||
<ToggleGroupItem value="month">Last 30 Days</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
);
|
||||
}
|
||||
@@ -5,7 +5,8 @@ import type { Team } from "@/api/team";
|
||||
import { useDashboardRouter } from "@/lib/DashboardRouter";
|
||||
import { CustomConnectWallet } from "@3rdweb-sdk/react/components/connect-wallet";
|
||||
import { useAccount } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { useCallback } from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
import { LazyCreateAPIKeyDialog } from "../../../components/settings/ApiKeys/Create/LazyCreateAPIKeyDialog";
|
||||
import {
|
||||
type AccountHeaderCompProps,
|
||||
AccountHeaderDesktopUI,
|
||||
@@ -17,6 +18,8 @@ export function AccountHeader(props: {
|
||||
}) {
|
||||
const myAccountQuery = useAccount();
|
||||
const router = useDashboardRouter();
|
||||
const [isCreateProjectDialogOpen, setIsCreateProjectDialogOpen] =
|
||||
useState(false);
|
||||
|
||||
const logout = useCallback(async () => {
|
||||
// log out the user
|
||||
@@ -35,12 +38,23 @@ export function AccountHeader(props: {
|
||||
email: myAccountQuery.data?.email,
|
||||
logout: logout,
|
||||
connectButton: <CustomConnectWallet />,
|
||||
createProject: () => setIsCreateProjectDialogOpen(true),
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AccountHeaderDesktopUI {...headerProps} className="max-lg:hidden" />
|
||||
<AccountHeaderMobileUI {...headerProps} className="lg:hidden" />
|
||||
|
||||
<LazyCreateAPIKeyDialog
|
||||
open={isCreateProjectDialogOpen}
|
||||
onOpenChange={setIsCreateProjectDialogOpen}
|
||||
wording="project"
|
||||
onCreateAndComplete={() => {
|
||||
// refresh projects
|
||||
router.refresh();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ function Variants(props: {
|
||||
email="[email protected]"
|
||||
logout={() => {}}
|
||||
connectButton={<ConnectButtonStub />}
|
||||
createProject={() => {}}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
</div>
|
||||
|
||||
@@ -14,6 +14,7 @@ export type AccountHeaderCompProps = {
|
||||
logout: () => void;
|
||||
connectButton: React.ReactNode;
|
||||
teamsAndProjects: Array<{ team: Team; projects: Project[] }>;
|
||||
createProject: () => void;
|
||||
};
|
||||
|
||||
export function AccountHeaderDesktopUI(props: AccountHeaderCompProps) {
|
||||
@@ -46,6 +47,7 @@ export function AccountHeaderDesktopUI(props: AccountHeaderCompProps) {
|
||||
currentTeam={undefined}
|
||||
teamsAndProjects={props.teamsAndProjects}
|
||||
focus="team-selection"
|
||||
createProject={props.createProject}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -85,7 +85,7 @@ function AccountAvatarFormControl() {
|
||||
saveButton={{
|
||||
onClick: handleSave,
|
||||
disabled: false,
|
||||
isLoading: updateAvatarMutation.isLoading,
|
||||
isLoading: updateAvatarMutation.isPending,
|
||||
}}
|
||||
noPermissionText={undefined}
|
||||
errorText={undefined}
|
||||
@@ -143,7 +143,7 @@ function AccountNameFormControl(props: {
|
||||
saveButton={{
|
||||
onClick: handleSave,
|
||||
disabled: accountName.length === 0,
|
||||
isLoading: updateAccountNameMutation.isLoading,
|
||||
isLoading: updateAccountNameMutation.isPending,
|
||||
}}
|
||||
errorText={undefined}
|
||||
noPermissionText={undefined} // TODO
|
||||
@@ -191,7 +191,7 @@ function DeleteAccountCard() {
|
||||
description={description}
|
||||
buttonLabel={title}
|
||||
buttonOnClick={handleDelete}
|
||||
isLoading={deleteAccount.isLoading}
|
||||
isLoading={deleteAccount.isPending}
|
||||
confirmationDialog={{
|
||||
title: "Are you sure you want to delete your account?",
|
||||
description:
|
||||
@@ -321,10 +321,10 @@ function EmailUpdateDialog(props: {
|
||||
<Button variant="outline"> Cancel </Button>
|
||||
</DialogClose>
|
||||
<Button className="min-w-24 gap-2" type="submit">
|
||||
{updateEmailMutation.isLoading && (
|
||||
{updateEmailMutation.isPending && (
|
||||
<Spinner className="size-4" />
|
||||
)}
|
||||
{updateEmailMutation.isLoading ? "Saving" : "Save"}
|
||||
{updateEmailMutation.isPending ? "Saving" : "Save"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import invariant from "tiny-invariant";
|
||||
import { cacheGet, cacheSet } from "../../../lib/redis";
|
||||
import type { ContactFormPayload } from "./types";
|
||||
|
||||
// Note: This handler cannot use "edge" runtime because of Redis usage.
|
||||
|
||||
export const POST = async (req: NextRequest) => {
|
||||
const rateLimitedResponse = await rateLimiter(req);
|
||||
if (rateLimitedResponse) {
|
||||
return rateLimitedResponse;
|
||||
}
|
||||
|
||||
const requestBody = (await req.json()) as ContactFormPayload;
|
||||
|
||||
const { fields } = requestBody;
|
||||
|
||||
invariant(process.env.HUBSPOT_ACCESS_TOKEN, "missing HUBSPOT_ACCESS_TOKEN");
|
||||
|
||||
const response = await fetch(
|
||||
"https://api.hsforms.com/submissions/v3/integration/secure/submit/23987964/38849262-3605-4eb2-883b-b4f1aa5ad845",
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${process.env.HUBSPOT_ACCESS_TOKEN}`,
|
||||
},
|
||||
method: "POST",
|
||||
body: JSON.stringify({ fields }),
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const body = await response.json();
|
||||
console.error("error", body);
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{ status: response.statusText },
|
||||
{
|
||||
status: response.status,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
async function rateLimiter(req: NextRequest) {
|
||||
// Max 1 requests per minute
|
||||
const rateLimitSeconds = 60;
|
||||
|
||||
const ipAddress =
|
||||
req.headers.get("CF-Connecting-IP") ||
|
||||
req.ip ||
|
||||
req.headers.get("X-Forwarded-For");
|
||||
|
||||
if (!ipAddress) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "Could not validate elligibility.",
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const cacheKey = `contact-us:${ipAddress}`;
|
||||
const cacheValue = await cacheGet(cacheKey);
|
||||
|
||||
// if we have a cached value, return an error
|
||||
if (cacheValue !== null) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: `Rate limit exceeded. Try again in ${rateLimitSeconds} seconds.`,
|
||||
},
|
||||
{ status: 429 },
|
||||
);
|
||||
}
|
||||
|
||||
// cache it for `rateLimitSeconds`
|
||||
await cacheSet(cacheKey, "contact-us-used", rateLimitSeconds);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export type ContactFormPayload = {
|
||||
fields: {
|
||||
name: string;
|
||||
value: string;
|
||||
}[];
|
||||
};
|
||||
@@ -35,7 +35,6 @@ export function DashboardHeader() {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right */}
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="items-center gap-2 lg:flex">
|
||||
|
||||
@@ -4,7 +4,7 @@ import { cn } from "@/lib/utils";
|
||||
import Link, { type LinkProps } from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
export type NavLinkProps = Omit<
|
||||
type NavLinkProps = Omit<
|
||||
React.AnchorHTMLAttributes<HTMLAnchorElement>,
|
||||
keyof LinkProps
|
||||
> &
|
||||
|
||||
@@ -7,9 +7,13 @@ import PlausibleProvider from "next-plausible";
|
||||
import dynamic from "next/dynamic";
|
||||
import { Inter } from "next/font/google";
|
||||
import NextTopLoader from "nextjs-toploader";
|
||||
import { setOverrides } from "../lib/vercel-utils";
|
||||
import { PostHogProvider } from "./components/root-providers";
|
||||
import { AppRouterProviders } from "./providers";
|
||||
|
||||
// run this on app load
|
||||
setOverrides();
|
||||
|
||||
const fontSans = Inter({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-sans",
|
||||
@@ -65,7 +69,7 @@ export default function RootLayout({
|
||||
<PostHogProvider>
|
||||
<body
|
||||
className={cn(
|
||||
"h-screen bg-background font-sans antialiased",
|
||||
"bg-background font-sans antialiased",
|
||||
fontSans.variable,
|
||||
)}
|
||||
>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user