Compare commits

..
Author SHA1 Message Date
Joaquim VergesGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
1cff849b69 Version Packages (#6089)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-28 18:22:36 -08:00
Jonas DanielsandGitHub b5e327e5ec add billingPlanVersion to TeamResponse (#6084) 2025-01-28 18:20:57 -08:00
5917 changed files with 133762 additions and 306797 deletions
+9 -15
View File
@@ -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"
}
}
-128
View File
@@ -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);
}
```
+4 -4
View File
@@ -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
+4 -5
View File
@@ -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());
+22 -33
View File
@@ -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.0
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,19 +169,16 @@ 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
uses: andresz1/size-limit-action@94bc357df29c36c8f8d50ea497c3e225c3c95d1d # v1.8.0
uses: andresz1/size-limit-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
package_manager: pnpm
+1 -1
View File
@@ -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]
+4 -4
View File
@@ -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
+49
View File
@@ -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]}`);
+1 -1
View File
@@ -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 }}"
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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 -1
View File
@@ -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`);
+1 -4
View File
@@ -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
-3
View File
@@ -1,3 +0,0 @@
public-hoist-pattern[]=*import-in-the-middle*
public-hoist-pattern[]=*require-in-the-middle*
public-hoist-pattern[]=*pino-pretty*
+16 -21
View File
@@ -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"
]
}
-160
View File
@@ -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.
+6 -3
View File
@@ -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,6 +11,9 @@
<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>
@@ -28,12 +31,12 @@
## Library Comparison
| | thirdweb | Wagmi + Viem | Ethers@6 |
| | thirdweb | Wagmi + Viem | Ethers@6 |
| ----------------------------------------- | -------- | ------------------ | -------- |
| Type safe contract API | ✅ | ✅ | ✅ |
| Type safe wallet API | ✅ | ✅ | ✅ |
| EVM utils | ✅ | ✅ | ✅ |
| RPC for any EVM | ✅  | ⚠️ public RPC only | ❌ |
| RPC for any EVM | ✅  | ⚠️ public RPC only | ❌ |
| Automatic ABI Resolution | ✅ | ❌ | ❌ |
| IPFS Upload/Download | ✅ | ❌ | ❌ |
| Embedded wallet (email/ social login) | ✅ | ⚠️ via 3rd party | ❌ |
+12 -12
View File
@@ -6,14 +6,10 @@
# not required to build, defaults to prod
NEXT_PUBLIC_THIRDWEB_DOMAIN="localhost:3000"
# API host. For local development, please use "https://api.thirdweb-dev.com"
# API host. For local development, please use "https://api.thirdweb-preview.com"
# otherwise: "https://api.thirdweb.com"
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"
# Paper API host
NEXT_PUBLIC_THIRDWEB_EWS_API_HOST="https://ews.thirdweb-dev.com"
@@ -44,6 +40,9 @@ NEXT_PUBLIC_STRIPE_KEY=
NEXT_PUBLIC_STRIPE_PAYMENT_METHOD_CFG_ID=
# Needed for contract analytics / blockchain data information
CHAINSAW_API_KEY=
#
# Private (server)
#
@@ -62,11 +61,17 @@ MORALIS_API_KEY=
# - 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"
@@ -91,10 +96,5 @@ REDIS_URL=""
ANALYTICS_SERVICE_URL=""
# required for billing parts of the dashboard (team -> settings -> billing / invoices)
STRIPE_SECRET_KEY=""
# required for server wallet management
NEXT_PUBLIC_THIRDWEB_VAULT_URL=""
NEXT_PUBLIC_ENGINE_CLOUD_URL=""
# Required for Nebula Chat
NEXT_PUBLIC_NEBULA_URL=""
+106 -126
View File
@@ -1,97 +1,51 @@
module.exports = {
env: {
browser: true,
node: true,
},
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@next/next/recommended",
"plugin:storybook/recommended",
],
overrides: [
// disable restricted imports in tw-components
{
files: "src/tw-components/**/*",
rules: {
"no-restricted-imports": ["off"],
},
},
// allow direct PostHog imports inside analytics helpers
{
files: "src/@/analytics/**/*",
rules: {
"no-restricted-imports": ["off"],
},
},
// enable rule specifically for TypeScript files
{
files: ["*.ts", "*.tsx"],
rules: {
"@typescript-eslint/explicit-module-boundary-types": ["off"],
},
},
// in test files, allow null assertions and anys and eslint is sometimes weird about the react-scope thing
{
files: ["*test.ts?(x)"],
rules: {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"react/display-name": "off",
},
},
// allow requires in non-transpiled JS files and logical key ordering in config files
{
files: [
"babel-node.js",
"*babel.config.js",
"env.config.js",
"next.config.js",
"webpack.config.js",
"packages/mobile-web/package-builder/**",
],
rules: {},
},
// setupTests can have separated imports for logical grouping
{
files: ["setupTests.ts"],
rules: {
"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"],
},
],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaFeatures: {
impliedStrict: true,
jsx: true,
},
ecmaVersion: 2019,
warnOnUnsupportedTypeScriptVersion: true,
},
plugins: ["@typescript-eslint", "react-compiler"],
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",
@@ -128,77 +82,47 @@ module.exports = {
],
message:
'Use the equivalent component from "tw-components" instead.',
name: "@chakra-ui/react",
},
{
name: "@chakra-ui/layout",
message:
"Import from `@chakra-ui/react` instead of `@chakra-ui/layout`.",
name: "@chakra-ui/layout",
},
{
name: "@chakra-ui/button",
message:
"Import from `@chakra-ui/react` instead of `@chakra-ui/button`.",
name: "@chakra-ui/button",
},
{
name: "@chakra-ui/menu",
message:
"Import from `@chakra-ui/react` instead of `@chakra-ui/menu`.",
name: "@chakra-ui/menu",
},
{
name: "next/navigation",
importNames: ["useRouter"],
message:
'Use `import { useDashboardRouter } from "@/lib/DashboardRouter";` instead',
name: "next/navigation",
},
{
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"',
name: "lucide-react",
},
{
message:
'Import "posthog-js" directly only within the analytics helpers ("src/@/analytics/*"). Use the exported helpers from "@/analytics/track" elsewhere.',
name: "posthog-js",
},
],
},
],
"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",
},
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint", "react-compiler"],
parserOptions: {
ecmaVersion: 2019,
ecmaFeatures: {
impliedStrict: true,
jsx: true,
},
warnOnUnsupportedTypeScriptVersion: true,
},
settings: {
react: {
@@ -207,4 +131,60 @@ module.exports = {
version: "detect",
},
},
overrides: [
// disable restricted imports in tw-components
{
files: "src/tw-components/**/*",
rules: {
"no-restricted-imports": ["off"],
},
},
// enable rule specifically for TypeScript files
{
files: ["*.ts", "*.tsx"],
rules: {
"@typescript-eslint/explicit-module-boundary-types": ["off"],
},
},
// in test files, allow null assertions and anys and eslint is sometimes weird about the react-scope thing
{
files: ["*test.ts?(x)"],
rules: {
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-explicit-any": "off",
"react/display-name": "off",
},
},
// allow requires in non-transpiled JS files and logical key ordering in config files
{
files: [
"babel-node.js",
"*babel.config.js",
"env.config.js",
"next.config.js",
"webpack.config.js",
"packages/mobile-web/package-builder/**",
],
rules: {},
},
// setupTests can have separated imports for logical grouping
{
files: ["setupTests.ts"],
rules: {
"import/newline-after-import": "off",
},
},
// THIS NEEDS TO GO LAST!
{
files: ["*.ts", "*.js", "*.tsx", "*.jsx"],
extends: ["biome"],
},
],
env: {
browser: true,
node: true,
},
};
+2 -4
View File
@@ -13,8 +13,9 @@ 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"),
@@ -26,8 +27,5 @@ const config: StorybookConfig = {
},
},
staticDirs: ["../public"],
features: {
experimentalRSC: true,
},
};
export default config;
+17 -41
View File
@@ -1,5 +1,5 @@
import type { Preview } from "@storybook/nextjs";
import "../src/global.css";
import type { Preview } from "@storybook/react";
import "@/styles/globals.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { MoonIcon, SunIcon } from "lucide-react";
import { ThemeProvider, useTheme } from "next-themes";
@@ -7,7 +7,6 @@ import { Inter as interFont } from "next/font/google";
// biome-ignore lint/style/useImportType: <explanation>
import React from "react";
import { useEffect } from "react";
import { Toaster } from "sonner";
import { Button } from "../src/@/components/ui/button";
const queryClient = new QueryClient();
@@ -17,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,
@@ -80,30 +57,29 @@ function StoryLayout(props: {
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"} />;
}
+14 -2
View File
@@ -1,4 +1,16 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.0/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"
}
}
}
}
]
}
+13 -13
View File
@@ -2,29 +2,29 @@ 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,
browserChecks: {
frequency: Frequency.EVERY_24H,
testMatch: "./tests/**/*.spec.ts",
},
checkMatch: "./**/*.check.ts",
frequency: Frequency.EVERY_24H,
ignoreDirectoriesMatch: [],
locations: ["us-east-1", "eu-west-1"],
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",
},
},
runtimeId: "2023.09",
tags: ["website"],
browserChecks: {
frequency: Frequency.EVERY_24H,
testMatch: "./tests/**/*.spec.ts",
},
},
cli: {
runLocation: "eu-west-1",
},
logicalId: "thirdweb-www",
projectName: "thirdweb.com",
repoUrl: "https://github.com/thirdweb-dev/dashboard",
});
+8 -8
View File
@@ -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"
}
}
+11 -28
View File
@@ -6,16 +6,10 @@ module.exports = [
"/bounties",
"/contact-us",
// -- product landing pages --
// -- build category
"/wallets",
"/account-abstraction",
"/universal-bridge",
"/auth",
"/in-app-wallets",
"/transactions",
// -- end build category
// -- storage
"/storage",
// -- connect
"/connect",
"/connect/sign-in",
"/connect/pay",
// -- nebula
"/nebula",
// --insight
@@ -25,9 +19,13 @@ module.exports = [
"/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 --
@@ -40,23 +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",
];
+4 -9
View File
@@ -1,18 +1,13 @@
{
"$schema": "https://unpkg.com/knip@5/schema.json",
"next": true,
"ignore": [
"src/@/components/ui/**",
"src/components/notices/AnnouncementBanner.tsx",
"src/components/cmd-k-search/index.tsx",
"src/lib/search.ts"
],
"ignoreBinaries": ["only-allow"],
"ignoreDependencies": [
"@thirdweb-dev/service-utils",
"@thirdweb-dev/vault-sdk",
"@types/color",
"fast-xml-parser"
],
"next": true,
"project": ["src/**"]
"project": ["src/**"],
"ignoreBinaries": ["only-allow", "biome"],
"ignoreDependencies": ["@storybook/blocks", "@thirdweb-dev/service-utils"]
}
-3
View File
@@ -1,3 +0,0 @@
declare module "lucide-react" {
export * from "lucide-react/dist/lucide-react.suffixed";
}
+31 -53
View File
@@ -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;
}
+57 -77
View File
@@ -12,7 +12,7 @@ const ContentSecurityPolicy = `
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;
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,90 +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 = {
compiler: {
emotion: true,
},
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,
{
@@ -137,69 +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}`,
})),
];
},
serverExternalPackages: ["pino-pretty"],
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 {
@@ -227,6 +206,7 @@ function getConfig(): NextConfig {
type: "memory",
});
}
config.externals.push("pino-pretty");
config.module = {
...config.module,
exprContextCritical: false,
+96 -96
View File
@@ -1,4 +1,26 @@
{
"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",
@@ -7,138 +29,116 @@
"@emotion/styled": "11.14.0",
"@hookform/resolvers": "^3.9.1",
"@marsidev/react-turnstile": "^1.1.0",
"@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-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",
"@scalar/api-reference-react": "^0.6.19",
"@sentry/nextjs": "9.29.0",
"@shazow/whatsabi": "0.22.2",
"@tanstack/react-query": "5.80.7",
"@tanstack/react-table": "^8.21.3",
"@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.1.0",
"@vercel/og": "^0.6.8",
"@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": "5.0.0",
"color": "^4.2.3",
"compare-versions": "^6.1.0",
"date-fns": "4.1.0",
"fast-xml-parser": "^5.2.5",
"framer-motion": "12.17.0",
"fuse.js": "7.1.0",
"fetch-event-stream": "0.1.5",
"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.514.0",
"next": "15.3.3",
"lucide-react": "0.474.0",
"next": "15.1.6",
"next-plausible": "^3.12.4",
"next-themes": "^0.4.6",
"next-themes": "^0.4.4",
"nextjs-toploader": "^1.6.12",
"nuqs": "^2.4.3",
"openapi-types": "^12.1.3",
"p-limit": "^6.2.0",
"papaparse": "^5.5.3",
"papaparse": "^5.5.1",
"pluralize": "^8.0.0",
"posthog-js": "1.252.0",
"prettier": "3.5.3",
"posthog-js": "1.67.1",
"qrcode": "^1.5.3",
"react": "19.1.0",
"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": "^5.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": "1.27.0",
"sonner": "2.0.5",
"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.62"
"use-debounce": "^10.0.4",
"zod": "3.24.1"
},
"devDependencies": {
"@biomejs/biome": "2.0.0",
"@chakra-ui/cli": "^2.4.1",
"@chromatic-com/storybook": "4.0.0",
"@next/bundle-analyzer": "15.3.3",
"@next/eslint-plugin-next": "15.3.3",
"@playwright/test": "1.53.0",
"@storybook/addon-docs": "9.0.8",
"@storybook/addon-links": "9.0.8",
"@storybook/addon-onboarding": "9.0.8",
"@storybook/nextjs": "9.0.8",
"@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/qrcode": "^1.5.5",
"@types/react": "19.1.8",
"@types/react-dom": "19.1.6",
"@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",
"checkly": "5.6.0",
"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.8",
"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.5",
"storybook": "9.0.8",
"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",
"gen:theme-typings": "chakra-cli tokens src/theme/index.ts",
"knip": "knip",
"lint": "biome check ./src && knip && eslint ./src",
"playwright": "playwright test",
"postbuild": "next-sitemap",
"postinstall": "pnpm run gen:theme-typings",
"preinstall": "npx only-allow pnpm",
"start": "next start",
"storybook": "storybook dev -p 6006",
"typecheck": "tsc --noEmit",
"update-checkly": "npx checkly deploy"
},
"version": "3.0.0"
"typescript": "5.7.3"
}
}
+18 -18
View File
@@ -10,10 +10,25 @@ import { defineConfig, devices } from "@playwright/test";
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
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: [
@@ -52,28 +67,13 @@ export default defineConfig({
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: "html",
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
testDir: "./tests",
/* 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",
},
/* Run your local dev server before starting the tests */
webServer: process.env.CI
? undefined
: {
command: "pnpm run start",
reuseExistingServer: !process.env.CI,
url: "http://127.0.0.1:3000",
reuseExistingServer: !process.env.CI,
},
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
});
+1 -1
View File
@@ -1,6 +1,6 @@
module.exports = {
plugins: {
autoprefixer: {},
tailwindcss: {},
autoprefixer: {},
},
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

@@ -0,0 +1,14 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5 12.9509C16.8924 12.9509 17.2105 13.269 17.2105 13.6614V13.9693C17.6641 14.0614 18.0863 14.2399 18.4592 14.4869L18.8397 14.1064C19.1172 13.8289 19.567 13.8289 19.8445 14.1064C20.122 14.3839 20.122 14.8337 19.8445 15.1112L19.464 15.4917C19.711 15.8646 19.8895 16.2868 19.9815 16.7404H20.2895C20.6819 16.7404 21 17.0585 21 17.4509C21 17.8433 20.6819 18.1614 20.2895 18.1614H19.9815C19.8895 18.615 19.711 19.0372 19.464 19.4101L19.8445 19.7906C20.122 20.0681 20.122 20.518 19.8445 20.7954C19.567 21.0729 19.1172 21.0729 18.8397 20.7954L18.4592 20.4149C18.0863 20.6619 17.6641 20.8404 17.2105 20.9324V21.2404C17.2105 21.6328 16.8924 21.9509 16.5 21.9509C16.1076 21.9509 15.7895 21.6328 15.7895 21.2404V20.9324C15.3359 20.8404 14.9137 20.6619 14.5408 20.4149L14.1603 20.7954C13.8828 21.0729 13.4329 21.0729 13.1555 20.7954C12.878 20.518 12.878 20.0681 13.1555 19.7906L13.536 19.4101C13.289 19.0372 13.1105 18.615 13.0184 18.1614H12.7105C12.3181 18.1614 12 17.8433 12 17.4509C12 17.0585 12.3181 16.7404 12.7105 16.7404H13.0184C13.1105 16.2868 13.289 15.8646 13.536 15.4917L13.1555 15.1112C12.878 14.8337 12.878 14.3839 13.1555 14.1064C13.4329 13.8289 13.8828 13.8289 14.1603 14.1064L14.5408 14.4869C14.9137 14.2399 15.3359 14.0614 15.7895 13.9693V13.6614C15.7895 13.269 16.1076 12.9509 16.5 12.9509Z" fill="#9C30B7"/>
<path d="M5 18.4516C3.89543 18.4516 3 17.5562 3 16.4516L3 5.45163C3 4.34706 3.89543 3.45163 5 3.45163L16 3.45163C17.1046 3.45163 18 4.34706 18 5.45163L18 16.4516C18 17.5562 17.1046 18.4516 16 18.4516L5 18.4516Z" fill="url(#paint0_linear_618_1490)" fill-opacity="0.8"/>
<path d="M6.5 8.45163H14.5" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.5 7.45163V9.45163" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.5 13.4516H14.5" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.5 12.4516V14.4516" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<defs>
<linearGradient id="paint0_linear_618_1490" x1="3" y1="10.9516" x2="18" y2="10.9516" gradientUnits="userSpaceOnUse">
<stop stop-color="#E5A1F5"/>
<stop offset="1" stop-color="#E169FF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

Some files were not shown because too many files have changed in this diff Show More