Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cff849b69 | ||
|
|
b5e327e5ec |
@@ -1,11 +1,5 @@
|
||||
{
|
||||
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
|
||||
"onlyUpdatePeerDependentsWhenOutOfRange": true,
|
||||
"updateInternalDependents": "always"
|
||||
},
|
||||
"$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"changelog": [
|
||||
"@changesets/changelog-github",
|
||||
{
|
||||
@@ -13,15 +7,15 @@
|
||||
}
|
||||
],
|
||||
"commit": false,
|
||||
"ignore": [
|
||||
"playground-web",
|
||||
"thirdweb-dashboard",
|
||||
"wallet-ui",
|
||||
"portal",
|
||||
"nebula-app"
|
||||
],
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": ["playground-web", "thirdweb-dashboard", "wallet-ui", "portal"],
|
||||
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
|
||||
"updateInternalDependents": "always",
|
||||
"onlyUpdatePeerDependentsWhenOutOfRange": true
|
||||
},
|
||||
"snapshot": {
|
||||
"prereleaseTemplate": "{tag}-{commit}-{datetime}"
|
||||
},
|
||||
"updateInternalDependencies": "patch"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
---
|
||||
description: Rules for writing features in apps/dashboard
|
||||
globs: dashboard
|
||||
alwaysApply: false
|
||||
---
|
||||
# Reusable Core UI Components
|
||||
|
||||
- Always import from the central UI library under `@/components/ui/*` – e.g. `import { Button } from "@/components/ui/button"`.
|
||||
- Prefer composable primitives over custom markup: `Button`, `Input`, `Select`, `Tabs`, `Card`, `Sidebar`, `Separator`, `Badge`.
|
||||
- Use `NavLink` (`@/components/ui/NavLink`) for internal navigation so active states are handled automatically.
|
||||
- Layouts should reuse `SidebarLayout` / `FullWidthSidebarLayout` (`@/components/blocks/SidebarLayout`).
|
||||
- For notices & skeletons rely on `AnnouncementBanner`, `GenericLoadingPage`, `EmptyStateCard`.
|
||||
- Icons come from `lucide-react` or the project-specific `…/icons` exports – never embed raw SVG.
|
||||
- Group related components in their own folder and expose a single barrel `index.ts` where necessary.
|
||||
- Keep components pure; fetch data outside (server component or hook) and pass it down via props.
|
||||
|
||||
# Styling
|
||||
|
||||
- Tailwind CSS is **the** styling system – avoid inline styles or CSS modules.
|
||||
- Merge class names with `cn` from `@/lib/utils` to keep conditional logic readable.
|
||||
- Stick to design-tokens: background (`bg-card`), borders (`border-border`), muted text (`text-muted-foreground`) etc.
|
||||
- Use the `container` class with a `max-w-7xl` cap for page width consistency.
|
||||
- Spacing utilities (`px-*`, `py-*`, `gap-*`) are preferred over custom margins.
|
||||
- Responsive helpers follow mobile-first (`max-sm`, `md`, `lg`, `xl`).
|
||||
- Never hard-code colors – always go through Tailwind variables.
|
||||
- Add `className` to the root element of every component for external overrides.
|
||||
|
||||
# Creating a new Component
|
||||
|
||||
- Place the file close to its feature: `feature/components/MyComponent.tsx`.
|
||||
- Name files after the component in **PascalCase**; append `.client.tsx` when interactive.
|
||||
- Client components must start with `'use client';` before imports.
|
||||
- Accept a typed `props` object and export a **named** function (`export function MyComponent()`).
|
||||
- Reuse core UI primitives; avoid re-implementing buttons, cards, modals.
|
||||
- Combine class names via `cn`, expose `className` prop if useful.
|
||||
- Local state or effects live inside; data fetching happens in hooks.
|
||||
- Provide a Storybook story (`MyComponent.stories.tsx`) or unit test alongside the component.
|
||||
|
||||
# When to use Server Side Rendering (Server Components)
|
||||
|
||||
- Reading cookies/headers with `next/headers` (`getAuthToken()`, `cookies()`).
|
||||
- Accessing server-only environment variables or secrets.
|
||||
- Heavy data fetching that should not ship to the client (e.g. analytics, billing).
|
||||
- Redirect logic using `redirect()` from `next/navigation`.
|
||||
- Building layout shells (`layout.tsx`) and top-level pages that mainly assemble data.
|
||||
- Export default async functions without `'use client';` – they run on the Node edge.
|
||||
- Co-locate data helpers under `@/api/**` and mark them with `"server-only"`.
|
||||
|
||||
# When to use Client Side Rendering (Client Components)
|
||||
|
||||
- Interactive UI that relies on hooks (`useState`, `useEffect`, React Query, wallet hooks).
|
||||
- Components that listen to user events, animations or live updates.
|
||||
- When you need access to browser APIs (localStorage, window, IntersectionObserver etc.).
|
||||
- Pages requiring fast transitions where data is prefetched on the client.
|
||||
- Anything that consumes hooks from `@tanstack/react-query` or thirdweb SDKs.
|
||||
|
||||
# Fetching Authenticated Data – Server
|
||||
|
||||
```ts
|
||||
import "server-only";
|
||||
import { API_SERVER_URL } from "@/constants/env";
|
||||
import { getAuthToken } from "@/app/(app)/api/lib/getAuthToken";
|
||||
|
||||
export async function getProjects(teamSlug: string) {
|
||||
const token = await getAuthToken();
|
||||
if (!token) return [];
|
||||
const res = await fetch(`${API_SERVER_URL}/v1/teams/${teamSlug}/projects`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
return res.ok ? (await res.json()).result : [];
|
||||
}
|
||||
```
|
||||
|
||||
Guidelines:
|
||||
|
||||
- Always call `getAuthToken()` to get the JWT from cookies.
|
||||
- Prefix files with `import "server-only";` so they never end up in the client bundle.
|
||||
- Pass the token in the `Authorization: Bearer` header – never embed it in the URL.
|
||||
- Return typed results (`Project[]`, `User[]`, …) – avoid `any`.
|
||||
|
||||
# Fetching Authenticated Data – Client
|
||||
|
||||
```ts
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { fetchJson } from "@/lib/fetch-json";
|
||||
|
||||
export function useProjects(teamSlug: string) {
|
||||
return useQuery({
|
||||
queryKey: ["projects", teamSlug],
|
||||
queryFn: () => fetchJson(`/api/projects?team=${teamSlug}`), // internal API route handles token
|
||||
staleTime: 60_000,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Guidelines:
|
||||
|
||||
- Use **React Query** (`@tanstack/react-query`) for all client data fetching.
|
||||
- Create light wrappers (e.g. `fetchJson`) that automatically attach the JWT from cookies/session when calling internal API routes.
|
||||
- Keep `queryKey` stable and descriptive for cache hits.
|
||||
- Prefer API routes or server actions to keep tokens secret; the browser only sees relative paths.
|
||||
- Configure `staleTime` / `cacheTime` according to freshness requirements.
|
||||
|
||||
# Analytics Event Reporting
|
||||
|
||||
- **Add events intentionally** – only when they answer a concrete product/business question.
|
||||
- **Event name**: human-readable `<subject> <verb>` phrase (e.g. `"contract deployed"`).
|
||||
- **Reporting helper**: `report<Subject><Verb>` (PascalCase); all live in `src/@/analytics/report.ts`.
|
||||
- **Mandatory JSDoc**: explain *Why* the event exists and *Who* owns it (`@username`).
|
||||
- **Typed properties**: accept a single `properties` object and pass it unchanged to `posthog.capture`.
|
||||
- **Client-side only**: never import `posthog-js` in server components.
|
||||
- **Housekeeping**: ping **#core-services** before renaming or removing an event.
|
||||
|
||||
```ts
|
||||
/**
|
||||
* ### Why do we need to report this event?
|
||||
* - Tracks number of contracts deployed
|
||||
*
|
||||
* ### Who is responsible for this event?
|
||||
* @jnsdls
|
||||
*/
|
||||
export function reportContractDeployed(properties: {
|
||||
address: string;
|
||||
chainId: number;
|
||||
}) {
|
||||
posthog.capture("contract deployed", properties);
|
||||
}
|
||||
```
|
||||
@@ -6,15 +6,15 @@ runs:
|
||||
steps:
|
||||
# we use bun for some test suites
|
||||
- name: Setup bun
|
||||
uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: 1.0.35
|
||||
# pnpm for our dependencies
|
||||
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9.11.0
|
||||
version: 9
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
check-latest: true
|
||||
|
||||
@@ -6,6 +6,8 @@ To get started, read the [How this repo works](#how-this-repo-works) section bel
|
||||
|
||||
From there, you can take a look at our [Good First Issues](https://github.com/thirdweb-dev/js/labels/good%20first%20issue) board and find an issue that interests you!
|
||||
|
||||
If you have any questions about the issue, feel free to ask on our [Discord server](https://discord.gg/thirdweb) in the `#contributors` channel; where you'll be able to get help from our team and other contributors.
|
||||
|
||||
<br />
|
||||
|
||||
## How this repo works
|
||||
@@ -14,7 +16,7 @@ We use [Turborepo](https://turbo.build/repo/docs) to manage the repository, and
|
||||
|
||||
We use [pnpm](https://pnpm.io) for package management across the repo. `pnpm` is similar to `npm` or `yarn` but with more efficient disk space usage.
|
||||
|
||||
**With the v5 SDK, we've consolidated everything into a single project at [/packages/thirdweb](../packages/thirdweb). You can still find the legacy packages at [/legacy_packages](../legacy_packages).**
|
||||
**With the v5 SDK, we've consolidated everything into a single project at [/packages/thirdweb](./packages/thirdweb). You can still find the legacy packages at [/legacy_packages](./legacy_packages).**
|
||||
|
||||
This single package provides a performant & lightweight SDK to interact with any EVM chain across Node, React, and React Native. Learn more about how to use the thirdweb SDK in our [documentation](https://portal.thirdweb.com/typescript/v5).
|
||||
|
||||
@@ -83,10 +85,7 @@ If your test depends on a downstream network call, you must mock the call using
|
||||
import { setupServer } from "msw/node";
|
||||
import { downloadMock, uploadMock } from "../../../test/src/mocks/storage.js";
|
||||
|
||||
const server = setupServer(
|
||||
uploadMock("HASH"),
|
||||
downloadMock({ name: "Test NFT" })
|
||||
);
|
||||
const server = setupServer(uploadMock("HASH"), downloadMock({ name: "Test NFT" }));
|
||||
|
||||
beforeAll(() => server.listen());
|
||||
afterEach(() => server.resetHandlers());
|
||||
|
||||
@@ -16,18 +16,17 @@ env:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
TW_SECRET_KEY: ${{ secrets.TW_SECRET_KEY }}
|
||||
TW_CLIENT_ID: ${{ secrets.TW_CLIENT_ID }}
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
jobs:
|
||||
optimize_ci:
|
||||
runs-on: ubuntu-latest-8
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
skip: ${{ steps.check_skip.outputs.skip }}
|
||||
steps:
|
||||
- name: Optimize CI
|
||||
id: check_skip
|
||||
uses: withgraphite/graphite-ci-action@9cb601a55e114099561b6d755505de377d45db40 # v0.0.9 ("main")
|
||||
uses: withgraphite/graphite-ci-action@main
|
||||
with:
|
||||
graphite_token: ${{ secrets.GRAPHITE_OMTIMIZE_TOKEN }}
|
||||
|
||||
@@ -35,11 +34,11 @@ jobs:
|
||||
build:
|
||||
needs: optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
runs-on: ubuntu-latest-8
|
||||
runs-on: ubuntu-latest
|
||||
name: Build Packages
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup & Install
|
||||
uses: ./.github/composite-actions/install
|
||||
@@ -52,18 +51,18 @@ jobs:
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
timeout-minutes: 15
|
||||
name: Lint Packages
|
||||
runs-on: ubuntu-latest-8
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup & Install
|
||||
uses: ./.github/composite-actions/install
|
||||
|
||||
- name: Setup Biome
|
||||
uses: biomejs/setup-biome@a9763ed3d2388f5746f9dc3e1a55df7f4609bc89 # v2.5.1
|
||||
uses: biomejs/setup-biome@v2
|
||||
with:
|
||||
version: 2.0.6
|
||||
version: latest
|
||||
|
||||
- run: pnpm lint
|
||||
|
||||
@@ -72,16 +71,16 @@ jobs:
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
timeout-minutes: 15
|
||||
name: Unit Tests
|
||||
runs-on: ubuntu-latest-8
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup & Install
|
||||
uses: ./.github/composite-actions/install
|
||||
|
||||
- name: Set up foundry
|
||||
uses: foundry-rs/foundry-toolchain@82dee4ba654bd2146511f85f0d013af94670c4de # v1.4.0
|
||||
uses: foundry-rs/foundry-toolchain@v1
|
||||
with:
|
||||
cache: false
|
||||
version: nightly-c4a984fbf2c48b793c8cd53af84f56009dd1070c
|
||||
@@ -89,7 +88,7 @@ jobs:
|
||||
- run: pnpm test
|
||||
|
||||
- name: Code Coverage
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
directory: packages/
|
||||
flags: packages
|
||||
@@ -100,14 +99,14 @@ jobs:
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
timeout-minutes: 15
|
||||
name: E2E Tests
|
||||
runs-on: ubuntu-latest-8
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
package_manager: [pnpm] # TODO, reenable [npm, yarn, pnpm, bun]
|
||||
package_manager: [npm, yarn, pnpm, bun]
|
||||
bundler: [vite, webpack, esbuild]
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup & Install
|
||||
uses: ./.github/composite-actions/install
|
||||
@@ -124,14 +123,7 @@ jobs:
|
||||
mkdir test-project
|
||||
cd test-project
|
||||
npm init -y
|
||||
|
||||
# Handle different package managers
|
||||
if [ "${{ matrix.package_manager }}" = "pnpm" ]; then
|
||||
# Create pnpm workspace
|
||||
echo '{"name": "test-project", "private": true, "workspaces": ["."]}' > package.json
|
||||
echo '{"packages": ["../packages/*"]}' > pnpm-workspace.yaml
|
||||
pnpm add react react-dom ../packages/thirdweb -w
|
||||
fi
|
||||
${{ matrix.package_manager }} add react react-dom ../packages/thirdweb
|
||||
- name: Create test file
|
||||
run: |
|
||||
cd test-project
|
||||
@@ -141,7 +133,7 @@ jobs:
|
||||
if: matrix.bundler == 'vite'
|
||||
run: |
|
||||
cd test-project
|
||||
${{matrix.package_manager}} add vite -w
|
||||
${{matrix.package_manager}} add vite
|
||||
echo 'import { defineConfig } from "vite"; import {resolve} from "path"; export default defineConfig({ build: { lib: { entry: resolve(__dirname, "index.js"), name: "e2e_test" }, outDir: "dist" }});' > vite.config.js
|
||||
npx vite build
|
||||
|
||||
@@ -149,7 +141,7 @@ jobs:
|
||||
if: matrix.bundler == 'webpack'
|
||||
run: |
|
||||
cd test-project
|
||||
${{matrix.package_manager}} add webpack webpack-cli -w
|
||||
${{matrix.package_manager}} add webpack webpack-cli
|
||||
echo 'const path = require("path"); module.exports = { mode: "production", entry: "./index.js", output: { path: path.resolve(__dirname, "dist"), filename: "bundle.js" }};' > webpack.config.js
|
||||
npx webpack
|
||||
|
||||
@@ -157,7 +149,7 @@ jobs:
|
||||
if: matrix.bundler == 'esbuild'
|
||||
run: |
|
||||
cd test-project
|
||||
${{matrix.package_manager}} add esbuild -w
|
||||
${{matrix.package_manager}} add esbuild
|
||||
npx esbuild index.js --bundle --outdir=dist
|
||||
|
||||
- name: Verify bundle
|
||||
@@ -177,27 +169,17 @@ jobs:
|
||||
if: github.event_name == 'pull_request' && needs.optimize_ci.outputs.skip == 'false'
|
||||
timeout-minutes: 15
|
||||
name: "Size"
|
||||
runs-on: ubuntu-latest-8
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup & Install
|
||||
uses: ./.github/composite-actions/install
|
||||
|
||||
- name: Build Packages
|
||||
run: pnpm build
|
||||
|
||||
- name: Report bundle size (thirdweb)
|
||||
uses: andresz1/size-limit-action@94bc357df29c36c8f8d50ea497c3e225c3c95d1d # v1.8.0
|
||||
- name: Report bundle size
|
||||
uses: andresz1/size-limit-action@master
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
package_manager: pnpm
|
||||
directory: packages/thirdweb
|
||||
|
||||
- name: Report bundle size (nexus)
|
||||
uses: andresz1/size-limit-action@94bc357df29c36c8f8d50ea497c3e225c3c95d1d # v1.8.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
package_manager: pnpm
|
||||
directory: packages/nexus
|
||||
directory: packages/thirdweb
|
||||
@@ -2,7 +2,7 @@ name: Auto Author Assign
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, ready_for_review]
|
||||
types: [opened, reopened, ready_for_review, draft]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
@@ -16,4 +16,4 @@ jobs:
|
||||
github.event.pull_request.author_association == 'COLLABORATOR' ||
|
||||
github.event.pull_request.author_association == 'CONTRIBUTOR'
|
||||
steps:
|
||||
- uses: toshimaru/auto-author-assign@16f0022cf3d7970c106d8d1105f75a1165edb516 # v2.1.1
|
||||
- uses: toshimaru/[email protected]
|
||||
|
||||
@@ -42,11 +42,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
@@ -72,4 +72,4 @@ jobs:
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
|
||||
uses: github/codeql-action/analyze@v3
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
name: Linked Issue
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, ready_for_review]
|
||||
|
||||
env:
|
||||
VALID_ISSUE_PREFIXES: "CORE|TOOL|NEB|INFRA"
|
||||
|
||||
jobs:
|
||||
linear:
|
||||
name: Linear
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check for linked issue
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const pr = await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.issue.number
|
||||
});
|
||||
|
||||
// Check if contributor is external
|
||||
const isInternalContributor = ['MEMBER', 'OWNER', 'COLLABORATOR'].includes(
|
||||
context.payload.pull_request.author_association
|
||||
);
|
||||
|
||||
// Automatically pass for external contributors
|
||||
if (!isInternalContributor) {
|
||||
console.log('External contributor detected - automatically passing check');
|
||||
return;
|
||||
}
|
||||
|
||||
const body = pr.data.body || '';
|
||||
const branchName = pr.data.head.ref;
|
||||
const issueRegex = new RegExp(`(${process.env.VALID_ISSUE_PREFIXES})-\\d+`, 'i');
|
||||
const branchIssueRegex = new RegExp(`(${process.env.VALID_ISSUE_PREFIXES.toLowerCase()})-\\d+`, 'i');
|
||||
|
||||
if (!issueRegex.test(body) && !branchIssueRegex.test(branchName)) {
|
||||
core.setFailed(
|
||||
`No valid issue reference found. PR body or branch name must contain an issue ID with one of these prefixes: ${process.env.VALID_ISSUE_PREFIXES}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const matches = body.match(issueRegex) || branchName.match(branchIssueRegex);
|
||||
console.log(`Found issue reference: ${matches[0]}`);
|
||||
@@ -17,6 +17,6 @@ jobs:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0
|
||||
- uses: actions/labeler@v5
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# Do not use the GITHUB_TOKEN by default
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# Do not use the GITHUB_TOKEN by default
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
- name: Create release Pull Request or publish to NPM
|
||||
id: changesets
|
||||
uses: changesets/action@e0145edc7d9d8679003495b11f87bd8ef63c0cba #v1.4.0
|
||||
uses: changesets/action@v1
|
||||
with:
|
||||
publish: pnpm release
|
||||
version: pnpm version-packages
|
||||
|
||||
@@ -7,7 +7,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v.9.1.0
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
stale-issue-message: 'This issue has been inactive for 7 days. It is now marked as stale and will be closed in 2 days if no further activity occurs.'
|
||||
stale-pr-message: 'This PR has been inactive for 7 days. It is now marked as stale and will be closed in 2 days if no further activity occurs.'
|
||||
|
||||
@@ -21,10 +21,30 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install
|
||||
uses: ./.github/composite-actions/install
|
||||
|
||||
- name: Run TypeDoc
|
||||
run: pnpm typedoc
|
||||
|
||||
- name: Update Gist
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GIST_TOKEN }}
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('./packages/thirdweb/typedoc/parsed.json', 'utf8');
|
||||
const gistId = '678fe1f331a01270bb002fee660f285d';
|
||||
|
||||
await github.rest.gists.update({
|
||||
gist_id: gistId,
|
||||
files: {
|
||||
'data.json': {
|
||||
content: content
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`Permalink: https://gist.githubusercontent.com/raw/${gistId}/data.json`);
|
||||
|
||||
@@ -16,7 +16,6 @@ yalc.lock
|
||||
./build/
|
||||
playwright-report/
|
||||
.env/
|
||||
.pnpm-store/
|
||||
|
||||
# codecov binary
|
||||
codecov
|
||||
@@ -31,6 +30,4 @@ packages/*/typedoc/*
|
||||
storybook-static
|
||||
.aider*
|
||||
|
||||
tsconfig.tsbuildinfo
|
||||
.cursor
|
||||
apps/dashboard/node-compile-cache
|
||||
tsconfig.tsbuildinfo
|
||||
@@ -1,3 +0,0 @@
|
||||
public-hoist-pattern[]=*import-in-the-middle*
|
||||
public-hoist-pattern[]=*require-in-the-middle*
|
||||
public-hoist-pattern[]=*pino-pretty*
|
||||
@@ -1,8 +1,19 @@
|
||||
{
|
||||
"[css]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.organizeImports.biome": "explicit",
|
||||
"quickfix.biome": "explicit"
|
||||
},
|
||||
"editor.defaultFormatter": "biomejs.biome",
|
||||
"typescript.preferences.autoImportFileExcludePatterns": [
|
||||
"./packages/thirdweb/src/exports"
|
||||
],
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[javascript]": {
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[json]": {
|
||||
@@ -11,30 +22,14 @@
|
||||
"[markdown]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescript]": {
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"[css]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"editor.codeActionsOnSave": {
|
||||
"quickfix.biome": "explicit",
|
||||
"source.fixAll.biome": "explicit"
|
||||
},
|
||||
"editor.defaultFormatter": "biomejs.biome",
|
||||
"editor.formatOnSave": true,
|
||||
"eslint.workingDirectories": [
|
||||
{ "pattern": "./packages/*/" },
|
||||
{ "pattern": "./apps/*/" }
|
||||
],
|
||||
"typescript.preferences.autoImportFileExcludePatterns": [
|
||||
"./packages/thirdweb/src/exports"
|
||||
],
|
||||
"typescript.preferences.autoImportSpecifierExcludeRegexes": [
|
||||
"@radix-ui",
|
||||
"next/router",
|
||||
"next/dist",
|
||||
"^lucide-react/dist/lucide-react.suffixed$"
|
||||
],
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
🤖 Codex Agent Guidelines for thirdweb-dev/js
|
||||
|
||||
Welcome, AI copilots! This guide captures the coding standards, architectural decisions, and workflow conventions that every automated agent (and human contributor!) must follow. Unless a rule explicitly targets a sub‑project, it applies repo‑wide.
|
||||
|
||||
⸻
|
||||
|
||||
1. GitHub Workflow & Etiquette
|
||||
|
||||
- Pull‑request titles must start with the affected workspace in brackets (e.g. [SDK], [Dashboard], [Portal], [Playground]).
|
||||
- Begin the PR description with a one‑sentence summary, then add a checklist of changes and reference issues with Fixes #123.
|
||||
- Keep commits small and topical – one logical change per commit.
|
||||
- Branch names should follow area/brief-topic (e.g. sdk/fix-gas-estimate). Avoid personal names.
|
||||
- Request at least one core maintainer review. Do not self‑merge unless you are the sole owner of that package.
|
||||
- All CI checks (type‑check, Biome, tests) must pass before merging.
|
||||
|
||||
⸻
|
||||
|
||||
2. Formatting & Linting
|
||||
|
||||
- Biome governs formatting and linting; its rules live in biome.json.
|
||||
- Run pnpm biome check --apply before committing.
|
||||
- Avoid editor‑specific configs; rely on the shared settings.
|
||||
- make sure everything builds after each file change by running `pnpm build`
|
||||
|
||||
⸻
|
||||
|
||||
3. TypeScript Style Guide
|
||||
|
||||
- Write idiomatic TypeScript: explicit function declarations and return types.
|
||||
- Limit each file to one stateless, single‑responsibility function for clarity and testability.
|
||||
- Re‑use shared types from @/types or local types.ts barrels.
|
||||
- Prefer type aliases over interface except for nominal shapes.
|
||||
- Avoid any and unknown unless unavoidable; narrow generics whenever possible.
|
||||
- Choose composition over inheritance; leverage utility types (Partial, Pick, etc.).
|
||||
|
||||
⸻
|
||||
|
||||
4. Testing Strategy
|
||||
|
||||
- Co‑locate tests: foo.ts ↔ foo.test.ts.
|
||||
- Use real function invocations with stub data; avoid brittle mocks.
|
||||
- For network interactions, use Mock Service Worker (MSW) to intercept fetch/HTTP calls, mocking only scenarios that are hard to reproduce.
|
||||
- Keep tests deterministic and side‑effect free; Vitest is pre‑configured.
|
||||
- to run the tests: `cd packages thirdweb & pnpm test:dev <filename>`
|
||||
|
||||
⸻
|
||||
|
||||
5. packages/thirdweb
|
||||
|
||||
5.1 Public API Surface
|
||||
|
||||
- Export everything via the exports/ directory, grouped by feature.
|
||||
- Every public symbol must have comprehensive TSDoc:
|
||||
- Include at least one @example block that compiles.
|
||||
- Tag with one custom annotation (@beta, @internal, @experimental, etc.).
|
||||
- Comment only ambiguous logic; avoid restating TypeScript in prose.
|
||||
|
||||
5.2 Performance
|
||||
|
||||
- Lazy‑load heavy dependencies inside async paths to keep the initial bundle lean:
|
||||
|
||||
`const { jsPDF } = await import("jspdf");`
|
||||
|
||||
⸻
|
||||
|
||||
6. apps/dashboard & apps/playground
|
||||
|
||||
6.1 Core UI Toolkit
|
||||
|
||||
- Import primitives from @/components/ui/\_ (e.g. Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator).
|
||||
- Use NavLink for internal navigation so active states are handled automatically.
|
||||
- Group feature‑specific components under feature/components/\_ and expose a barrel index.ts when necessary.
|
||||
|
||||
6.2 Styling Conventions
|
||||
|
||||
- Tailwind CSS is the styling system – no inline styles or CSS modules.
|
||||
- Merge class names with cn() from @/lib/utils to keep conditional logic readable.
|
||||
- Stick to design tokens: backgrounds (bg-card), borders (border-border), muted text (text-muted-foreground), etc.
|
||||
- Expose a className prop on the root element of every component for overrides.
|
||||
|
||||
6.3 Component Patterns
|
||||
|
||||
- Server Components (run on the Node edge):
|
||||
-- Read cookies/headers with next/headers.
|
||||
-- Access server‑only environment variables or secrets.
|
||||
-- Perform heavy data fetching that should not ship to the client.
|
||||
-- Implement redirect logic with redirect() from next/navigation.
|
||||
-- Start files with import "server-only"; to prevent client bundling.
|
||||
- Client Components (run in the browser):
|
||||
-- Begin files with 'use client'; before imports.
|
||||
-- Handle interactive UI relying on React hooks (useState, useEffect, React Query, wallet hooks).
|
||||
-- Access browser APIs (localStorage, window, IntersectionObserver, etc.).
|
||||
-- Support fast transitions where data is prefetched on the client.
|
||||
|
||||
6.4 Data Fetching Guidelines
|
||||
|
||||
- Server Side
|
||||
-- Always call getAuthToken() to retrieve the JWT from cookies.
|
||||
-- Inject the token as an Authorization: Bearer header – never embed it in the URL.
|
||||
-- Return typed results (Project[], User[], …) – avoid any.
|
||||
- Client Side
|
||||
-- Wrap calls in React Query (@tanstack/react-query).
|
||||
-- Use descriptive, stable queryKeys for cache hits.
|
||||
-- Configure staleTime / cacheTime based on freshness requirements (default ≥ 60 s).
|
||||
-- Keep tokens secret by calling internal API routes or server actions.
|
||||
|
||||
6.5 Analytics Event Reporting
|
||||
|
||||
- **When to create a new event**
|
||||
-- Only add events that answer a clear product or business question.
|
||||
-- Check `src/@/analytics/report.ts` first; avoid duplicates.
|
||||
|
||||
- **Naming conventions**
|
||||
-- **Event name**: human-readable phrase in the form `<subject> <verb>` (e.g. "contract deployed").
|
||||
-- **Reporting function**: `report<Subject><Verb>` (PascalCase).
|
||||
-- All reporting helpers currently live in the shared `report.ts` file.
|
||||
|
||||
- **Boilerplate template**
|
||||
-- Add a JSDoc header explaining **Why** the event exists and **Who** owns it (`@username`).
|
||||
-- Accept a single typed `properties` object and forward it unchanged to `posthog.capture`.
|
||||
-- Example:
|
||||
|
||||
```ts
|
||||
/**
|
||||
* ### Why do we need to report this event?
|
||||
* - Tracks number of contracts deployed
|
||||
*
|
||||
* ### Who is responsible for this event?
|
||||
* @jnsdls
|
||||
*/
|
||||
export function reportContractDeployed(properties: {
|
||||
address: string;
|
||||
chainId: number;
|
||||
}) {
|
||||
posthog.capture("contract deployed", properties);
|
||||
}
|
||||
```
|
||||
|
||||
- **Client-side only**: never import `posthog-js` in server components.
|
||||
- **Housekeeping**: Inform **#eng-core-services** before renaming or removing an existing event.
|
||||
|
||||
⸻
|
||||
|
||||
7. Performance & Bundle Size
|
||||
|
||||
- Track bundle budgets via package.json#size-limit.
|
||||
- Lazy‑import optional features; avoid top‑level side‑effects.
|
||||
- De‑duplicate dependencies across packages through pnpm workspace hoisting.
|
||||
|
||||
⸻
|
||||
|
||||
8. Documentation & Developer Experience
|
||||
|
||||
- Each change in packages/\* should contain a changeset for the appropriate package, with the appropriate version bump
|
||||
|
||||
- patch for changes that don't impact the public API
|
||||
- minor for any new/modified public API
|
||||
|
||||
- Surface breaking changes prominently in PR descriptions.
|
||||
- For new UI components, add Storybook stories (\*.stories.tsx) alongside the code.
|
||||
@@ -1,290 +0,0 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Package Management
|
||||
|
||||
- Use `pnpm install` (or `pnpm install --ignore-scripts` on Windows)
|
||||
- Monorepo managed with Turborepo and pnpm workspaces
|
||||
|
||||
### Building
|
||||
|
||||
```bash
|
||||
# Build all packages
|
||||
pnpm build
|
||||
|
||||
# Build specific packages with dependencies
|
||||
turbo run build --filter=./packages/*
|
||||
|
||||
# Development with watch mode
|
||||
pnpm dev # Core thirdweb package
|
||||
pnpm dashboard # Run dashboard + dependencies
|
||||
pnpm playground # Run playground + dependencies
|
||||
pnpm portal # Run portal docs + dependencies
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
pnpm test
|
||||
|
||||
# Interactive testing (thirdweb package)
|
||||
cd packages/thirdweb && pnpm test:devr
|
||||
|
||||
# Test specific file
|
||||
pnpm test:dev <path-to-test-file>
|
||||
|
||||
# E2E testing (dashboard)
|
||||
cd apps/dashboard && pnpm playwright
|
||||
```
|
||||
|
||||
### Code Quality
|
||||
|
||||
```bash
|
||||
# Lint all packages
|
||||
pnpm lint
|
||||
|
||||
# Auto-fix linting issues
|
||||
pnpm fix
|
||||
|
||||
# Format code
|
||||
turbo run format
|
||||
|
||||
# Biome is the primary linter/formatter
|
||||
```
|
||||
|
||||
### Development Workflow
|
||||
|
||||
```bash
|
||||
# Start development server for dashboard
|
||||
pnpm dashboard
|
||||
|
||||
# Start playground for SDK testing
|
||||
pnpm playground
|
||||
|
||||
# Generate changeset for releases
|
||||
pnpm changeset
|
||||
|
||||
# Version packages
|
||||
pnpm version-packages
|
||||
```
|
||||
|
||||
## Repository Architecture
|
||||
|
||||
### Monorepo Structure
|
||||
|
||||
This is a Turborepo monorepo with the main thirdweb v5 SDK consolidated into `/packages/thirdweb/`. Legacy packages are in `/legacy_packages/`.
|
||||
|
||||
### Core Package (`/packages/thirdweb/`)
|
||||
|
||||
**Main Modules:**
|
||||
|
||||
- `client/` - ThirdwebClient foundation
|
||||
- `chains/` - 50+ supported blockchain definitions
|
||||
- `contract/` - Contract interaction with automatic ABI resolution
|
||||
- `transaction/` - Transaction management and execution
|
||||
- `wallets/` - Comprehensive wallet integration system
|
||||
- `extensions/` - Modular contract extensions (ERC20, ERC721, etc.)
|
||||
- `auth/` - SIWE authentication and signature verification
|
||||
- `pay/` - Fiat and crypto payment infrastructure
|
||||
- `storage/` - IPFS integration for decentralized storage
|
||||
- `rpc/` - Low-level blockchain communication
|
||||
|
||||
**Exports Structure:**
|
||||
The SDK uses modular exports from `src/exports/` including:
|
||||
|
||||
- `thirdweb.ts` - Core client and utilities
|
||||
- `chains.ts` - Chain definitions
|
||||
- `wallets.ts` - Wallet connectors
|
||||
- `react.ts` - React hooks and components
|
||||
- `extensions/` - Contract standards and protocols
|
||||
|
||||
### Applications (`/apps/`)
|
||||
|
||||
- **dashboard** - Web-based developer console (Next.js, Chakra UI)
|
||||
- **playground-web** - Interactive SDK testing environment
|
||||
- **portal** - Documentation site with MDX
|
||||
- **nebula** - Account abstraction and smart wallet management
|
||||
- **wallet-ui** - Wallet interface and testing
|
||||
|
||||
### Key Packages (`/packages/`)
|
||||
|
||||
- **thirdweb** - Main SDK (TypeScript, React, React Native)
|
||||
- **engine** - thirdweb Engine API client
|
||||
- **insight** - Analytics and data APIs
|
||||
- **nebula** - Account abstraction client
|
||||
- **service-utils** - Shared utilities across services
|
||||
|
||||
## Development Practices
|
||||
|
||||
### GitHub Workflow & Pull Requests
|
||||
|
||||
- **PR titles**: Must start with affected workspace in brackets (e.g. `[SDK]`, `[Dashboard]`, `[Portal]`, `[Playground]`)
|
||||
- **PR descriptions**: Begin with one-sentence summary, add checklist of changes, reference issues with `Fixes #123`
|
||||
- **Commits**: Keep small and topical – one logical change per commit
|
||||
- **Branch naming**: Use `area/brief-topic` format (e.g. `sdk/fix-gas-estimate`). Avoid personal names
|
||||
- **Reviews**: Request at least one core maintainer review. Do not self-merge unless sole package owner
|
||||
- **CI requirements**: All checks (type-check, Biome, tests) must pass before merging
|
||||
|
||||
### Code Quality & Formatting
|
||||
|
||||
- **Biome**: Primary linter/formatter (rules in `biome.json`)
|
||||
- **Pre-commit**: Run `pnpm biome check --apply` before committing
|
||||
- **Build verification**: Run `pnpm build` after each file change to ensure everything builds
|
||||
- Avoid editor-specific configs; rely on shared settings
|
||||
|
||||
### TypeScript Guidelines
|
||||
|
||||
- **Style**: Write idiomatic TypeScript with explicit function declarations and return types
|
||||
- **File structure**: Limit each file to one stateless, single-responsibility function for clarity
|
||||
- **Types**: Re-use shared types from `@/types` or local `types.ts` barrels
|
||||
- **Interfaces vs Types**: Prefer type aliases over interface except for nominal shapes
|
||||
- **Type safety**: Avoid `any` and `unknown` unless unavoidable; narrow generics when possible
|
||||
- **Architecture**: Choose composition over inheritance; leverage utility types (`Partial`, `Pick`, etc.)
|
||||
|
||||
### Testing Strategy
|
||||
|
||||
- **Co-location**: Place tests alongside code: `foo.ts` ↔ `foo.test.ts`
|
||||
- **Test approach**: Use real function invocations with stub data; avoid brittle mocks
|
||||
- **Network mocking**: Use Mock Service Worker (MSW) for fetch/HTTP call interception
|
||||
- **Test quality**: Keep tests deterministic and side-effect free
|
||||
- **Running tests**: `cd packages/thirdweb && pnpm test:dev <filename>`
|
||||
- **Test accounts**: Predefined accounts in `test/src/test-wallets.ts`
|
||||
- **Chain forking**: Use `FORKED_ETHEREUM_CHAIN` for mainnet interactions, `ANVIL_CHAIN` for isolated tests
|
||||
|
||||
### SDK Development (`packages/thirdweb`)
|
||||
|
||||
#### Public API Guidelines
|
||||
- **Exports**: Export everything via `exports/` directory, grouped by feature
|
||||
- **Documentation**: Every public symbol must have comprehensive TSDoc with:
|
||||
- At least one `@example` block that compiles
|
||||
- Custom annotation tags (`@beta`, `@internal`, `@experimental`)
|
||||
- **Comments**: Comment only ambiguous logic; avoid restating TypeScript in prose
|
||||
|
||||
#### Performance Optimization
|
||||
- **Lazy loading**: Load heavy dependencies inside async paths to keep initial bundle lean:
|
||||
```typescript
|
||||
const { jsPDF } = await import("jspdf");
|
||||
```
|
||||
- **Bundle budgets**: Track via `package.json#size-limit`
|
||||
- **Dependencies**: De-duplicate across packages through pnpm workspace hoisting
|
||||
|
||||
### Dashboard & Playground Development
|
||||
|
||||
#### UI Component Standards
|
||||
- **Core components**: Import primitives from `@/components/ui/*` (Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator)
|
||||
- **Navigation**: Use `NavLink` for internal navigation with automatic active states
|
||||
- **Organization**: Group feature-specific components under `feature/components/*` with barrel `index.ts`
|
||||
|
||||
#### Styling Conventions
|
||||
- **CSS framework**: Tailwind CSS only – no inline styles or CSS modules
|
||||
- **Class merging**: Use `cn()` from `@/lib/utils` for conditional logic
|
||||
- **Design tokens**: Use design system tokens (backgrounds: `bg-card`, borders: `border-border`, muted text: `text-muted-foreground`)
|
||||
- **Component API**: Expose `className` prop on root element for overrides
|
||||
|
||||
#### Component Architecture
|
||||
- **Server Components** (Node edge):
|
||||
- Start files with `import "server-only";`
|
||||
- Read cookies/headers with `next/headers`
|
||||
- Access server-only environment variables
|
||||
- Perform heavy data fetching
|
||||
- Implement redirect logic with `redirect()` from `next/navigation`
|
||||
- **Client Components** (browser):
|
||||
- Begin files with `'use client';`
|
||||
- Handle interactive UI with React hooks (`useState`, `useEffect`, React Query, wallet hooks)
|
||||
- Access browser APIs (`localStorage`, `window`, `IntersectionObserver`)
|
||||
- Support fast transitions with prefetched data
|
||||
|
||||
#### Data Fetching Patterns
|
||||
- **Server Side**:
|
||||
- Always call `getAuthToken()` to retrieve JWT from cookies
|
||||
- Use `Authorization: Bearer` header – never embed tokens in URLs
|
||||
- Return typed results (`Project[]`, `User[]`) – avoid `any`
|
||||
- **Client Side**:
|
||||
- Wrap calls in React Query (`@tanstack/react-query`)
|
||||
- Use descriptive, stable `queryKeys` for cache hits
|
||||
- Configure `staleTime`/`cacheTime` based on freshness (default ≥ 60s)
|
||||
- Keep tokens secret via internal API routes or server actions
|
||||
|
||||
#### Analytics Event Guidelines
|
||||
- **When to add**: Only create events that answer clear product/business questions
|
||||
- **Check duplicates**: Review `src/@/analytics/report.ts` first
|
||||
- **Naming**:
|
||||
- Event name: human-readable `<subject> <verb>` (e.g. "contract deployed")
|
||||
- Function: `report<Subject><Verb>` (PascalCase)
|
||||
- **Template**:
|
||||
```typescript
|
||||
/**
|
||||
* ### Why do we need to report this event?
|
||||
* - Tracks number of contracts deployed
|
||||
*
|
||||
* ### Who is responsible for this event?
|
||||
* @username
|
||||
*/
|
||||
export function reportContractDeployed(properties: {
|
||||
address: string;
|
||||
chainId: number;
|
||||
}) {
|
||||
posthog.capture("contract deployed", properties);
|
||||
}
|
||||
```
|
||||
- **Client-side only**: Never import `posthog-js` in server components
|
||||
- **Changes**: Inform **#eng-core-services** before renaming/removing events
|
||||
|
||||
### Extension Development
|
||||
|
||||
- Extensions follow modular pattern in `src/extensions/`
|
||||
- Auto-generated contracts from ABI definitions
|
||||
- Composable functions with TypeScript safety
|
||||
- Support for read/write operations
|
||||
|
||||
### Wallet Architecture
|
||||
|
||||
- Unified `Wallet` and `Account` interfaces
|
||||
- Support for in-app wallets (social/email login)
|
||||
- Smart wallets with account abstraction
|
||||
- EIP-1193, EIP-5792, EIP-7702 standard support
|
||||
|
||||
## Contribution Workflow
|
||||
|
||||
1. **Fork and Clone**: Create fork, clone, create feature branch
|
||||
2. **Install**: `pnpm install` (use `--ignore-scripts` on Windows)
|
||||
3. **Develop**: Use appropriate dev commands above
|
||||
4. **Test**: Write unit tests, run linting, test on demo apps
|
||||
5. **Changeset**: Run `pnpm changeset` for semantic versioning
|
||||
6. **PR**: Submit pull request to main branch
|
||||
|
||||
### Release Testing
|
||||
|
||||
Comment `/release-pr` on PR to trigger dev release to npm for testing.
|
||||
|
||||
### Changeset Guidelines
|
||||
|
||||
Each change in `packages/*` should contain a changeset for the appropriate package with the appropriate version bump:
|
||||
- **patch**: Changes that don't impact the public API
|
||||
- **minor**: Any new/modified public API
|
||||
- **major**: Breaking changes (surface prominently in PR descriptions)
|
||||
|
||||
### Documentation Standards
|
||||
|
||||
- For new UI components, add Storybook stories (`*.stories.tsx`) alongside the code
|
||||
- Surface breaking changes prominently in PR descriptions
|
||||
- Keep documentation focused on developer experience and practical usage
|
||||
|
||||
## Multi-Platform Support
|
||||
|
||||
The SDK supports:
|
||||
|
||||
- **Web**: React hooks and components
|
||||
- **React Native**: Mobile-specific exports and components
|
||||
- **Node.js**: Server-side functionality
|
||||
- **Framework Adapters**: Wagmi, Ethers compatibility layers
|
||||
|
||||
Key files:
|
||||
|
||||
- `src/exports/react.native.ts` - React Native specific exports
|
||||
- `packages/react-native-adapter/` - Mobile platform shims
|
||||
- `packages/wagmi-adapter/` - Wagmi ecosystem integration
|
||||
@@ -1,7 +1,7 @@
|
||||
<p align="center">
|
||||
<br />
|
||||
<a href="https://thirdweb.com">
|
||||
<img src="https://thirdweb.com/brand/thirdweb-icon.svg" width="200" alt=""/></a>
|
||||
<img src="https://github.com/thirdweb-dev/js/blob/main/legacy_packages/sdk/logo.svg?raw=true" width="200" alt=""/></a>
|
||||
<br />
|
||||
</p>
|
||||
|
||||
@@ -11,154 +11,44 @@
|
||||
<a href="https://github.com/thirdweb-dev/js/actions/workflows/CI.yml">
|
||||
<img alt="Build Status" src="https://github.com/thirdweb-dev/js/actions/workflows/CI.yml/badge.svg"/>
|
||||
</a>
|
||||
<a href="https://discord.gg/thirdweb">
|
||||
<img alt="Join our Discord!" src="https://img.shields.io/discord/834227967404146718.svg?color=7289da&label=discord&logo=discord&style=flat"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center"><strong>All-in-one web3 SDK for Browser, Node and Mobile apps</strong></p>
|
||||
|
||||
## Core Package
|
||||
## Features
|
||||
|
||||
#### [`thirdweb`](./packages/thirdweb/README.md)
|
||||
|
||||
The main SDK package providing all-in-one web3 functionality for Browser, Node, and Mobile applications.
|
||||
|
||||
```bash
|
||||
npm install thirdweb
|
||||
```
|
||||
|
||||
**Features:**
|
||||
|
||||
- Type-safe contract and transaction APIs
|
||||
- In-app wallets with social/email login
|
||||
- Account abstraction (ERC4337/EIP7702) support
|
||||
- 500+ external wallets supported
|
||||
- Built in infra (RPC, bundler, paymaster, indexer)
|
||||
- React hooks and UI components
|
||||
- Support for React & React-Native
|
||||
- First party support for [Embedded Wallets](https://portal.thirdweb.com/connect/embedded-wallet/overview) (social/email login)
|
||||
- First party support for [Account Abstraction](https://portal.thirdweb.com/connect/account-abstraction/overview)
|
||||
- Instant connection to any chain with RPC Edge integration
|
||||
- Integrated IPFS upload/download
|
||||
- UI Components for connection, transactions, nft rendering
|
||||
- High level contract extensions for interacting with common standards
|
||||
- Automatic ABI resolution
|
||||
- IPFS upload/download
|
||||
- Cross-platform support (Web, React Native)
|
||||
|
||||
### Documentation
|
||||
## Library Comparison
|
||||
|
||||
Visit the [developer portal](https://portal.thirdweb.com) for full documentation.
|
||||
|
||||
### 🚀 Quick Start
|
||||
|
||||
#### For React Applications
|
||||
|
||||
```bash
|
||||
npm install thirdweb
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { createThirdwebClient } from "thirdweb";
|
||||
import { ConnectButton, useActiveAccount } from "thirdweb/react";
|
||||
|
||||
const client = createThirdwebClient({
|
||||
clientId: "YOUR_CLIENT_ID",
|
||||
});
|
||||
|
||||
function App() {
|
||||
const account = useActiveAccount();
|
||||
console.log("Connected as", account?.address);
|
||||
|
||||
return <ConnectButton client={client} />;
|
||||
}
|
||||
```
|
||||
|
||||
For React Native Applications, you'll also need to install the `@thirdweb-dev/react-native-adapter` package and import it at app startup for polyfills.
|
||||
|
||||
#### For Backend Applications
|
||||
|
||||
```bash
|
||||
npm install thirdweb
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { createThirdwebClient, Engine } from "thirdweb";
|
||||
|
||||
const client = createThirdwebClient({
|
||||
secretKey: "YOUR_SECRET_KEY",
|
||||
});
|
||||
|
||||
const wallet = Engine.serverWallet({
|
||||
client,
|
||||
address: "0x...",
|
||||
});
|
||||
|
||||
const transaction = transfer({
|
||||
contract: getContract({
|
||||
client,
|
||||
address: "0x...", // token contract
|
||||
chain: defineChain(1),
|
||||
}),
|
||||
to: "0x...", // recipient
|
||||
amount: "0.01", // amount in tokens
|
||||
});
|
||||
|
||||
await wallet.enqueueTransaction({
|
||||
transaction,
|
||||
});
|
||||
```
|
||||
|
||||
## Adapters
|
||||
|
||||
#### [`@thirdweb-dev/react-native-adapter`](./packages/react-native-adapter/README.md)
|
||||
|
||||
Required polyfills and configuration for running the thirdweb SDK in React Native applications.
|
||||
|
||||
```bash
|
||||
npm install @thirdweb-dev/react-native-adapter
|
||||
```
|
||||
|
||||
#### [`@thirdweb-dev/wagmi-adapter`](./packages/wagmi-adapter/README.md)
|
||||
|
||||
Integration layer for using thirdweb's in-app wallets with wagmi.
|
||||
|
||||
```bash
|
||||
npm install @thirdweb-dev/wagmi-adapter
|
||||
```
|
||||
|
||||
## Type safe API wrappers
|
||||
|
||||
#### [`@thirdweb-dev/api`](./packages/api/README.md)
|
||||
|
||||
TypeScript SDK for thirdweb's API, combining all of thirdweb products.
|
||||
|
||||
```bash
|
||||
npm install @thirdweb-dev/api
|
||||
```
|
||||
|
||||
#### [`@thirdweb-dev/engine`](./packages/engine/README.md)
|
||||
|
||||
TypeScript SDK for Engine, thirdweb's backend onchain executor service.
|
||||
|
||||
```bash
|
||||
npm install @thirdweb-dev/engine
|
||||
```
|
||||
|
||||
#### [`@thirdweb-dev/insight`](./packages/insight/README.md)
|
||||
|
||||
TypeScript SDK for Insight, thirdweb's multichain indexer service.
|
||||
|
||||
```bash
|
||||
npm install @thirdweb-dev/insight
|
||||
```
|
||||
|
||||
#### [`@thirdweb-dev/vault-sdk`](./packages/vault-sdk/README.md)
|
||||
|
||||
SDK for interacting with Vault, thirdweb's secure key management service.
|
||||
|
||||
```bash
|
||||
npm install @thirdweb-dev/vault-sdk
|
||||
```
|
||||
|
||||
#### [`@thirdweb-dev/nebula`](./packages/nebula/README.md)
|
||||
|
||||
TypeScript SDK for Nebula, thirdweb's AI agent service.
|
||||
|
||||
```bash
|
||||
npm install @thirdweb-dev/nebula
|
||||
```
|
||||
| | thirdweb | Wagmi + Viem | Ethers@6 |
|
||||
| ----------------------------------------- | -------- | ------------------ | -------- |
|
||||
| Type safe contract API | ✅ | ✅ | ✅ |
|
||||
| Type safe wallet API | ✅ | ✅ | ✅ |
|
||||
| EVM utils | ✅ | ✅ | ✅ |
|
||||
| RPC for any EVM | ✅ | ⚠️ public RPC only | ❌ |
|
||||
| Automatic ABI Resolution | ✅ | ❌ | ❌ |
|
||||
| IPFS Upload/Download | ✅ | ❌ | ❌ |
|
||||
| Embedded wallet (email/ social login) | ✅ | ⚠️ via 3rd party | ❌ |
|
||||
| Account abstraction (ERC4337) support | ✅ | ⚠️ via 3rd party | ❌ |
|
||||
| Web3 wallet connectors | ✅ | ✅ | ❌ |
|
||||
| Local wallet creation | ✅ | ✅ | ✅ |
|
||||
| Auth (SIWE) | ✅ | ✅ | ❌ |
|
||||
| Extensions functions for common standards | ✅ | ✅ | ❌ |
|
||||
| React Hooks | ✅ | ✅ | ❌ |
|
||||
| React UI components | ✅ | ❌ | ❌ |
|
||||
| React Native Hooks | ✅ | ✅ | ❌ |
|
||||
| React Native UI Components | ✅ | ❌ | ❌ |
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -168,12 +58,9 @@ See our [open source page](https://thirdweb.com/open-source) for more informatio
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Dashboard](https://thirdweb.com/login)
|
||||
- [Documentation](https://portal.thirdweb.com/)
|
||||
- [Documentation](https://portal.thirdweb.com/typescript/v5)
|
||||
- [Templates](https://thirdweb.com/templates)
|
||||
- [YouTube](https://www.youtube.com/c/thirdweb)
|
||||
- [X/Twitter](https://x.com/thirdweb)
|
||||
- [Telegram](https://t.me/officialthirdweb)
|
||||
|
||||
## Support
|
||||
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
#
|
||||
# Public (client)
|
||||
# ------------------------------------------------------------------------
|
||||
#
|
||||
|
||||
# API host. For local development, please use "https://api.thirdweb-dev.com"
|
||||
# API authorized domain
|
||||
# not required to build, defaults to prod
|
||||
NEXT_PUBLIC_THIRDWEB_DOMAIN="localhost:3000"
|
||||
|
||||
# API host. For local development, please use "https://api.thirdweb-preview.com"
|
||||
# otherwise: "https://api.thirdweb.com"
|
||||
# local host - http://127.0.0.1:3005
|
||||
NEXT_PUBLIC_THIRDWEB_API_HOST="https://api.thirdweb-dev.com"
|
||||
|
||||
# Bridge API. For local development, please use "https://bridge.thirdweb-dev.com"
|
||||
# otherwise: "https://bridge.thirdweb.com"
|
||||
NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST="https://bridge.thirdweb-dev.com"
|
||||
|
||||
# in-app wallet host
|
||||
# Paper API host
|
||||
NEXT_PUBLIC_THIRDWEB_EWS_API_HOST="https://ews.thirdweb-dev.com"
|
||||
|
||||
# Client ID - Required
|
||||
# Get the client id from https://thirdweb.com/create-api-key and set it here, and also set DASHBOARD_SECRET_KEY below
|
||||
# thirdweb local api host
|
||||
# NEXT_PUBLIC_THIRDWEB_API_HOST="http://127.0.0.1:3005"
|
||||
|
||||
# Client ID
|
||||
# Get the client id from https://thirdweb.com/create-api-key and set it here,
|
||||
# make sure to allow localhost:3000 in the allowed origins
|
||||
NEXT_PUBLIC_DASHBOARD_CLIENT_ID=""
|
||||
NEXT_PUBLIC_DASHBOARD_CLIENT_ID="<replace_me_with_your_client_id>"
|
||||
|
||||
# IPFS gateway url
|
||||
NEXT_PUBLIC_IPFS_GATEWAY_URL="https://{clientId}.thirdwebstorage-dev.com/ipfs/{cid}/{path}"
|
||||
@@ -30,39 +33,68 @@ NEXT_PUBLIC_TYPESENSE_CONTRACT_API_KEY=
|
||||
|
||||
# posthog API key
|
||||
# - not required for prod/staging
|
||||
NEXT_PUBLIC_POSTHOG_KEY=""
|
||||
NEXT_PUBLIC_POSTHOG_API_KEY="ignored"
|
||||
|
||||
# required for server wallet management
|
||||
NEXT_PUBLIC_THIRDWEB_VAULT_URL=""
|
||||
NEXT_PUBLIC_ENGINE_CLOUD_URL=""
|
||||
# Stripe Customer portal
|
||||
NEXT_PUBLIC_STRIPE_KEY=
|
||||
|
||||
NEXT_PUBLIC_STRIPE_PAYMENT_METHOD_CFG_ID=
|
||||
|
||||
# Demo Engine - required for showing demo engine page
|
||||
NEXT_PUBLIC_DEMO_ENGINE_URL=""
|
||||
|
||||
# Needed for contract analytics / blockchain data information
|
||||
CHAINSAW_API_KEY=
|
||||
|
||||
#
|
||||
# Private (server)
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
# Get the secret key from https://thirdweb.com/create-api-key and set it here and also set NEXT_PUBLIC_DASHBOARD_CLIENT_ID above,
|
||||
# Make sure to allow localhost:3000 in the allowed origins
|
||||
#
|
||||
# Get the secret key from https://thirdweb.com/create-api-key and set it here, Make sure to allow localhost:3000 in the allowed origins
|
||||
DASHBOARD_SECRET_KEY="<replace_me_with_your_secret_key>"
|
||||
|
||||
# Client id for api routes - required for contract OG image generation
|
||||
# Client id for api routes
|
||||
API_ROUTES_CLIENT_ID=
|
||||
|
||||
# <moralis.io API key (used for api routes for token balances and wallet NFTs)
|
||||
# - not required to build (unless using wallet NFTs and token balances)>
|
||||
MORALIS_API_KEY=
|
||||
|
||||
# alchemy.com API key (used for wallet NFTS)
|
||||
# - cannot be restricted to IP/domain because vercel has no stable IPs and it happens during build & runtime api route call
|
||||
# - not required to build (unless testing wallet NFTs)>
|
||||
SSR_ALCHEMY_KEY=
|
||||
|
||||
# beehiiv.com API key (used for newsletter signups)
|
||||
# - not required to build (unless testing newsletter signups)>
|
||||
BEEHIIV_API_KEY=
|
||||
|
||||
# Hubspot Access Token (used for contact us form)
|
||||
# - not required to build (unless testing contact us form)>
|
||||
HUBSPOT_ACCESS_TOKEN=
|
||||
|
||||
# Github API Token (used for /open-source)
|
||||
GITHUB_API_TOKEN="ghp_..."
|
||||
|
||||
# Upload server url
|
||||
NEXT_PUBLIC_DASHBOARD_UPLOAD_SERVER="https://storage.thirdweb-preview.com"
|
||||
|
||||
# Unthread variables - only required for submitting the support form in /support
|
||||
UNTHREAD_API_KEY=""
|
||||
UNTHREAD_TRIAGE_CHANNEL_ID=""
|
||||
UNTHREAD_EMAIL_INBOX_ID=""
|
||||
UNTHREAD_FREE_TIER_ID=""
|
||||
UNTHREAD_GROWTH_TIER_ID=""
|
||||
UNTHREAD_PRO_TIER_ID=""
|
||||
|
||||
# Demo Engine
|
||||
NEXT_PUBLIC_DEMO_ENGINE_URL=""
|
||||
|
||||
# API server secret (required for thirdweb.com SIWE login). Copy from Vercel.
|
||||
API_SERVER_SECRET=""
|
||||
|
||||
# Used in faucet on chain page (/<chain-id>) and login page (/login) - turnstile is disabled in localhost development
|
||||
# Used for the Faucet page (/<chain_id>)
|
||||
NEXT_PUBLIC_TURNSTILE_SITE_KEY=""
|
||||
TURNSTILE_SECRET_KEY=""
|
||||
|
||||
REDIS_URL=""
|
||||
|
||||
ANALYTICS_SERVICE_URL=""
|
||||
|
||||
# required for billing parts of the dashboard (team -> settings -> billing / invoices)
|
||||
STRIPE_SECRET_KEY=""
|
||||
GROWTH_PLAN_SKU=""
|
||||
PAYMENT_METHOD_CONFIGURATION=""
|
||||
# Required for Nebula Chat
|
||||
NEXT_PUBLIC_NEBULA_URL=""
|
||||
@@ -1,18 +1,140 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
},
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:@next/next/recommended",
|
||||
"plugin:storybook/recommended",
|
||||
],
|
||||
rules: {
|
||||
"react-compiler/react-compiler": "error",
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
selector: "CallExpression[callee.name='useEffect']",
|
||||
message:
|
||||
'Are you *sure* you need to use "useEffect" here? If you loading any async function prefer using "useQuery".',
|
||||
},
|
||||
{
|
||||
selector: "CallExpression[callee.name='createContext']",
|
||||
message:
|
||||
'Are you *sure* you need to use a "Context"? In almost all cases you should prefer passing props directly.',
|
||||
},
|
||||
{
|
||||
selector: "CallExpression[callee.name='defineChain']",
|
||||
message:
|
||||
"Use useV5DashboardChain instead if you are using it inside a component",
|
||||
},
|
||||
{
|
||||
selector: "CallExpression[callee.name='defineDashboardChain']",
|
||||
message:
|
||||
"Use useV5DashboardChain instead if you are using it inside a component",
|
||||
},
|
||||
{
|
||||
selector: "CallExpression[callee.name='mapV4ChainToV5Chain']",
|
||||
message:
|
||||
"Use useV5DashboardChain instead if you are using it inside a component",
|
||||
},
|
||||
{
|
||||
selector: "CallExpression[callee.name='resolveScheme']",
|
||||
message:
|
||||
"resolveScheme can throw error if resolution fails. Either catch the error and ignore the lint warning or Use `resolveSchemeWithErrorHandler` / `replaceIpfsUrl` utility in dashboard instead",
|
||||
},
|
||||
],
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
paths: [
|
||||
{
|
||||
name: "@chakra-ui/react",
|
||||
// these are provided by tw-components, so we don't want to import them from chakra directly
|
||||
importNames: [
|
||||
"Card",
|
||||
"Button",
|
||||
"Checkbox",
|
||||
"Badge",
|
||||
"Drawer",
|
||||
"Heading",
|
||||
"Text",
|
||||
"FormLabel",
|
||||
"FormHelperText",
|
||||
"FormErrorMessage",
|
||||
"MenuGroup",
|
||||
"VStack",
|
||||
"HStack",
|
||||
"AspectRatio",
|
||||
"useToast",
|
||||
"useClipboard",
|
||||
"Badge",
|
||||
"Stack",
|
||||
// also the types
|
||||
"ButtonProps",
|
||||
"BadgeProps",
|
||||
"DrawerProps",
|
||||
"HeadingProps",
|
||||
"TextProps",
|
||||
"FormLabelProps",
|
||||
"HelpTextProps",
|
||||
"MenuGroupProps",
|
||||
"MenuItemProps",
|
||||
"AspectRatioProps",
|
||||
"BadgeProps",
|
||||
"StackProps",
|
||||
],
|
||||
message:
|
||||
'Use the equivalent component from "tw-components" instead.',
|
||||
},
|
||||
{
|
||||
name: "@chakra-ui/layout",
|
||||
message:
|
||||
"Import from `@chakra-ui/react` instead of `@chakra-ui/layout`.",
|
||||
},
|
||||
{
|
||||
name: "@chakra-ui/button",
|
||||
message:
|
||||
"Import from `@chakra-ui/react` instead of `@chakra-ui/button`.",
|
||||
},
|
||||
{
|
||||
name: "@chakra-ui/menu",
|
||||
message:
|
||||
"Import from `@chakra-ui/react` instead of `@chakra-ui/menu`.",
|
||||
},
|
||||
{
|
||||
name: "next/navigation",
|
||||
importNames: ["useRouter"],
|
||||
message:
|
||||
'Use `import { useDashboardRouter } from "@/lib/DashboardRouter";` instead',
|
||||
},
|
||||
{
|
||||
name: "lucide-react",
|
||||
importNames: ["Link", "Table", "Sidebar"],
|
||||
message:
|
||||
'This is likely a mistake. If you really want to import this - postfix the imported name with Icon. Example - "LinkIcon"',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
parser: "@typescript-eslint/parser",
|
||||
plugins: ["@typescript-eslint", "react-compiler"],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2019,
|
||||
ecmaFeatures: {
|
||||
impliedStrict: true,
|
||||
jsx: true,
|
||||
},
|
||||
warnOnUnsupportedTypeScriptVersion: true,
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
createClass: "createReactClass",
|
||||
pragma: "React",
|
||||
version: "detect",
|
||||
},
|
||||
},
|
||||
overrides: [
|
||||
// allow direct PostHog imports inside analytics helpers
|
||||
// disable restricted imports in tw-components
|
||||
{
|
||||
files: "src/@/analytics/**/*",
|
||||
files: "src/tw-components/**/*",
|
||||
rules: {
|
||||
"no-restricted-imports": ["off"],
|
||||
},
|
||||
@@ -29,8 +151,8 @@ module.exports = {
|
||||
{
|
||||
files: ["*test.ts?(x)"],
|
||||
rules: {
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
|
||||
"react/display-name": "off",
|
||||
},
|
||||
@@ -55,119 +177,14 @@ module.exports = {
|
||||
"import/newline-after-import": "off",
|
||||
},
|
||||
},
|
||||
// turn OFF unused vars via eslint
|
||||
{
|
||||
files: ["*.ts", "*.tsx"],
|
||||
rules: {
|
||||
"@next/next/no-img-element": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
},
|
||||
},
|
||||
// THIS NEEDS TO GO LAST!
|
||||
{
|
||||
extends: ["biome"],
|
||||
files: ["*.ts", "*.js", "*.tsx", "*.jsx"],
|
||||
extends: ["biome"],
|
||||
},
|
||||
],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
impliedStrict: true,
|
||||
jsx: true,
|
||||
},
|
||||
ecmaVersion: 2019,
|
||||
warnOnUnsupportedTypeScriptVersion: true,
|
||||
},
|
||||
plugins: ["@typescript-eslint", "react-compiler"],
|
||||
rules: {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
paths: [
|
||||
{
|
||||
importNames: ["useRouter"],
|
||||
message:
|
||||
'Use `import { useDashboardRouter } from "@/lib/DashboardRouter";` instead',
|
||||
name: "next/navigation",
|
||||
},
|
||||
{
|
||||
message:
|
||||
'Import "posthog-js" directly only within the analytics helpers ("src/@/analytics/*"). Use the exported helpers from "@/analytics/track" elsewhere.',
|
||||
name: "posthog-js",
|
||||
},
|
||||
{
|
||||
importNames: ["useSendTransaction"],
|
||||
message:
|
||||
'Use `import { useSendAndConfirmTx } from "@/hooks/useSendTx";` instead',
|
||||
name: "thirdweb/react",
|
||||
},
|
||||
{
|
||||
importNames: ["useSendAndConfirmTransaction"],
|
||||
message:
|
||||
'Use `import { useSendAndConfirmTx } from "@/hooks/useSendTx";` instead',
|
||||
name: "thirdweb/react",
|
||||
},
|
||||
{
|
||||
importNames: ["sendTransaction"],
|
||||
message:
|
||||
'Use `import { useSendAndConfirmTx } from "@/hooks/useSendTx";` instead if used in react component',
|
||||
name: "thirdweb",
|
||||
},
|
||||
{
|
||||
importNames: ["sendAndConfirmTransaction"],
|
||||
message:
|
||||
'Use `import { useSendAndConfirmTx } from "@/hooks/useSendTx";` instead if used in react component',
|
||||
name: "thirdweb",
|
||||
},
|
||||
],
|
||||
patterns: [
|
||||
{
|
||||
group: ["**/../@/**"],
|
||||
message: "Use absolute imports instead. Example: '@/foo/bar..'",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
message:
|
||||
'Are you *sure* you need to use "useEffect" here? If you loading any async function prefer using "useQuery".',
|
||||
selector: "CallExpression[callee.name='useEffect']",
|
||||
},
|
||||
{
|
||||
message:
|
||||
'Are you *sure* you need to use a "Context"? In almost all cases you should prefer passing props directly.',
|
||||
selector: "CallExpression[callee.name='createContext']",
|
||||
},
|
||||
{
|
||||
message:
|
||||
"Use useV5DashboardChain instead if you are using it inside a component",
|
||||
selector: "CallExpression[callee.name='defineChain']",
|
||||
},
|
||||
{
|
||||
message:
|
||||
"Use useV5DashboardChain instead if you are using it inside a component",
|
||||
selector: "CallExpression[callee.name='defineDashboardChain']",
|
||||
},
|
||||
{
|
||||
message:
|
||||
"Use useV5DashboardChain instead if you are using it inside a component",
|
||||
selector: "CallExpression[callee.name='mapV4ChainToV5Chain']",
|
||||
},
|
||||
{
|
||||
message:
|
||||
"resolveScheme can throw error if resolution fails. Either catch the error and ignore the lint warning or Use `resolveSchemeWithErrorHandler` / `replaceIpfsUrl` utility in dashboard instead",
|
||||
selector: "CallExpression[callee.name='resolveScheme']",
|
||||
},
|
||||
],
|
||||
"react-compiler/react-compiler": "error",
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
createClass: "createReactClass",
|
||||
pragma: "React",
|
||||
version: "detect",
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -13,16 +13,19 @@ const config: StorybookConfig = {
|
||||
addons: [
|
||||
getAbsolutePath("@storybook/addon-onboarding"),
|
||||
getAbsolutePath("@storybook/addon-links"),
|
||||
getAbsolutePath("@storybook/addon-essentials"),
|
||||
getAbsolutePath("@chromatic-com/storybook"),
|
||||
getAbsolutePath("@storybook/addon-docs"),
|
||||
getAbsolutePath("@storybook/addon-interactions"),
|
||||
],
|
||||
framework: {
|
||||
name: getAbsolutePath("@storybook/nextjs"),
|
||||
options: {},
|
||||
},
|
||||
staticDirs: ["../public"],
|
||||
features: {
|
||||
experimentalRSC: true,
|
||||
refs: {
|
||||
"@chakra-ui/react": {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
staticDirs: ["../public"],
|
||||
};
|
||||
export default config;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { Preview } from "@storybook/nextjs";
|
||||
import "@workspace/ui/global.css";
|
||||
import type { Preview } from "@storybook/react";
|
||||
import "@/styles/globals.css";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { Button } from "@workspace/ui/components/button";
|
||||
import { MoonIcon, SunIcon } from "lucide-react";
|
||||
import { Inter as interFont } from "next/font/google";
|
||||
import { ThemeProvider, useTheme } from "next-themes";
|
||||
// biome-ignore lint/style/useImportType: ok
|
||||
import React, { useEffect } from "react";
|
||||
import { Toaster } from "sonner";
|
||||
import { Inter as interFont } from "next/font/google";
|
||||
// biome-ignore lint/style/useImportType: <explanation>
|
||||
import React from "react";
|
||||
import { useEffect } from "react";
|
||||
import { Button } from "../src/@/components/ui/button";
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
@@ -16,30 +16,8 @@ const fontSans = interFont({
|
||||
variable: "--font-sans",
|
||||
});
|
||||
|
||||
const customViewports = {
|
||||
xs: {
|
||||
// Regular sized phones (iphone 15 / 15 pro)
|
||||
name: "iPhone",
|
||||
styles: {
|
||||
width: "390px",
|
||||
height: "844px",
|
||||
},
|
||||
},
|
||||
sm: {
|
||||
// Larger phones (iphone 15 plus / 15 pro max)
|
||||
name: "iPhone Plus",
|
||||
styles: {
|
||||
width: "430px",
|
||||
height: "932px",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
viewport: {
|
||||
viewports: customViewports,
|
||||
},
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
@@ -68,7 +46,9 @@ const preview: Preview = {
|
||||
|
||||
export default preview;
|
||||
|
||||
function StoryLayout(props: { children: React.ReactNode }) {
|
||||
function StoryLayout(props: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const { setTheme, theme } = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -77,30 +57,29 @@ function StoryLayout(props: { children: React.ReactNode }) {
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<div className="flex min-h-dvh min-w-0 flex-col bg-background text-foreground">
|
||||
<div className="fixed right-0 bottom-0 z-50 flex justify-end gap-2 p-4">
|
||||
<div className="flex min-h-screen min-w-0 flex-col bg-background text-foreground">
|
||||
<div className="flex justify-end gap-2 border-b p-4">
|
||||
<Button
|
||||
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
|
||||
onClick={() => setTheme("dark")}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="h-auto w-auto shrink-0 rounded-full p-2"
|
||||
variant={theme === "dark" ? "default" : "outline"}
|
||||
className="h-auto w-auto rounded-full p-2"
|
||||
>
|
||||
{theme === "dark" ? (
|
||||
<MoonIcon className="size-4" />
|
||||
) : (
|
||||
<SunIcon className="size-4" />
|
||||
)}
|
||||
<MoonIcon className="size-4" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={() => setTheme("light")}
|
||||
size="sm"
|
||||
variant={theme === "light" ? "default" : "outline"}
|
||||
className="h-auto w-auto rounded-full p-2"
|
||||
>
|
||||
<SunIcon className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex min-w-0 grow flex-col">{props.children}</div>
|
||||
<ToasterSetup />
|
||||
</div>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function ToasterSetup() {
|
||||
const { theme } = useTheme();
|
||||
return <Toaster richColors theme={theme === "light" ? "light" : "dark"} />;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.0.6/schema.json",
|
||||
"extends": "//"
|
||||
"$schema": "https://biomejs.dev/schemas/1.9.2/schema.json",
|
||||
"extends": ["../../biome.json"],
|
||||
"overrides": [
|
||||
{
|
||||
"include": ["src/css/swagger-ui.css"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"suspicious": {
|
||||
"noImportantInKeyframe": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { defineConfig } from "checkly";
|
||||
import { Frequency } from "checkly/constructs";
|
||||
|
||||
export default defineConfig({
|
||||
projectName: "thirdweb.com",
|
||||
logicalId: "thirdweb-www",
|
||||
repoUrl: "https://github.com/thirdweb-dev/dashboard",
|
||||
checks: {
|
||||
activated: true,
|
||||
muted: false,
|
||||
runtimeId: "2023.09",
|
||||
frequency: Frequency.EVERY_24H,
|
||||
locations: ["us-east-1", "eu-west-1"],
|
||||
tags: ["website"],
|
||||
checkMatch: "./**/*.check.ts",
|
||||
ignoreDirectoriesMatch: [],
|
||||
playwrightConfig: {
|
||||
use: {
|
||||
baseURL: process.env.ENVIRONMENT_URL || "https://thirdweb.com",
|
||||
},
|
||||
},
|
||||
browserChecks: {
|
||||
frequency: Frequency.EVERY_24H,
|
||||
testMatch: "./tests/**/*.spec.ts",
|
||||
},
|
||||
},
|
||||
cli: {
|
||||
runLocation: "eu-west-1",
|
||||
},
|
||||
});
|
||||
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils"
|
||||
},
|
||||
"rsc": true,
|
||||
"style": "default",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"baseColor": "neutral",
|
||||
"config": "tailwind.config.js",
|
||||
"css": "src/global.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"tsx": true
|
||||
}
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
}
|
||||
@@ -6,33 +6,26 @@ module.exports = [
|
||||
"/bounties",
|
||||
"/contact-us",
|
||||
// -- product landing pages --
|
||||
// -- build category
|
||||
"/wallets",
|
||||
"/account-abstraction",
|
||||
"/payments",
|
||||
"/x402",
|
||||
"/nexus",
|
||||
"/auth",
|
||||
"/in-app-wallets",
|
||||
"/transactions",
|
||||
// -- end build category
|
||||
|
||||
// -- scale category
|
||||
"/rpc",
|
||||
// -- connect
|
||||
"/connect",
|
||||
"/connect/sign-in",
|
||||
"/connect/pay",
|
||||
// -- nebula
|
||||
"/nebula",
|
||||
// --insight
|
||||
"/insight",
|
||||
"/storage",
|
||||
// -- end scale category
|
||||
|
||||
// -- ai
|
||||
"/ai",
|
||||
// -- contracts
|
||||
"/contracts",
|
||||
"/contracts/modular-contracts",
|
||||
"/contracts/explore",
|
||||
"/contracts/deployment-tool",
|
||||
|
||||
// -- engine
|
||||
"/engine",
|
||||
// -- solutions pages --
|
||||
"/solutions/:solution_slug",
|
||||
"/solutions/gaming",
|
||||
"/solutions/consumer-apps",
|
||||
"/solutions/defi",
|
||||
"/solutions/ecosystem",
|
||||
// -- campaigns --
|
||||
// "/unlimited-wallets", -- OFF for now
|
||||
// -- TPP --
|
||||
@@ -45,26 +38,8 @@ module.exports = [
|
||||
"/community/ambassadors",
|
||||
"/community/startup-program",
|
||||
// -- grants --
|
||||
"/grants",
|
||||
"/superchain",
|
||||
"/grant/superchain",
|
||||
// -- templates --
|
||||
"/templates",
|
||||
"/templates/:template_slug",
|
||||
// -- learn --
|
||||
"/learn",
|
||||
"/learn/guides",
|
||||
"/learn/guides/:guide_slug",
|
||||
"/learn/courses",
|
||||
"/learn/glossary",
|
||||
"/learn/glossary/:glossary_slug",
|
||||
// -- faucets --
|
||||
"/faucets",
|
||||
// -- brand kit --
|
||||
"/brand-kit",
|
||||
// -- universal bridge landing pages --
|
||||
"/universal-bridge-regions/:region_slug",
|
||||
"/enterprise",
|
||||
"/token",
|
||||
"/vault",
|
||||
"/monetize/bridge",
|
||||
];
|
||||
|
||||
@@ -1,27 +1,13 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/knip@5/schema.json",
|
||||
"next": true,
|
||||
"ignore": [
|
||||
"src/@/components/ui/**",
|
||||
"src/@/components/misc/AnnouncementBanner.tsx",
|
||||
"src/@/components/cmd-k-search/index.tsx",
|
||||
"src/@/lib/search.ts"
|
||||
"src/components/notices/AnnouncementBanner.tsx",
|
||||
"src/components/cmd-k-search/index.tsx",
|
||||
"src/lib/search.ts"
|
||||
],
|
||||
"ignoreBinaries": ["only-allow"],
|
||||
"ignoreDependencies": [
|
||||
"@thirdweb-dev/api",
|
||||
"@thirdweb-dev/service-utils",
|
||||
"@thirdweb-dev/vault-sdk",
|
||||
"thirdweb",
|
||||
"@types/color",
|
||||
"fast-xml-parser",
|
||||
"@workspace/ui",
|
||||
"tailwindcss-animate",
|
||||
"@radix-ui/react-tooltip",
|
||||
"shiki",
|
||||
"react-children-utilities",
|
||||
"react-markdown",
|
||||
"remark-gfm"
|
||||
],
|
||||
"next": true,
|
||||
"project": ["src/**"]
|
||||
"project": ["src/**"],
|
||||
"ignoreBinaries": ["only-allow", "biome"],
|
||||
"ignoreDependencies": ["@storybook/blocks", "@thirdweb-dev/service-utils"]
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
declare module "lucide-react" {
|
||||
export * from "lucide-react/dist/lucide-react.suffixed";
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
// @ts-check
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { XMLParser } = require("fast-xml-parser");
|
||||
|
||||
/**
|
||||
* @returns {Promise<Array<{chainId: number, name: string, slug: string}>>}
|
||||
*
|
||||
*/
|
||||
async function fetchChainsFromApi() {
|
||||
const res = await fetch("https://api.thirdweb.com/v1/chains", {
|
||||
@@ -44,37 +42,12 @@ async function getSingleChain(chainIdOrSlug) {
|
||||
|
||||
/** @type {import('next-sitemap').IConfig} */
|
||||
module.exports = {
|
||||
additionalPaths: async (config) => {
|
||||
const [framerUrls, allChains] = await Promise.all([
|
||||
getFramerXML(),
|
||||
fetchChainsFromApi(),
|
||||
]);
|
||||
|
||||
return [
|
||||
...framerUrls.map((url) => {
|
||||
return {
|
||||
changefreq: config.changefreq,
|
||||
lastmod: config.autoLastmod ? new Date().toISOString() : undefined,
|
||||
loc: url.loc,
|
||||
priority: config.priority,
|
||||
};
|
||||
}),
|
||||
...allChains.map((chain) => {
|
||||
return {
|
||||
changefreq: config.changefreq,
|
||||
lastmod: config.autoLastmod ? new Date().toISOString() : undefined,
|
||||
loc: `/${chain.slug}`,
|
||||
priority: config.priority,
|
||||
};
|
||||
}),
|
||||
...(await createSearchRecordSitemaps(config)),
|
||||
];
|
||||
},
|
||||
exclude: ["/chain/validate"],
|
||||
siteUrl: process.env.SITE_URL || "https://thirdweb.com",
|
||||
generateRobotsTxt: true,
|
||||
robotsTxtOptions: {
|
||||
policies: [
|
||||
{
|
||||
userAgent: "*",
|
||||
// allow all if production
|
||||
allow: process.env.VERCEL_ENV === "production" ? ["/"] : [],
|
||||
// disallow all if not production
|
||||
@@ -83,11 +56,10 @@ module.exports = {
|
||||
? ["/"]
|
||||
: // disallow `/team` and `/team/*` if production
|
||||
["/team", "/team/*"],
|
||||
userAgent: "*",
|
||||
},
|
||||
],
|
||||
},
|
||||
siteUrl: process.env.SITE_URL || "https://thirdweb.com",
|
||||
exclude: ["/chain/validate"],
|
||||
transform: async (config, _path) => {
|
||||
let path = _path;
|
||||
|
||||
@@ -101,14 +73,36 @@ module.exports = {
|
||||
path = path.replace("deployer.thirdweb.eth", "thirdweb.eth");
|
||||
}
|
||||
return {
|
||||
alternateRefs: config.alternateRefs ?? [],
|
||||
changefreq: config.changefreq,
|
||||
lastmod: config.autoLastmod ? new Date().toISOString() : undefined,
|
||||
// => this will be exported as http(s)://<config.siteUrl>/<path>
|
||||
loc: path,
|
||||
changefreq: config.changefreq,
|
||||
priority: config.priority,
|
||||
lastmod: config.autoLastmod ? new Date().toISOString() : undefined,
|
||||
alternateRefs: config.alternateRefs ?? [],
|
||||
};
|
||||
},
|
||||
additionalPaths: async (config) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const FRAMER_PATHS = require("./framer-rewrites");
|
||||
const allChains = await fetchChainsFromApi();
|
||||
return [
|
||||
...FRAMER_PATHS.map((path) => ({
|
||||
loc: path,
|
||||
changefreq: config.changefreq,
|
||||
priority: config.priority,
|
||||
lastmod: config.autoLastmod ? new Date().toISOString() : undefined,
|
||||
})),
|
||||
...allChains.map((chain) => {
|
||||
return {
|
||||
loc: `/${chain.slug}`,
|
||||
changefreq: config.changefreq,
|
||||
priority: config.priority,
|
||||
lastmod: config.autoLastmod ? new Date().toISOString() : undefined,
|
||||
};
|
||||
}),
|
||||
...(await createSearchRecordSitemaps(config)),
|
||||
];
|
||||
},
|
||||
};
|
||||
/**
|
||||
* @param {{ changefreq?: any; priority?: any; autoLastmod?: any; }} config
|
||||
@@ -131,10 +125,10 @@ async function createSearchRecordSitemaps(config) {
|
||||
parsedLines.map((parsedLine) => {
|
||||
return getSingleChain(parsedLine.chain_id)
|
||||
.then((parsedLineChain) => ({
|
||||
changefreq: config.changefreq,
|
||||
lastmod: config.autoLastmod ? new Date().toISOString() : undefined,
|
||||
loc: `/${parsedLineChain.slug}/${parsedLine.contract_address}`,
|
||||
changefreq: config.changefreq,
|
||||
priority: config.priority,
|
||||
lastmod: config.autoLastmod ? new Date().toISOString() : undefined,
|
||||
}))
|
||||
.catch(() => null);
|
||||
}),
|
||||
@@ -142,19 +136,3 @@ async function createSearchRecordSitemaps(config) {
|
||||
// filter out any failed requests
|
||||
return chainsForLines.filter(Boolean);
|
||||
}
|
||||
|
||||
async function getFramerXML() {
|
||||
const framerSiteMapText = await fetch(
|
||||
"https://landing.thirdweb.com/sitemap.xml",
|
||||
).then((res) => res.text());
|
||||
|
||||
const parser = new XMLParser();
|
||||
const xmlObject = parser.parse(framerSiteMapText);
|
||||
|
||||
/**
|
||||
* @type {Array<{loc: string}>}
|
||||
*/
|
||||
const urls = xmlObject.urlset.url;
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@ const ContentSecurityPolicy = `
|
||||
img-src * data: blob:;
|
||||
media-src * data: blob:;
|
||||
object-src 'none';
|
||||
style-src 'self' 'unsafe-inline' vercel.live us.posthog.com;
|
||||
style-src 'self' 'unsafe-inline' vercel.live;
|
||||
font-src 'self' vercel.live assets.vercel.com framerusercontent.com fonts.gstatic.com;
|
||||
frame-src * data:;
|
||||
script-src 'self' 'unsafe-eval' 'unsafe-inline' 'wasm-unsafe-eval' 'inline-speculation-rules' *.thirdweb.com *.thirdweb-dev.com vercel.live js.stripe.com framerusercontent.com events.framer.com challenges.cloudflare.com googletagmanager.com us-assets.i.posthog.com edit.framer.com framer.com googletagmanager.com;
|
||||
script-src 'self' 'unsafe-eval' 'unsafe-inline' 'wasm-unsafe-eval' 'inline-speculation-rules' *.thirdweb.com *.thirdweb-dev.com vercel.live js.stripe.com framerusercontent.com events.framer.com challenges.cloudflare.com;
|
||||
connect-src * data: blob:;
|
||||
worker-src 'self' blob:;
|
||||
block-all-mixed-content;
|
||||
@@ -46,88 +46,90 @@ function determineIpfsGateways() {
|
||||
const remotePatterns: RemotePattern[] = [];
|
||||
if (process.env.API_ROUTES_CLIENT_ID) {
|
||||
remotePatterns.push({
|
||||
protocol: "https",
|
||||
hostname: `${process.env.API_ROUTES_CLIENT_ID}.ipfscdn.io`,
|
||||
protocol: "https",
|
||||
});
|
||||
remotePatterns.push({
|
||||
protocol: "https",
|
||||
hostname: `${process.env.API_ROUTES_CLIENT_ID}.thirdwebstorage-staging.com`,
|
||||
protocol: "https",
|
||||
});
|
||||
remotePatterns.push({
|
||||
hostname: `${process.env.API_ROUTES_CLIENT_ID}.thirdwebstorage-dev.com`,
|
||||
protocol: "https",
|
||||
hostname: `${process.env.API_ROUTES_CLIENT_ID}.thirdwebstorage-dev.com`,
|
||||
});
|
||||
} else {
|
||||
// this should only happen in development
|
||||
remotePatterns.push({
|
||||
hostname: "ipfs.io",
|
||||
protocol: "https",
|
||||
hostname: "ipfs.io",
|
||||
});
|
||||
}
|
||||
// also add the dashboard clientId ipfs gateway if it is set
|
||||
if (process.env.NEXT_PUBLIC_DASHBOARD_CLIENT_ID) {
|
||||
remotePatterns.push({
|
||||
protocol: "https",
|
||||
hostname: `${process.env.NEXT_PUBLIC_DASHBOARD_CLIENT_ID}.ipfscdn.io`,
|
||||
protocol: "https",
|
||||
});
|
||||
remotePatterns.push({
|
||||
protocol: "https",
|
||||
hostname: `${process.env.NEXT_PUBLIC_DASHBOARD_CLIENT_ID}.thirdwebstorage-staging.com`,
|
||||
protocol: "https",
|
||||
});
|
||||
remotePatterns.push({
|
||||
hostname: `${process.env.NEXT_PUBLIC_DASHBOARD_CLIENT_ID}.thirdwebstorage-dev.com`,
|
||||
protocol: "https",
|
||||
hostname: `${process.env.NEXT_PUBLIC_DASHBOARD_CLIENT_ID}.thirdwebstorage-dev.com`,
|
||||
});
|
||||
}
|
||||
return remotePatterns;
|
||||
}
|
||||
|
||||
const SENTRY_OPTIONS: SentryBuildOptions = {
|
||||
// For all available options, see:
|
||||
// https://github.com/getsentry/sentry-webpack-plugin#options
|
||||
|
||||
org: "thirdweb-dev",
|
||||
project: "dashboard",
|
||||
// An auth token is required for uploading source maps.
|
||||
authToken: process.env.SENTRY_AUTH_TOKEN,
|
||||
// Suppresses source map uploading logs during build
|
||||
silent: true,
|
||||
// For all available options, see:
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
|
||||
|
||||
// Upload a larger set of source maps for prettier stack traces (increases build time)
|
||||
widenClientFileUpload: true,
|
||||
|
||||
// Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers (increases server load)
|
||||
tunnelRoute: "/err",
|
||||
|
||||
// Hides source maps from generated client bundles
|
||||
hideSourceMaps: true,
|
||||
|
||||
// Automatically tree-shake Sentry logger statements to reduce bundle size
|
||||
disableLogger: true,
|
||||
|
||||
// Enables automatic instrumentation of Vercel Cron Monitors.
|
||||
// See the following for more information:
|
||||
// https://docs.sentry.io/product/crons/
|
||||
// https://vercel.com/docs/cron-jobs
|
||||
automaticVercelMonitors: false,
|
||||
|
||||
// Automatically tree-shake Sentry logger statements to reduce bundle size
|
||||
disableLogger: true,
|
||||
// For all available options, see:
|
||||
// https://github.com/getsentry/sentry-webpack-plugin#options
|
||||
|
||||
org: "thirdweb-dev",
|
||||
project: "dashboard",
|
||||
// Suppresses source map uploading logs during build
|
||||
silent: true,
|
||||
|
||||
// Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers (increases server load)
|
||||
tunnelRoute: "/err",
|
||||
// For all available options, see:
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
|
||||
|
||||
// Upload a larger set of source maps for prettier stack traces (increases build time)
|
||||
widenClientFileUpload: true,
|
||||
};
|
||||
|
||||
// add additional languages to the framer rewrite paths here (english is already included by default)
|
||||
const FRAMER_ADDITIONAL_LANGUAGES = ["es"];
|
||||
|
||||
const baseNextConfig: NextConfig = {
|
||||
transpilePackages: ["@workspace/ui"],
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
productionBrowserSourceMaps: false,
|
||||
experimental: {
|
||||
serverSourceMaps: false,
|
||||
taint: true,
|
||||
webpackBuildWorker: true,
|
||||
webpackMemoryOptimizations: true,
|
||||
serverSourceMaps: false,
|
||||
},
|
||||
serverExternalPackages: ["pino-pretty"],
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
// Apply these headers to all routes in your application.
|
||||
source: "/(.*)",
|
||||
headers: [
|
||||
...securityHeaders,
|
||||
{
|
||||
@@ -135,68 +137,48 @@ const baseNextConfig: NextConfig = {
|
||||
value: "sec-ch-viewport-width",
|
||||
},
|
||||
],
|
||||
// Apply these headers to all routes in your application.
|
||||
source: "/(.*)",
|
||||
},
|
||||
];
|
||||
},
|
||||
images: {
|
||||
contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",
|
||||
dangerouslyAllowSVG: true,
|
||||
remotePatterns: [
|
||||
{
|
||||
hostname: "**.thirdweb.com",
|
||||
protocol: "https",
|
||||
},
|
||||
...determineIpfsGateways(),
|
||||
],
|
||||
},
|
||||
productionBrowserSourceMaps: false,
|
||||
reactStrictMode: true,
|
||||
async redirects() {
|
||||
return getRedirects();
|
||||
},
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
destination: "https://us-assets.i.posthog.com/static/:path*",
|
||||
source: "/_ph/static/:path*",
|
||||
},
|
||||
{
|
||||
destination: "https://us.i.posthog.com/:path*",
|
||||
source: "/_ph/:path*",
|
||||
},
|
||||
{
|
||||
destination: "https://us.i.posthog.com/decide",
|
||||
source: "/_ph/decide",
|
||||
},
|
||||
{
|
||||
destination: "/deployer.thirdweb.eth",
|
||||
source: "/thirdweb.eth",
|
||||
destination: "/deployer.thirdweb.eth",
|
||||
},
|
||||
{
|
||||
destination: "/deployer.thirdweb.eth/:path*",
|
||||
source: "/thirdweb.eth/:path*",
|
||||
destination: "/deployer.thirdweb.eth/:path*",
|
||||
},
|
||||
// re-write /home to / (this is so that logged in users will be able to go to /home and NOT be redirected to the logged in app)
|
||||
{
|
||||
destination: "https://landing.thirdweb.com",
|
||||
source: "/home",
|
||||
destination: "/",
|
||||
},
|
||||
// flatmap the framer paths for the default language and the additional languages
|
||||
...FRAMER_PATHS.flatMap((path) => [
|
||||
{
|
||||
destination: `https://landing.thirdweb.com${path}`,
|
||||
source: path,
|
||||
},
|
||||
// this is for additional languages
|
||||
...FRAMER_ADDITIONAL_LANGUAGES.map((lang) => ({
|
||||
destination: `https://landing.thirdweb.com/${lang}${path}`,
|
||||
source: `/${lang}${path}`,
|
||||
})),
|
||||
]),
|
||||
...FRAMER_PATHS.map((path) => ({
|
||||
source: path,
|
||||
destination: `https://landing.thirdweb.com${path}`,
|
||||
})),
|
||||
];
|
||||
},
|
||||
images: {
|
||||
dangerouslyAllowSVG: true,
|
||||
contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "**.thirdweb.com",
|
||||
},
|
||||
...determineIpfsGateways(),
|
||||
],
|
||||
},
|
||||
compiler: {
|
||||
emotion: true,
|
||||
},
|
||||
reactStrictMode: true,
|
||||
};
|
||||
|
||||
function getConfig(): NextConfig {
|
||||
@@ -205,29 +187,36 @@ function getConfig(): NextConfig {
|
||||
const withBundleAnalyzer = require("@next/bundle-analyzer")({
|
||||
enabled: process.env.ANALYZE === "true",
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { withPlausibleProxy } = require("next-plausible");
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { withSentryConfig } = require("@sentry/nextjs");
|
||||
return withBundleAnalyzer(
|
||||
withSentryConfig(
|
||||
{
|
||||
...baseNextConfig,
|
||||
// @ts-expect-error - this is a valid option
|
||||
webpack: (config) => {
|
||||
if (config.cache) {
|
||||
config.cache = Object.freeze({
|
||||
type: "memory",
|
||||
});
|
||||
}
|
||||
config.module = {
|
||||
...config.module,
|
||||
exprContextCritical: false,
|
||||
};
|
||||
// Important: return the modified config
|
||||
return config;
|
||||
withPlausibleProxy({
|
||||
customDomain: "https://pl.thirdweb.com",
|
||||
scriptName: "pl",
|
||||
})(
|
||||
withSentryConfig(
|
||||
{
|
||||
...baseNextConfig,
|
||||
// @ts-expect-error - this is a valid option
|
||||
webpack: (config) => {
|
||||
if (config.cache) {
|
||||
config.cache = Object.freeze({
|
||||
type: "memory",
|
||||
});
|
||||
}
|
||||
config.externals.push("pino-pretty");
|
||||
config.module = {
|
||||
...config.module,
|
||||
exprContextCritical: false,
|
||||
};
|
||||
// Important: return the modified config
|
||||
return config;
|
||||
},
|
||||
},
|
||||
},
|
||||
SENTRY_OPTIONS,
|
||||
SENTRY_OPTIONS,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,131 +1,144 @@
|
||||
{
|
||||
"name": "thirdweb-dashboard",
|
||||
"version": "3.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"dev": "next dev --turbo",
|
||||
"build": "NODE_OPTIONS=--max-old-space-size=6144 next build",
|
||||
"start": "next start",
|
||||
"format": "biome format ./src --write",
|
||||
"lint": "biome check ./src && knip && eslint ./src",
|
||||
"fix": "biome check ./src --fix && eslint ./src --fix",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"gen:theme-typings": "chakra-cli tokens src/theme/index.ts",
|
||||
"postinstall": "pnpm run gen:theme-typings",
|
||||
"postbuild": "next-sitemap",
|
||||
"build:analyze": "ANALYZE=true pnpm run build",
|
||||
"knip": "knip",
|
||||
"playwright": "playwright test",
|
||||
"update-checkly": "npx checkly deploy",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@chakra-ui/react": "^2.8.2",
|
||||
"@chakra-ui/styled-system": "^2.9.2",
|
||||
"@chakra-ui/theme-tools": "^2.1.2",
|
||||
"@emotion/react": "11.14.0",
|
||||
"@emotion/styled": "11.14.0",
|
||||
"@hookform/resolvers": "^3.9.1",
|
||||
"@marsidev/react-turnstile": "^1.1.0",
|
||||
"@number-flow/react": "^0.5.10",
|
||||
"@radix-ui/react-accordion": "^1.2.11",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.14",
|
||||
"@radix-ui/react-avatar": "^1.1.10",
|
||||
"@radix-ui/react-checkbox": "^1.3.2",
|
||||
"@radix-ui/react-collapsible": "^1.1.11",
|
||||
"@radix-ui/react-dialog": "1.1.14",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
||||
"@radix-ui/react-hover-card": "^1.1.14",
|
||||
"@radix-ui/react-label": "^2.1.7",
|
||||
"@radix-ui/react-popover": "^1.1.14",
|
||||
"@radix-ui/react-progress": "^1.1.7",
|
||||
"@radix-ui/react-radio-group": "^1.3.7",
|
||||
"@radix-ui/react-select": "^2.2.5",
|
||||
"@radix-ui/react-separator": "^1.1.7",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"@radix-ui/react-switch": "^1.2.5",
|
||||
"@radix-ui/react-tooltip": "1.2.7",
|
||||
"@sentry/nextjs": "9.34.0",
|
||||
"@shazow/whatsabi": "0.22.2",
|
||||
"@stripe/react-stripe-js": "4.0.2",
|
||||
"@stripe/stripe-js": "7.9.0",
|
||||
"@tanstack/react-query": "5.81.5",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@thirdweb-dev/api": "workspace:*",
|
||||
"@n8tb1t/use-scroll-position": "^2.0.3",
|
||||
"@radix-ui/react-accordion": "^1.2.2",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.5",
|
||||
"@radix-ui/react-avatar": "^1.1.2",
|
||||
"@radix-ui/react-checkbox": "^1.1.3",
|
||||
"@radix-ui/react-dialog": "1.1.5",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.5",
|
||||
"@radix-ui/react-hover-card": "^1.1.5",
|
||||
"@radix-ui/react-label": "^2.1.1",
|
||||
"@radix-ui/react-popover": "^1.1.5",
|
||||
"@radix-ui/react-progress": "^1.1.1",
|
||||
"@radix-ui/react-radio-group": "^1.2.2",
|
||||
"@radix-ui/react-select": "^2.1.5",
|
||||
"@radix-ui/react-separator": "^1.1.1",
|
||||
"@radix-ui/react-slot": "^1.1.1",
|
||||
"@radix-ui/react-switch": "^1.1.2",
|
||||
"@radix-ui/react-tooltip": "1.1.7",
|
||||
"@sentry/nextjs": "8.51.0",
|
||||
"@shazow/whatsabi": "^0.19.0",
|
||||
"@tanstack/react-query": "5.65.0",
|
||||
"@tanstack/react-table": "^8.20.6",
|
||||
"@thirdweb-dev/service-utils": "workspace:*",
|
||||
"@thirdweb-dev/vault-sdk": "workspace:*",
|
||||
"@vercel/functions": "2.2.2",
|
||||
"@vercel/og": "^0.6.8",
|
||||
"@workspace/ui": "workspace:*",
|
||||
"@vercel/functions": "^1.5.2",
|
||||
"@vercel/og": "^0.6.5",
|
||||
"abitype": "1.0.8",
|
||||
"chakra-react-select": "^4.7.6",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"color": "^4.2.3",
|
||||
"compare-versions": "^6.1.0",
|
||||
"date-fns": "4.1.0",
|
||||
"fast-xml-parser": "^5.2.5",
|
||||
"fetch-event-stream": "0.1.5",
|
||||
"fuse.js": "7.1.0",
|
||||
"flat": "^6.0.1",
|
||||
"framer-motion": "12.0.6",
|
||||
"fuse.js": "7.0.0",
|
||||
"input-otp": "^1.4.1",
|
||||
"ioredis": "^5.6.1",
|
||||
"ioredis": "^5.4.1",
|
||||
"ipaddr.js": "^2.2.0",
|
||||
"lucide-react": "0.525.0",
|
||||
"next": "15.3.5",
|
||||
"next-themes": "^0.4.6",
|
||||
"lucide-react": "0.474.0",
|
||||
"next": "15.1.6",
|
||||
"next-plausible": "^3.12.4",
|
||||
"next-themes": "^0.4.4",
|
||||
"nextjs-toploader": "^1.6.12",
|
||||
"nuqs": "^2.4.3",
|
||||
"papaparse": "^5.5.3",
|
||||
"openapi-types": "^12.1.3",
|
||||
"p-limit": "^6.2.0",
|
||||
"papaparse": "^5.5.1",
|
||||
"pluralize": "^8.0.0",
|
||||
"posthog-js": "1.256.1",
|
||||
"prettier": "3.6.2",
|
||||
"react": "19.1.0",
|
||||
"posthog-js": "1.67.1",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "19.0.0",
|
||||
"react-children-utilities": "^2.10.0",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "19.1.0",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-error-boundary": "6.0.0",
|
||||
"react-hook-form": "7.55.0",
|
||||
"react-markdown": "10.1.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-dropzone": "^14.3.5",
|
||||
"react-error-boundary": "^4.1.2",
|
||||
"react-hook-form": "7.54.2",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-table": "^7.8.0",
|
||||
"recharts": "2.15.3",
|
||||
"remark-gfm": "4.0.1",
|
||||
"responsive-rsc": "0.0.7",
|
||||
"recharts": "2.15.1",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"server-only": "^0.0.1",
|
||||
"shiki": "3.12.0",
|
||||
"sonner": "2.0.6",
|
||||
"shiki": "1.27.0",
|
||||
"sonner": "^1.7.2",
|
||||
"spdx-correct": "^3.2.0",
|
||||
"stripe": "17.7.0",
|
||||
"swagger-ui-react": "^5.24.1",
|
||||
"swagger-ui-react": "^5.18.2",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"thirdweb": "workspace:*",
|
||||
"tiny-invariant": "^1.3.3",
|
||||
"use-debounce": "^10.0.5",
|
||||
"vaul": "^1.1.2",
|
||||
"zod": "3.25.75"
|
||||
"use-debounce": "^10.0.4",
|
||||
"zod": "3.24.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.0.6",
|
||||
"@chromatic-com/storybook": "4.0.1",
|
||||
"@next/bundle-analyzer": "15.3.5",
|
||||
"@next/eslint-plugin-next": "15.3.5",
|
||||
"@storybook/addon-docs": "9.0.15",
|
||||
"@storybook/addon-links": "9.0.15",
|
||||
"@storybook/addon-onboarding": "9.0.15",
|
||||
"@storybook/nextjs": "9.0.15",
|
||||
"@chakra-ui/cli": "^2.4.1",
|
||||
"@chromatic-com/storybook": "3.2.4",
|
||||
"@next/bundle-analyzer": "15.1.6",
|
||||
"@next/eslint-plugin-next": "15.1.6",
|
||||
"@playwright/test": "1.50.0",
|
||||
"@storybook/addon-essentials": "8.5.2",
|
||||
"@storybook/addon-interactions": "8.5.2",
|
||||
"@storybook/addon-links": "8.5.2",
|
||||
"@storybook/addon-onboarding": "8.5.2",
|
||||
"@storybook/addon-viewport": "8.5.2",
|
||||
"@storybook/blocks": "8.5.2",
|
||||
"@storybook/nextjs": "8.5.2",
|
||||
"@storybook/react": "8.5.2",
|
||||
"@storybook/test": "8.5.2",
|
||||
"@types/color": "4.2.0",
|
||||
"@types/node": "22.14.1",
|
||||
"@types/papaparse": "^5.3.16",
|
||||
"@types/node": "22.10.10",
|
||||
"@types/papaparse": "^5.3.15",
|
||||
"@types/pluralize": "^0.0.33",
|
||||
"@types/react": "19.1.8",
|
||||
"@types/react-dom": "19.1.6",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react": "19.0.8",
|
||||
"@types/react-dom": "19.0.3",
|
||||
"@types/react-table": "^7.7.20",
|
||||
"@types/spdx-correct": "^3.1.3",
|
||||
"@types/swagger-ui-react": "^5.18.0",
|
||||
"@types/swagger-ui-react": "^4.19.0",
|
||||
"@typescript-eslint/eslint-plugin": "7.14.1",
|
||||
"@typescript-eslint/parser": "7.14.1",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"checkly": "^4.18.0",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-config-biome": "1.9.4",
|
||||
"eslint-plugin-react-compiler": "19.1.0-rc.2",
|
||||
"eslint-plugin-storybook": "9.0.15",
|
||||
"knip": "5.60.2",
|
||||
"eslint-plugin-react-compiler": "19.0.0-beta-df7b47d-20241124",
|
||||
"eslint-plugin-storybook": "^0.11.1",
|
||||
"knip": "5.43.5",
|
||||
"next-sitemap": "^4.2.3",
|
||||
"postcss": "8.5.6",
|
||||
"storybook": "9.0.15",
|
||||
"postcss": "8.5.1",
|
||||
"storybook": "8.5.2",
|
||||
"tailwindcss": "3.4.17",
|
||||
"typescript": "5.8.3"
|
||||
},
|
||||
"name": "thirdweb-dashboard",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "NODE_OPTIONS=--max-old-space-size=6144 next build",
|
||||
"build-storybook": "storybook build",
|
||||
"build:analyze": "ANALYZE=true pnpm run build",
|
||||
"dev": "next dev --turbopack",
|
||||
"fix": "biome check ./src --fix && eslint ./src --fix",
|
||||
"format": "biome format ./src --write",
|
||||
"knip": "knip",
|
||||
"lint": "biome check ./src && knip && eslint ./src",
|
||||
"postbuild": "next-sitemap",
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"start": "next start",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"version": "3.0.0"
|
||||
"typescript": "5.7.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: "./tests",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "html",
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: process.env.ENVIRONMENT_URL || "http://127.0.0.1:3000",
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
},
|
||||
|
||||
{
|
||||
name: "firefox",
|
||||
use: { ...devices["Desktop Firefox"] },
|
||||
},
|
||||
|
||||
{
|
||||
name: "webkit",
|
||||
use: { ...devices["Desktop Safari"] },
|
||||
},
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: { ...devices['Pixel 5'] },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: { ...devices['iPhone 12'] },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: process.env.CI
|
||||
? undefined
|
||||
: {
|
||||
command: "pnpm run start",
|
||||
url: "http://127.0.0.1:3000",
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {},
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from "@workspace/ui/postcss.config";
|
||||
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 248 KiB |
|
After Width: | Height: | Size: 228 KiB |
|
After Width: | Height: | Size: 281 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 251 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 53 KiB |
@@ -0,0 +1,2 @@
|
||||
name,description,image,animation_url,external_url,background_color,youtube_url,additional,properties,can be,added
|
||||
Token 0 Name,Token 0 Description,ipfs://ipfsHash/0,ipfs://ipfsHash/0,https://thirdweb.com,#0098EE,,every,row,is a ,property
|
||||
|
@@ -0,0 +1,4 @@
|
||||
name,description,image,animation_url,external_url,background_color,youtube_url,additional,properties,can be,added
|
||||
Token 0 Name,Token 0 Description,0.png,0.mp4,https://thirdweb.com,#0098EE,,every,row,is a ,property
|
||||
Token 1 Name,Token 1 Description,0.png,0.mp4,https://thirdweb.com,#0098EE,,every,row,is a ,property
|
||||
Token 2 Name,Token 2 Description,0.png,0.mp4,https://thirdweb.com,#0098EE,,every,row,is a ,property
|
||||
|
@@ -0,0 +1,2 @@
|
||||
name,description,external_url,background_color,youtube_url,additional,properties,can be,added
|
||||
Token 0 Name,Token 0 Description,https://thirdweb.com,#0098EE,,every,row,is a ,property
|
||||
|
@@ -0,0 +1,3 @@
|
||||
address,maxClaimable
|
||||
0x0000000000000000000000000000000000000000,2
|
||||
0x000000000000000000000000000000000000dEaD,5
|
||||
|
@@ -0,0 +1,3 @@
|
||||
address,maxClaimable,price,currencyAddress
|
||||
0x0000000000000000000000000000000000000000,2,0.1,0x0000000000000000000000000000000000000000
|
||||
0x000000000000000000000000000000000000dEaD,5,2.5,0x0000000000000000000000000000000000000000
|
||||
|
@@ -0,0 +1,3 @@
|
||||
address
|
||||
0x0000000000000000000000000000000000000000
|
||||
0x000000000000000000000000000000000000dEaD
|
||||
|
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 119 KiB |
@@ -0,0 +1,6 @@
|
||||
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Check Circle Icon">
|
||||
<path id="Vector" d="M8.5 16.5C12.9184 16.5 16.5 12.9184 16.5 8.5C16.5 4.0816 12.9184 0.5 8.5 0.5C4.0816 0.5 0.5 4.0816 0.5 8.5C0.5 12.9184 4.0816 16.5 8.5 16.5Z" fill="white"/>
|
||||
<path id="Check" d="M4.87939 9.01716L6.9482 11.086L12.1202 5.91394" stroke="#000001" stroke-width="1.23077" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 470 B |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 395 KiB |
|
After Width: | Height: | Size: 336 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 158 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 244 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 302 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 217 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 47 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.64456 7.23942C3.52534 6.70242 3.54365 6.14402 3.69777 5.61599C3.8519 5.08796 4.13685 4.60739 4.52621 4.21884C4.91558 3.83029 5.39674 3.54634 5.9251 3.39333C6.45345 3.24031 7.01189 3.22317 7.54863 3.34351C7.84406 2.88147 8.25104 2.50124 8.73207 2.23786C9.21309 1.97448 9.75268 1.83643 10.3011 1.83643C10.8495 1.83643 11.3891 1.97448 11.8701 2.23786C12.3511 2.50124 12.7581 2.88147 13.0535 3.34351C13.5911 3.22265 14.1505 3.23971 14.6797 3.3931C15.2089 3.54649 15.6907 3.83122 16.0803 4.22083C16.4699 4.61043 16.7546 5.09223 16.908 5.62143C17.0614 6.15063 17.0785 6.71002 16.9576 7.24758C17.4197 7.54301 17.7999 7.94999 18.0633 8.43102C18.3266 8.91204 18.4647 9.45163 18.4647 10C18.4647 10.5484 18.3266 11.088 18.0633 11.5691C17.7999 12.0501 17.4197 12.4571 16.9576 12.7525C17.078 13.2892 17.0608 13.8477 16.9078 14.376C16.7548 14.9044 16.4708 15.3855 16.0823 15.7749C15.6937 16.1643 15.2132 16.4492 14.6851 16.6034C14.1571 16.7575 13.5987 16.7758 13.0617 16.6566C12.7667 17.1204 12.3594 17.5022 11.8775 17.7668C11.3957 18.0314 10.8549 18.1701 10.3052 18.1701C9.75547 18.1701 9.21466 18.0314 8.73282 17.7668C8.25097 17.5022 7.84367 17.1204 7.54863 16.6566C7.01189 16.7769 6.45345 16.7598 5.9251 16.6068C5.39674 16.4537 4.91558 16.1698 4.52621 15.7812C4.13685 15.3927 3.8519 14.9121 3.69777 14.3841C3.54365 13.8561 3.52534 13.2977 3.64456 12.7607C3.17897 12.466 2.79547 12.0584 2.52973 11.5757C2.26399 11.0931 2.12463 10.551 2.12463 10C2.12463 9.44905 2.26399 8.90702 2.52973 8.42435C2.79547 7.94168 3.17897 7.53407 3.64456 7.23942Z" stroke="white" stroke-width="1.6335" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7.85083 9.99972L9.48433 11.6332L12.7513 8.36621" stroke="white" stroke-width="1.6335" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 583 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 167 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 202 KiB |
|
After Width: | Height: | Size: 626 KiB |
|
After Width: | Height: | Size: 208 KiB |
|
After Width: | Height: | Size: 577 KiB |
|
After Width: | Height: | Size: 187 KiB |
|
After Width: | Height: | Size: 204 KiB |
|
After Width: | Height: | Size: 314 KiB |