Compare commits

..
Author SHA1 Message Date
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
0345492e99 Version Packages (#7433)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-26 03:01:11 -07:00
gregandGitHub e650434061 [SDK] Fix: Chain name label (#7442) 2025-06-25 15:47:17 -07:00
gregandGitHub 6712911950 [SDK] Chore: Remove onrampTokenAddress set to ETH (#7441) 2025-06-25 14:22:20 -07:00
orbuloandGitHub 1387337955 [SDK] Feature: update hey-api version to 0.76.0 (#7431) 2025-06-25 12:30:12 -07:00
Phillip HoandGitHub a2b737e144 fix: typo on a field in webhook event helper (#7440) 2025-06-25 23:19:02 +08:00
Phillip HoandGitHub c5f9921dc9 feat: add webhook producer to service-utils (#7439) 2025-06-25 13:35:19 +08:00
Joaquim VergesandGitHub 830125c920 Add CLAUDE.md (#7438) 2025-06-24 19:11:06 -07:00
Joaquim VergesandGitHub af89836b6c [SDK] Handle sponsored gas on TransactionWidget (#7437) 2025-06-24 19:10:47 -07:00
MananTank d85ee5fac3 Dashboard: add "Contracts" link in contract layout header (#7436)
![image.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/yNOf1svJ8o3zjO7zQouZ/05f5e0d2-cfee-4604-8567-a630e13f8232.png)

<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new optional property, `currentProjectSubpath`, to enhance the `TeamHeader` components by allowing the display of additional project navigation details, including a label, href, and icon.

### Detailed summary
- Added `currentProjectSubpath` property to `TeamHeaderLoggedIn` and `TeamHeaderDesktopUI`.
- Updated `TeamHeaderUI` to render project subpath details if provided.
- Introduced a new story in `TeamHeaderUI.stories.tsx` to showcase the `currentProjectSubpath` feature.
- Integrated `currentProjectSubpath` into the `ContractLayout` component for enhanced navigation.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added support for displaying an active project subpage link with an icon in the team header, enhancing breadcrumb navigation for projects.
  - Updated the team header to visually indicate the current subpage within a project.

- **Documentation**
  - Added a new Storybook variant to demonstrate the team header with an active subpage link.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-24 23:37:56 +00:00
MananTank 8bb4d4bc11 Dashboard: remove date selector from UB FTUX, improved skeletons (#7435)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on refactoring the `Page` component in the `loading.tsx` file and the `PayAnalytics` component in the `PayAnalytics.tsx` file, enhancing the structure and user experience by modifying the rendering of analytics and loading states.

### Detailed summary
- Removed `ResponsiveSuspense` and `PayAnalyticsFilter` from `loading.tsx`.
- Integrated `PayAnalytics` directly into the `Page` component.
- Added `ResponsiveSuspense` with a skeleton loader in `PayAnalytics`.
- Reorganized analytics display in `PayAnalytics`, including filtering and layout adjustments.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Refactor**
  - Improved the loading experience in the PayAnalytics section with responsive skeleton placeholders during data loading.
  - Updated the layout to display analytics filters above the loading state for better usability.

- **Chores**
  - Removed unused loading and suspense logic from related pages to streamline the user interface.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-24 22:27:52 +00:00
MananTank 3c6668b6c8 [TOOL-4833] Add Manage Contract button in new public contract pages (#7434)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the handling of `projectSlug` and `teamSlug` properties across various components in the application, improving navigation and data management related to teams and projects.

### Detailed summary
- Added `projectSlug` and `teamSlug` props in several components including `cards.tsx`, `DeployedContractsPage.tsx`, and `layout.tsx`.
- Updated links to include `teamSlug` and `projectSlug` for better navigation.
- Introduced new API function `getContractImportedProjects` to fetch projects with imported contracts.
- Created `TeamSelectorCard` and `ProjectAndTeamSelectorCard` components for selecting teams and projects.
- Improved UI elements such as buttons and tooltips in `ContractHeaderUI` and `ImportModal`.
- Enhanced type definitions for better type safety and clarity in components.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a team and project selector UI, enabling users to easily select teams or projects from visual cards.
  - Added a project and team selector for associating contracts, allowing users to view, select, or import contracts into projects across teams.
  - Enhanced contract management by providing direct navigation to contract pages based on project selection.
  - Improved contract view links to include team and project information in URLs for clearer navigation.
  - Added functionality to fetch projects that have imported specific contracts within teams.

- **UI Improvements**
  - Updated button styles with rounded edges for a polished look.
  - Added copy address and manage contract buttons with icons and tooltips for enhanced usability.

- **Storybook/Documentation**
  - Added Storybook stories to visually test and demonstrate the new team and project selector components under various scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-24 21:58:48 +00:00
gregandGitHub 6aa2765a34 Feature: UB Widget Tracking and Docs (#7426) 2025-06-24 13:19:01 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
bbff67db0c Version Packages (#7414)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-23 20:31:12 -07:00
Prithvish BaidyaandGitHub ef2895fa2c Implement support for EOA execution options in server wallets (#7429) 2025-06-23 20:27:59 -07:00
Prithvish BaidyaandGitHub cf44dae9ed add eoa execution options type (#7428) 2025-06-23 19:51:33 -07:00
Joaquim VergesandGitHub c26be2d958 Fix wallet row alignment in wide connect modal (#7425) 2025-06-23 16:38:44 -07:00
MananTank 029741126f [TOOL-4856] Engine Server wallets table UI adjustment, Fix Vault Portal Link (#7419)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on enhancing the user interface in the dashboard by improving the handling of server wallets and updating the link for the Vault.

### Detailed summary
- Updated the `href` in the `Link` component to point to `https://portal.thirdweb.com/vault` and added a "Learn more about Vault" text.
- Refactored the rendering logic for wallets:
  - Removed the conditional rendering for "No server wallets found" and replaced it with a new div that displays this message when there are no wallets.
  - Streamlined the mapping of `wallets` to display each wallet's details within a `TableRow`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Bug Fixes**
	- Improved the display of the "No server wallets found" message, which now appears below the wallets table instead of inside it for better clarity.

- **Style**
	- Updated the "Learn more about Vault" link to display clear link text and corrected its URL for improved user navigation.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-23 23:14:31 +00:00
MananTank b5114dfe22 [TOOL-4858] Update Team Usage Overview page UI for free plan (#7423)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on improving the handling of team usage and upsell features in the dashboard. It introduces new components and logic to display upsell content based on the team's billing plan.

### Detailed summary
- Updated import paths for `getTeamBySlug` and `UpsellWrapper`.
- Added `UpsellContent` component to display upsell information.
- Implemented logic to show upsell content if the team is on a "free" plan.
- Refactored the `UpsellWrapper` to include `UpsellContent`.
- Enhanced rendering of benefits in `UpsellContent`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Refactor**
  - Improved code structure by extracting upsell card UI into a dedicated component, resulting in cleaner and more maintainable code.
  - Updated import paths for consistency and clarity.

- **Style**
  - Adjusted layout classes for better visual alignment of upsell content.

- **Bug Fixes**
  - Unified error handling for usage page, ensuring a consistent message for all error scenarios. Free plan users now see a clear upgrade prompt.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-23 23:02:36 +00:00
MananTank 66270f7933 Dashboard: group project sidebar links (#7417)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the sidebar layout by introducing new components for better organization and rendering of sidebar links. It refactors the sidebar structure to use `RenderSidebarMenu` and `RenderSidebarGroup`, allowing for grouped sidebar items.

### Detailed summary
- Added `SidebarGroupLabel` and `SidebarMenuSubItem` components.
- Refactored `FullWidthSidebarLayout` to use `RenderSidebarMenu` instead of `RenderSidebarGroup`.
- Updated `RenderSidebarGroup` to include `SidebarGroupLabel` and `SidebarMenuItem`.
- Introduced grouping for sidebar links with `group` and `links` properties.
- Enhanced sidebar link structure to support separators and improved organization.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Refactor**
  - Improved sidebar navigation by organizing links into hierarchical groups ("Build," "Monetize," "Scale") for better clarity and usability.
  - Updated sidebar rendering for greater modularity and maintainability, with enhanced grouping and link management.

- **Style**
  - Sidebar appearance and structure are updated for a more organized and user-friendly experience.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-24 02:42:29 +05:30
AmineAfia 11b4d9e780 Add support for tuple types in ABI event and function signatures (#7415)
# [Dashboard] Fix: Improve ABI type handling for tuple types in webhook signatures

## Notes for the reviewer
This PR adds support for properly handling tuple types in ABI event and function signatures. Previously, we were only extracting the basic type string, which doesn't correctly represent tuple structures. Now we recursively build the canonical type representation for tuples, including nested tuples and array dimensions.

## How to test
The changes can be tested by creating webhooks with contracts that use tuple types in their events or functions. The signature extraction should now correctly represent the canonical format for these complex types.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Bug Fixes**
  - Improved handling of tuple types in ABI inputs to ensure accurate event and function signature generation, especially for nested tuple structures.
- **New Features**
  - Function signature labels in transaction filters are now truncated for better readability without affecting underlying data.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily enhances the handling of function signatures in the `FilterDetailsStep.tsx` component and improves the extraction of ABI input types in `abiUtils.ts`. It introduces a helper function to manage tuple types and modifies how function signatures are displayed.

### Detailed summary
- In `FilterDetailsStep.tsx`, updated the label for function signatures to use `truncateMiddle(sig.name, 30, 15)` for better readability.
- Added a new helper function `getCanonicalType` in `abiUtils.ts` to recursively handle tuple types in ABI inputs.
- Modified the extraction of canonical input types in both `extractEventSignatures` and `extractFunctionSignatures` to use `getCanonicalType` instead of directly mapping the type.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-06-23 13:49:17 +00:00
kilavvyandGitHub ad5aabcb7c Docs: Fix typos in modular contracts guide (#7408)
Signed-off-by: kilavvy <[email protected]>
2025-06-22 18:12:29 -07:00
Joaquim VergesandGitHub 3107ba1c37 [SDK] Handle non-URLs for metadata images in payment widgets (#7413) 2025-06-22 17:54:33 -07:00
MananTank 897bdc75d5 Dashboard: Remove unused assets #2 (#7412)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on the removal of several image files from the project, specifically logos and images used in the dashboard and product pages.

### Detailed summary
- Deleted image files from `apps/dashboard/src/components/partners/logos/`:
  - `aws.png`
  - `xai.png`
  - `ztx.png`
  - `myna.png`
  - `nyfw.png`
  - `paima.png`
  - `layer3.png`
  - `mirror.png`
  - `pixels.png`
  - `torque.png`
  - `unlock.png`
  - `animoca.png`
  - `polygon.png`
  - `rarible.png`
  - `shopify.png`
  - `avacloud.png`
  - `coinbase.png`
  - `coolcats.png`
  - `paradigm.png`
  - `treasure.png`
  - `mcfarlane.png`
  - `aavegotchi.png`
  - `gala_games.png`
  - `infinigods.png`
- Deleted image files from `apps/dashboard/src/components/product-pages/homepage/multi-chain/`:
  - `mobile.svg`
  - `desktop.svg`
  - `mobile-gray.svg`
  - `desktop-gray.svg`
- Deleted `blur.png` from `apps/dashboard/src/components/product-pages/homepage/`

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-06-22 19:29:45 +00:00
MananTank 87c645d633 Dashboard: file organization refactor (no code change) (#7410)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->
2025-06-22 19:29:44 +00:00
MananTank 15b4b4b2f9 Dashboard: Remove unused files (#7409)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on the removal of several unused asset files and CSS modules from the `apps/dashboard` directory, streamlining the project's file structure.

### Detailed summary
- Deleted `misc.svg` from `apps/dashboard/public/assets/support/`
- Deleted `engine.png` from `apps/dashboard/public/assets/support/`
- Deleted `engine.png` from `apps/dashboard/public/assets/og-image/`
- Deleted `account.svg` from `apps/dashboard/public/assets/support/`
- Deleted `wallets.png` from `apps/dashboard/public/assets/support/`
- Deleted `contracts.png` from `apps/dashboard/public/assets/support/`
- Deleted `contracts.png` from `apps/dashboard/public/assets/og-image/`
- Deleted `engine.png` from `apps/dashboard/public/assets/product-icons/`
- Deleted `contracts.png` from `apps/dashboard/public/assets/landingpage/`
- Deleted `wallets.png` from `apps/dashboard/public/assets/product-icons/`
- Deleted `contracts.png` from `apps/dashboard/public/assets/product-icons/`
- Deleted `partner-logo.module.css` from `apps/dashboard/src/components/partners/`
- Deleted `categories.module.css` from `apps/dashboard/src/components/homepage/category/`

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Style**
  - Removed custom styles for homepage category components, affecting layout, interactivity, and responsiveness.
  - Removed marquee animation and spacing styles for partner logo elements.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-22 19:29:43 +00:00
arcoraven 18ad1afbde fix: bug with no results on storage page (#7411)
## [Dashboard] Fix: Add optional chaining to prevent undefined errors in YourFilesSection

## Notes for the reviewer

Added optional chaining to prevent potential undefined errors when accessing `pinnedFilesQuery.data.result` properties in the YourFilesSection component. This ensures the component handles cases where the data or result might be undefined more gracefully.

## How to test

Verify that the YourFilesSection component renders correctly when data is loading or when the API returns undefined values.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Bug Fixes**
	- Improved reliability when displaying pinned files by preventing errors if data is missing or incomplete.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-22 19:19:54 +00:00
e70e9b9fcc Fix comment for max claimable tokens test in drop721.test.ts (#7336)
Signed-off-by: fuder.eth <[email protected]>
Co-authored-by: Jonas Daniels <[email protected]>
2025-06-21 13:19:18 -07:00
Jonas DanielsandGitHub dfe913e2a9 Upgrade Biome from 2.0.0 to 2.0.4 (#7407) 2025-06-21 13:16:45 -07:00
Jonas DanielsandGitHub 5bc50eb339 [Dashboard] Replace Sentry Replay with PostHog integration (#7406) 2025-06-21 13:11:41 -07:00
jnsdls b61ce679e1 Move Sentry config to instrumentation-client.ts (#7405)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Integrated Sentry error monitoring and Session Replay for improved error tracking and diagnostics in the dashboard.
  - Enhanced filtering of irrelevant errors and noise from third-party scripts and browser extensions.

- **Chores**
  - Updated analytics setup to include Sentry alongside existing PostHog integration.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on integrating `Sentry` for error tracking and monitoring within the `instrumentation-client.ts` file, alongside an existing `PostHog` setup for analytics.

### Detailed summary
- Removed `sentry.client.config.ts`.
- Added `Sentry` initialization with configuration settings including `allowUrls`, `denyUrls`, and `ignoreErrors`.
- Configured `Sentry` for session replay with `integrations`, `replaysOnErrorSampleRate`, and `tracesSampleRate`.
- Exported `onRouterTransitionStart` for router transition tracking.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-06-21 19:22:40 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
56495a1217 Version Packages (#7404)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-21 11:45:49 -07:00
MananTank c9b4730ecb Dashboard: Team Overview page UI improvements (#7400)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on enhancing UI components in the dashboard application, including pagination buttons, team member invitations, and changelog displays. It also refines layout and styling for better user experience.

### Detailed summary
- Updated `pagination-buttons.stories.tsx` to correct page labels.
- Modified `InviteTeamMembersButton` styling for a more rounded appearance.
- Enhanced layout in `page.tsx` for better responsiveness and spacing.
- Changed `Changelog` component structure for improved visual hierarchy.
- Adjusted `TeamProjectsPage` to refine project display and sorting options.
- Replaced `Select` component with a `DropdownMenu` for sorting.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
	- Changelog section now displays feature images for each entry, with fallback icons if unavailable.
	- Changelog items are shown in a responsive grid layout for improved readability.
	- Changelog header includes a title, description, and "View All" button.

- **Enhancements**
	- Team projects page features a redesigned search input, improved sorting with a dropdown menu, and a more prominent subtitle.
	- "Create Project" and other action buttons have updated styling for better visibility.
	- Invite Team Members button now has a rounded appearance and enhanced background styling.
	- Dashboard layout is streamlined with sequential content flow and consistent maximum width for better usability.
	- Pagination controls updated with fully rounded styling for a modern look.
	- Pagination storybook examples corrected and expanded to accurately represent page counts.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-21 18:19:28 +00:00
MananTank ea3c702c4f Fix lint errors (#7401)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the `sidebar.tsx` file by modifying the `href` property of a menu item and enhancing `globals.css` to improve code styling, particularly for code spans in both light and dark themes.

### Detailed summary
- In `sidebar.tsx`, the `href` property for the menu item "Get Dedicated Support" was removed.
- In `globals.css`, new styles were added for `code span` to set colors for light and dark themes.
- Redundant `.shiki` styles were removed.
- Existing styles for autofilled inputs and button elements remain unchanged.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Style**
  - Consolidated and reorganized CSS rules for code highlighting to improve maintainability. No visual changes were made.

- **Refactor**
  - Adjusted the order of properties within the sidebar configuration for improved code consistency. No changes to functionality or user experience.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-21 18:19:28 +00:00
Jonas DanielsandGitHub 51798f09be [Changeset] Remove thirdweb-login from ignore list (#7403) 2025-06-21 11:19:01 -07:00
Jonas DanielsandGitHub 387a739544 [Changeset] Add nebula-app to ignore list and reorder config (#7402) 2025-06-21 11:15:56 -07:00
Phillip HoandGitHub 6fd630af1f docs: Add docs about dedicated support channel (#7399) 2025-06-21 12:13:34 +08:00
Phillip HoandGitHub ff6e2453d0 feat: dedicated support self-serve (#7381) 2025-06-21 10:46:25 +08:00
MananTank eeb672d092 [TOOL-4842] Dashboard navigation changes (#7395)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on restructuring and renaming various components and pages within the application, particularly transitioning terminology from "assets" to "tokens" and reorganizing file paths for clarity. It also includes the deletion of unused files and adjustments in imports.

### Detailed summary
- Deleted unused files related to `nebula` and `in-app-wallets`.
- Renamed components and pages from "assets" to "tokens".
- Updated file paths to reflect the new structure.
- Adjusted imports for consistency with new paths.
- Enhanced UI elements for better user experience.

> The following files were skipped due to too many changes: `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/cloud/vault/components/create-vault-account.client.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/contracts/layout.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/cloud/analytics/ftux.client.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(general)/layout.tsx`, `apps/dashboard/redirects.js`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/contract-webhooks/contract-webhooks-page.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/cloud/server-wallets/components/try-it-out.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/cloud/layout.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/layout.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/vault/components/key-management.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx`, `apps/dashboard/src/components/pay/PayAnalytics/PayEmbedFTUX.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectFTUX/ProjectFTUX.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/cloud/server-wallets/wallet-table/wallet-table-ui.client.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/page.tsx`, `packages/nebula/package.json`, `pnpm-lock.yaml`

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a Vault section for secure key management, including a new layout and key management UI.
  - Added contract webhooks management with improved pages and navigation.

- **Improvements**
  - Renamed "Assets" to "Tokens" and "In-App Wallets" to "Wallets" throughout the dashboard.
  - Updated sidebar navigation labels, icons, and link destinations for clarity and consistency.
  - Enhanced "Transactions" section, replacing previous "Engine" references and updating related routes and UI.
  - Refined onboarding and FTUX experiences with clearer terminology and improved instructional content.
  - Improved contract import and table UI with updated labels and empty state messages.
  - Enhanced Pay analytics with richer code examples and a more flexible FTUX component.
  - Streamlined redirects and updated URL paths to remove deprecated segments for smoother navigation.
  - Improved pagination display and active link detection in navigation components.
  - Adjusted layout spacing, button sizes, and typography for improved visual consistency.
  - Updated footer and sidebar styling for better user experience.

- **Bug Fixes**
  - Fixed various redirect and navigation paths to align with new route structures.
  - Improved handling of pagination and active link states in navigation components.

- **Removals**
  - Removed Nebula analytics pages, components, and related tracking utilities.
  - Deleted unused tracking properties and constants.
  - Removed outdated Vault key management component replaced by new implementation.

- **Documentation**
  - Updated terminology in documentation and UI to reflect the shift from "assets" to "tokens".
  - Revised analytics event descriptions to use "token page" terminology.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-21 00:36:00 +00:00
PaoloRollo 9667c74981 feat: add @thirdweb-dev/nebula package. (#7389)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->
This PR adds the new `@thirdweb-dev/nebula` package. It's an OpenAPI Typescript Wrapper for the new Nebula APIs.

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces the initial release of the `@thirdweb-dev/nebula` TypeScript SDK, providing a wrapper for the Nebula API with configuration options and client functionalities.

### Detailed summary
- Added `@thirdweb-dev/nebula` package with initial release.
- Created configuration functions: `configure`, `configureWithClient`, and `getNebulaClient`.
- Generated client and types using `@hey-api/openapi-ts`.
- Included comprehensive README with usage examples.
- Established TypeScript configuration files.

> The following files were skipped due to too many changes: `packages/nebula/src/client/types.gen.ts`

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced the Nebula TypeScript SDK as a new package, providing a client for interacting with the Nebula OpenAPI AI agent service.
  - Added configuration utilities for flexible authentication and integration with existing clients.
  - Exposed a comprehensive set of API methods for managing agents, conversations, models, and user data.
  - Extended build scripts to include the Nebula package in relevant workspace commands.

- **Documentation**
  - Added detailed README with setup instructions and usage examples for the Nebula SDK.

- **Chores**
  - Added configuration and build files to support development and compilation of the new package.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-20 23:32:30 +00:00
Jonas DanielsandGitHub 997beeac3a biome 2 (#7398) 2025-06-20 16:14:38 -07:00
gregandGitHub 9fec4ccec4 [SDK] Fix: Deprecate test mode (#7396) 2025-06-20 13:07:52 -07:00
gregandGitHub 9c420c0c9d [SDK] Feature: Update PayEmbed to use new widgets (#7394) 2025-06-20 12:50:06 -07:00
Jonas DanielsandGitHub ffea73c719 [Dashboard] Disable 7-day trial for growth plan (#7392) 2025-06-20 00:22:21 -07:00
Jonas DanielsandGitHub f56cc00187 [Dashboard] Remove phone login option and add info alert (#7391) 2025-06-20 00:09:21 -07:00
gregandGitHub 6be9459361 Refactor: UB widget inputs (#7390) 2025-06-19 16:47:38 -07:00
MananTank 492c6d0194 Dashboard: NFT asset page UI updates (#7388)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR enhances the `TokenInfoUI` component by adding a new claim condition check, improving the UI for displaying NFT purchase availability, and restructuring the layout for better clarity. It also introduces a tabbed interface for viewing traits and purchasing options for ERC1155 tokens.

### Detailed summary
- Added a new check for claim conditions in `TokenInfoUI`.
- Introduced a "Not available for purchase" message when no claim conditions are set.
- Implemented a tabbed interface for ERC1155 tokens to switch between traits and purchase options.
- Reorganized the layout of the NFT details section for improved readability.
- Adjusted spacing in the `BuyEditionDrop` form for consistency.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
	- Introduced a tabbed interface for ERC1155 tokens, allowing users to switch between viewing traits and purchasing options.
	- Added badges displaying token type, token ID, and chain metadata for improved clarity.

- **Improvements**
	- Enhanced handling of claim conditions, now clearly indicating when an NFT is not available for purchase.
	- Refined the display of NFT traits, showing a "No Traits" message if none exist.
	- Updated form and input styling for a cleaner and more consistent appearance.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-19 18:13:48 +00:00
Yash094 787118d367 feat: add berachain and berachain-bepolia chains (#7343)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces the `berachain` and `berachainBepolia` blockchain definitions to the `thirdweb` package, enhancing its chain support. It also updates the `chains` export file to include these new chains.

### Detailed summary
- Added `berachain` and `berachainBepolia` definitions in their respective files.
- Updated `packages/thirdweb/src/exports/chains.ts` to export `berachain` and `berachainBepolia`.
- Defined `berachain` with an ID of 80094 and a native currency of `BERA`.
- Defined `berachainBepolia` with an ID of 80069, marked as a testnet.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added support for the Berachain mainnet and Berachain Bepolia testnet networks, including native currency details and integrated block explorers for enhanced network visibility.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-19 18:03:17 +00:00
MananTank 70e024f43e [TOOL-4831] SDK: Fix MediaRenderer not showing poster for 3d models (#7387)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on fixing the display of the `poster` in the `MediaRenderer` component for 3D models and adding tests to ensure expected behavior.

### Detailed summary
- Updated the `MediaRenderer` component to show a `poster` image for 3D models.
- Added a new test case to verify rendering of the `poster` image when a 3D model is provided.
- Replaced hardcoded image URL with a constant `imageLink` for clarity in tests.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Bug Fixes**
	- Fixed an issue where the poster image was not displayed when rendering 3D models in the MediaRenderer component. Now, if a poster image is available, it will be shown as a fallback for unsupported 3D models.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-19 17:16:55 +00:00
Phillip HoandGitHub 4c2988623f chore: add dedicatedSupportChannel to TeamResponse (#7382) 2025-06-19 21:24:50 +08:00
Jonas DanielsandGitHub 5492331263 [Dashboard] Add chain, publisher, and contract data to deploy failure analytics (#7376) 2025-06-18 21:14:01 -07:00
jnsdls 1397adf32e posthog migration: part 5 (#7367)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added event reporting for contract publishing, faucet usage, and custom chain configuration additions.
  - Introduced detailed event reporting for asset purchases, creations, imports, and configuration steps.

- **Refactor**
  - Replaced legacy analytics tracking hooks with centralized reporting functions for key user actions.
  - Updated asset creation flows to report step transitions and outcomes with enhanced detail.

- **Bug Fixes**
  - Clarified faucet claim restriction text to include the "Scale" plan alongside other eligible plans.

- **Chores**
  - Removed all legacy analytics tracking hooks and event calls across the dashboard.
  - Simplified event reporting by eliminating unused tracking utilities and hooks without affecting user workflows.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on removing the `useTrack` analytics hook from various components and replacing it with direct logging or removing tracking functionality entirely.

### Detailed summary
- Deleted `useTrack` imports and instances from multiple files.
- Removed tracking-related code from components such as `LinkWalletPrompt`, `LoginOrSignup`, and `StepCard`.
- Replaced tracking calls with direct logging in error handling.
- Adjusted various components to eliminate unnecessary tracking.

> The following files were skipped due to too many changes: `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/cloud/vault/components/list-access-tokens.client.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/primary-sale.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/mint-form.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/mint-supply-tab.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/reset-claim-eligibility.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/wallet-credentials/components/edit-wallet-credential-button.tsx`, `apps/dashboard/src/@/components/blocks/pricing-card.tsx`, `apps/dashboard/src/components/embedded-wallets/Configure/index.tsx`, `apps/dashboard/src/components/onboarding/ApplyForOpCreditsForm.tsx`, `apps/dashboard/src/components/settings/AuthorizedWallets/AuthorizedWalletsTable.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/platform-fees.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/update-metadata-form.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/burn-tab.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/shared-metadata-form.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/batch-lazy-mint-button.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/airdrop-tab.tsx`, `apps/dashboard/src/components/configure-networks/ConfigureNetworks.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/airdrop-form.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/burn-button.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/transfer-button.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/overview/components/backend-wallets-table.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/claim-button.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/admins/components/admins-table.tsx`, `apps/dashboard/src/components/smart-wallets/SponsorshipPolicies/index.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/relayers/components/relayers-table.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/access-tokens/components/access-tokens-table.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/claim-button.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/components/client/PayModal.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/settings/ProjectGeneralSettingsPage.tsx`, `apps/dashboard/src/app/(app)/login/onboarding/account-onboarding-ui.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/index.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/nft/upload-nfts/upload-nfts.tsx`, `apps/dashboard/src/components/CustomChat/CustomChatContent.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_layout/primary-dashboard-button.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/components/client/FaucetButton.tsx`, `apps/dashboard/src/components/contract-components/contract-publish-form/index.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/cards.tsx`, `apps/dashboard/src/app/(app)/login/onboarding/VerifyEmail/VerifyEmail.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/nft/launch/launch-nft.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/token/launch/launch-token.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/overview/buy-nft-drop/buy-nft-drop.client.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/_components/claim-tokens/claim-tokens-ui.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/overview/buy-edition-drop/buy-edition-drop.client.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/token/create-token-page-impl.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/nft/create-nft-page.tsx`, `apps/dashboard/src/@/analytics/report.ts`

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-06-19 00:53:58 +00:00
jnsdls 2aa9ce5f78 posthog migration: part 4 (#7366)
# Analytics Event Reporting System

This PR introduces a structured analytics event reporting system for the dashboard app using PostHog. It provides clear guidelines and standardized patterns for tracking user actions.

## Key Features

- Added a centralized analytics reporting system in `src/@/analytics/report.ts`
- Implemented standardized naming conventions for events (`<subject> <verb>`) and reporting functions (`report<Subject><Verb>`)
- Created comprehensive documentation in `apps/dashboard/src/@/analytics/README.md`
- Added mandatory JSDoc requirements to document the purpose and ownership of each event
- Integrated the first event: `reportContractDeployed` to track contract deployments
- Updated the contract deployment form to use the new analytics system

## Guidelines

- Events should only be added when they answer concrete product/business questions
- All events must include typed properties and proper documentation
- Analytics code is client-side only (never import `posthog-js` in server components)
- Developers should notify #core-services before renaming or removing events

This standardized approach will make our analytics more maintainable, discoverable, and purposeful.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Documentation**
  - Added comprehensive guidelines and a README for analytics event reporting, including naming conventions, usage examples, and requirements for event tracking in the dashboard app.

- **New Features**
  - Introduced new analytics events covering contract deployments and multiple stages of team onboarding, such as plan selection, member invitations, and onboarding completion.

- **Refactor**
  - Centralized analytics reporting by replacing generic event tracking with dedicated reporting functions across contract deployment and team onboarding flows.
  - Removed redundant and granular event tracking calls, simplifying analytics integration and improving consistency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing analytics event reporting for onboarding and contract deployment processes across the application, ensuring better tracking of user interactions and feedback.

### Detailed summary
- Added `.eslintignore` entries for markdown files.
- Updated `InviteTeamMembers` and `InviteSection` to report success counts on invites.
- Introduced new reporting functions in `report.ts` for onboarding and contract events.
- Removed unused `trackEvent` calls, replacing them with specific reporting functions.
- Enhanced documentation for analytics guidelines and event reporting in `README.md` files.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-06-19 00:53:58 +00:00
jnsdls d9eee50dc1 posthog migration: part 3 (#7365)
# Remove Analytics Tracking Components

This PR removes analytics tracking components and properties from the dashboard app. Key changes include:

- Removed `TrackedLinkTW` and `TrackedUnderlineLink` components, replacing them with standard `Link` and `UnderlineLink` components
- Removed tracking properties from sidebar links and navigation components
- Eliminated tracking metadata from contract cards, NFT cards, and marketplace components
- Removed tracking-related props from various UI components
- Simplified component interfaces by removing tracking-specific parameters
- Replaced legacy tracked button components with standard button components

These changes simplify the codebase by removing the analytics tracking layer while maintaining all core functionality.

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing security by adding `rel="noopener noreferrer"` to various `Link` components throughout the codebase to prevent potential security vulnerabilities associated with opening links in new tabs.

### Detailed summary
- Added `rel="noopener noreferrer"` to multiple `Link` components for security.
- Removed `tracking` properties from several components.
- Replaced `TrackedLinkTW` with `Link` or `UnderlineLink` in various files.
- Updated imports to use `Link` from `next/link` instead of custom tracking links.

> The following files were skipped due to too many changes: `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/metrics/components/EngineSystemMetrics.tsx`, `apps/dashboard/src/tw-components/link.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/NFTCards.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(general)/import/EngineImportPage.tsx`, `apps/dashboard/src/components/explore/upsells/deploy-your-own.tsx`, `apps/dashboard/src/components/contract-functions/interactive-abi-function.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/overview/components/LatestEvents.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/contract-subscriptions/components/engine-contract-subscription.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(general)/_components.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/_components/version.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/overview/components/NFTDetails.tsx`, `apps/dashboard/src/@3rdweb-sdk/react/components/connect-wallet/index.tsx`, `apps/dashboard/src/components/contract-components/publisher/publisher-header.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/overview/ContractOverviewPage.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/overview/components/PermissionsTable.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/profile/[addressOrEns]/components/PublishedContractTable.tsx`, `apps/dashboard/src/components/explore/contract-card/index.tsx`, `apps/dashboard/src/contract-ui/tabs/code/components/code-overview.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/connect/account-abstraction/factories/page.tsx`, `apps/dashboard/src/components/embedded-wallets/Configure/index.tsx`, `apps/dashboard/src/@/components/blocks/app-footer.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/footer/FooterLinksSection.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/connect/account-abstraction/AccountAbstractionPage.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx`, `apps/dashboard/src/components/buttons/MismatchButton.tsx`, `apps/dashboard/src/tw-components/button.tsx`, `apps/dashboard/src/components/smart-wallets/SponsorshipPolicies/index.tsx`, `apps/dashboard/src/components/cmd-k-search/index.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectFTUX/ProjectFTUX.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/overview/components/MarketplaceDetails.tsx`

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Refactor**
  - Removed all analytics tracking from links and buttons across the dashboard.
  - Replaced custom tracking link and button components with standard or simplified alternatives.
  - Updated component props and interfaces to eliminate tracking-related fields.
  - Streamlined UI components by removing tracking props and logic, resulting in cleaner navigation and interactions.

- **Style**
  - Added `rel="noopener noreferrer"` attribute to all external links opening in new tabs for improved security and privacy.
  - Minor adjustments to link styling in some components to accommodate new link components.

No changes to core functionality or user workflows; navigation and UI remain consistent.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-19 00:53:58 +00:00
jnsdls 5f2bca8371 posthog migration: part 2 (#7364)
# Add PostHog Analytics Identification for Accounts and Teams

This PR adds analytics tracking capabilities to identify users and teams in PostHog:

- Created two new hooks:
  - `useIdentifyAccount`: Identifies users by their account ID and optional email
  - `useIdentifyTeam`: Identifies teams by their team ID using PostHog's group functionality

- Implemented these hooks in the `TeamHeaderLoggedIn` component to track both the current user and team

Both hooks include safety checks to ensure PostHog is properly loaded before attempting to identify users or teams.

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on integrating `posthog-js` analytics functionalities into various components of the application, specifically for resetting analytics on logout and identifying users and teams.

### Detailed summary
- Added `resetAnalytics` function in `reset.ts` to reset analytics.
- Implemented `useIdentifyTeam` hook in `identify-team.ts` for team identification.
- Implemented `useIdentifyAccount` hook in `identify-account.ts` for account identification.
- Integrated `resetAnalytics` in logout processes across multiple components:
  - `CustomConnectWallet`
  - `AccountHeader`
  - `AccountSettingsPage`
  - `LoginPage`
  - `TeamHeaderLoggedIn`
- Updated ESLint rules to allow direct imports of `posthog-js` within analytics helpers.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
	- Introduced automatic analytics identification for user accounts and teams within the dashboard.
	- Added a function to reset analytics data, now triggered on logout, wallet disconnection, and account deletion.

- **Chores**
	- Updated internal configuration to ensure analytics tracking is consistently managed and restricted to designated areas.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-19 00:53:57 +00:00
jnsdls 6b24fed21f posthog migration: part 1 (#7363)
# Migrate to PostHog US Instance

This PR updates our PostHog integration across all apps to use the US-based PostHog instance instead of the previous self-hosted solution. Key changes include:

- Upgraded PostHog JS SDK from 1.67.1 to 1.252.0
- Added API proxying via Next.js rewrites to route PostHog requests through `/_ph` endpoints
- Implemented a new initialization approach using `instrumentation-client.ts` files
- Removed custom PostHog components and providers in favor of the official SDK's auto-capture functionality
- Configured PostHog to capture page views on history changes automatically
- Disabled session recording and exception capturing for privacy and performance
- Removed the `flat` dependency which is no longer needed

These changes will provide more reliable analytics while improving performance by using PostHog's official CDN and modern SDK features.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added support for proxying analytics-related requests to external services across multiple apps, enabling seamless integration with analytics providers.

- **Chores**
  - Upgraded the analytics library dependency to the latest version in several apps.
  - Updated environment variable examples to support new analytics configuration.

- **Refactor**
  - Replaced in-app analytics instrumentation with new centralized clients for improved control and consistency.

- **Bug Fixes**
  - Minor CSS z-index syntax adjustments for improved styling consistency.

- **Removed**
  - Eliminated legacy analytics components and tracking from layouts, providers, hooks, and feedback forms, simplifying application structure and reducing external tracking.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on the removal of several `Posthog` components and their related files across multiple applications, as well as updating the `posthog-js` library version to `1.252.0`. It also introduces new `rewrites` in various configuration files to accommodate the updated tracking.

### Detailed summary
- Deleted files related to `Posthog` from:
  - `apps/portal/src/lib/env.ts`
  - `apps/portal/src/lib/posthog/Posthog.tsx`
  - `apps/dashboard/src/lib/posthog/Posthog.tsx`
  - `apps/playground-web/src/lib/posthog/Posthog.tsx`
  - `apps/dashboard/src/components/wallets/PosthogIdentifier.tsx`
- Updated `posthog-js` version to `1.252.0` in:
  - `apps/nebula/package.json`
  - `apps/playground-web/package.json`
  - `apps/portal/package.json`
  - `apps/dashboard/package.json`
- Added `NEXT_PUBLIC_POSTHOG_KEY` and `NEXT_PUBLIC_POSTHOG_HOST` to `apps/nebula/.env.example`.
- Introduced new `rewrites` in `next.config.mjs` for `apps/playground-web`, `apps/dashboard`, `apps/nebula`, and `apps/portal` to direct traffic for `Posthog`.
- Removed `Posthog` components from various layouts and providers, simplifying the structure in `apps/dashboard/src/app/pay/components/client/Providers.client.tsx` and `apps/dashboard/src/app/bridge/components/client/Providers.client.tsx`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-06-19 00:53:57 +00:00
gregandGitHub cc3fbba895 Fix: Fallback insight owned tokens request to mainnet (#7375) 2025-06-18 15:38:58 -07:00
Toomas OosaluandGitHub 381e423d59 insight playground - allow array items to be single item (#7373) 2025-06-18 15:14:12 -07:00
Manan TankandGitHub c70d5ab013 [PRO-199] Playground sidebar changes (#7369) 2025-06-18 15:13:30 -07:00
gregandGitHub 3dbf9e16fd Fix: Bridge Playground Chain Input Form (#7371) 2025-06-18 11:58:21 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
be49798e99 Version Packages (#7361)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-18 09:37:52 -07:00
AmineAfia 167f9213ff Add SignatureSelector component for custom signature input in webhooks (#7370)
# [INFRA-1454][Dashboard] Feature: Add SignatureSelector component for improved signature selection

## Notes for the reviewer

This PR introduces a new `SignatureSelector` component that enhances the signature selection experience in the webhooks UI. The component allows users to either select from predefined signatures or enter custom ones, with appropriate UI feedback.

Key improvements:
- Users can now enter custom signatures directly in the input field
- Warning message appears when a custom signature is entered
- Automatic ABI detection for known signatures
- Better handling of signature formats (automatically converts text signatures to hash format)

## How to test

1. Navigate to the webhooks creation flow
2. Test selecting predefined event/function signatures
3. Test entering custom signatures and verify the warning appears
4. Verify that ABIs are properly loaded for known signatures
5. Test the reset functionality

The component is fully integrated with the existing form validation system.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a new signature selector with search and custom input support for selecting or entering function signatures.
  - Enhanced dropdown components to allow custom search input elements.

- **Refactor**
  - Replaced the previous signature dropdown with the new selector for improved usability and consistency.
  - Improved ABI input handling, including automatic detection and reset options.

- **Bug Fixes**
  - Improved normalization of signature hash values and ABI fallback logic for webhook payloads.
  - Enhanced address parsing for ABI reset functionality.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces enhancements to the `webhookPayloadUtils`, `MultiSelect`, and `SignatureSelector` components, improving signature handling and custom input functionality. It also updates the `FilterDetailsStep` to utilize the new `SignatureSelector` and remove the deprecated `SignatureDropdown`.

### Detailed summary
- Updated `buildEventWebhookPayload` and `buildTransactionWebhookPayload` functions to handle `sigHash` and `abi` more robustly.
- Added `customSearchInput` prop to `MultiSelect` for enhanced search functionality.
- Implemented `SignatureSelector` component for better signature selection with custom input support.
- Replaced `SignatureDropdown` with `SignatureSelector` in `FilterDetailsStep`.
- Improved handling of custom signatures and ABI management in `FilterDetailsStep`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-06-18 15:49:37 +00:00
Jonas DanielsandGitHub bb09e20bcb [Dashboard] Update rewrites and redirects for Connect to Build migration (#7362) 2025-06-17 16:51:05 -07:00
gregandGitHub 78b9c8e334 [SDK] Fix: stringify bigint erc20Value in useTransactionDetails (#7360) 2025-06-17 15:32:04 -07:00
Manan TankandGitHub a5163c0c7f Fix z-index issue in NFT asset page (#7359) 2025-06-17 15:04:09 -07:00
gregandGitHub 55baa997f0 Fix: Throw error if amount and gas is more than balance (#7358) 2025-06-17 14:39:55 -07:00
Manan TankandGitHub c75e434498 [TOOL-4827] Move Nebula out of dashboard (#7356) 2025-06-17 14:30:24 -07:00
YashandGitHub 2b5941ff7d Replace CLI login article link (#7355) 2025-06-17 10:12:20 -07:00
Manan TankandGitHub 85b21ec65c Add Empty Nebula project shell (#7357) 2025-06-17 09:38:46 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
508b4c48e5 chore(deps): bump github/codeql-action from 3.28.19 to 3.29.0 (#7348)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-16 22:19:42 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
a5e086f173 chore(deps): bump @sentry/nextjs from 9.28.1 to 9.29.0 (#7351)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-16 22:19:26 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
8af6143d53 Version Packages (#7304)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-16 17:56:01 -07:00
ed81006741 Feature: New UB Components (#7354)
Co-authored-by: Joaquim Verges <[email protected]>
2025-06-16 17:07:28 -07:00
MananTank 3f74760113 [TOOL-4819] Dashboard: Load all engine backend wallets, add pagination in table (#7347)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR enhances the `useEngine` hook and the `BackendWalletsSection` component by adding pagination functionality for backend wallets retrieval, allowing users to navigate through pages of wallet data.

### Detailed summary
- Added optional `limit` and `page` parameters to the `useEngine` hook.
- Updated the fetch URL in the hook to accommodate pagination.
- Introduced pagination controls in `BackendWalletsSection` using `Button` components.
- Managed pagination state with `useState` for current page tracking.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-06-16 20:59:30 +00:00
MananTank 9253c54cf9 [TOOL-4821] Improve Team members search (#7346)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a search feature for members in the `ManageMembersSection` component by integrating the `Fuse.js` library for fuzzy searching. This enhancement allows users to filter members based on their names and emails more effectively.

### Detailed summary
- Added `Fuse` from `fuse.js` to enable fuzzy searching.
- Created a `membersIndex` using `useMemo` to index `props.members`.
- Updated the filtering logic to use `membersIndex.search(searchTerm)` for searching members.
- Included `membersIndex` in the dependency array of the `membersToShow` memoization.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Improved member search functionality with fuzzy matching, allowing for more flexible and accurate search results based on names and emails.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-16 20:47:28 +00:00
MananTank 913ea98d23 [TOOL-4802] NFT Asset Page (#7332)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the handling of NFTs and contract components within the application, including updates to types, UI components, and functionality related to NFT collections and claims.

### Detailed summary
- Updated `NFTMetadata` type to include `slug`.
- Added `slug` property in various components.
- Introduced `supplyFormatter` for better NFT supply formatting.
- Changed contract type handling to `NFTCollection`.
- Enhanced `MinimalProject` type to include `slug`.
- Updated UI components for better user interaction.
- Improved pagination and NFT display logic.
- Added new utilities for NFT claim conditions and total counts.
- Refactored claim token UI to utilize new components and structures.
- Implemented responsive layouts for better mobile support.

> The following files were skipped due to too many changes: `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/overview/nfts-grid.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/overview/buy-nft-drop/buy-nft-drop-ui.client.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx`

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced public NFT pages for ERC-721 and ERC-1155 contracts with responsive layouts, NFT grids, token viewer dialogs, and buy functionality.
  - Added components for token price display, supply claimed progress, and enhanced pagination controls.
  - Enabled NFT purchases from drops with dedicated UI and transaction handling for ERC-721 and ERC-1155 standards.
  - Added tabbed navigation for public NFT pages and improved media rendering with loading placeholders.
  - Introduced a responsive layout component for device-specific rendering and customizable wallet address focus behavior.
  - Added new components for buying NFTs and editions with form validation, pricing display, and transaction feedback.
  - Added a token price component with loading skeleton and strikethrough styling.
  - Added a supply claimed progress bar with detailed percentage display.
  - Introduced a token viewer dialog with dynamic data fetching and rich metadata display.
  - Added a paginated NFT grid with detailed claim condition info for ERC-1155 tokens.
  - Added buy NFT drop card components with server and client rendering support.
  - Added a custom media renderer component with loading state management.

- **Enhancements**
  - Improved contract and page header components with customizable styling options.
  - Expanded project and team data structures to include project slugs for better navigation.
  - Updated wallet address and pagination components with additional props for accessibility and styling.
  - Refined tracking of click and keyboard events for asset cards.
  - Simplified purchase UI code by delegating price and supply display to shared components.
  - Enhanced pagination to handle two-page scenarios with simplified controls.
  - Updated public page rendering logic to support multiple ERC standards with unified detection.
  - Improved purchase flows with analytics tracking and toast notifications.
  - Added support for special user pricing and dynamic claim condition fetching.

- **Bug Fixes**
  - Fixed conditional rendering and redirection logic for public NFT pages based on contract type and metadata presence.

- **Documentation**
  - Added Storybook stories for new UI components including supply claimed progress and NFT drop purchase flows.

- **Refactor**
  - Delegated price and supply display logic to shared components for consistency.
  - Updated types and props to support new features and improve maintainability.

- **Chores**
  - Broadened NFT metadata field types for improved compatibility with diverse data formats.
  - Added fallback for React experimental API to prevent errors in unsupported environments.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-16 17:56:26 +00:00
gregandGitHub 5ce8a5618f Revert "brige embed (#7222)" (#7345) 2025-06-16 10:39:31 -07:00
35a909a897 WIP brige embed (#7222)
Co-authored-by: gregfromstl <[email protected]>
2025-06-15 18:36:11 -07:00
gregandGitHub 9f6c1a4820 Feature: clarify webhook disclaimer (#7339) 2025-06-15 16:21:26 -07:00
Phillip HoandGitHub e43afa1b4e chore: update team/account docs for team management (#7341) 2025-06-14 14:04:32 +08:00
Jonas DanielsandGitHub 94e2cbc468 [Dashboard] Add Google Tag Manager and PostHog to CSP script sources (#7338) 2025-06-13 15:08:47 -07:00
kumaryash90 64ee04c25d Update implementations (#7335)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the `MarketplaceV3` addresses in the `ZKSYNC_IMPLEMENTATIONS` and `ZKSYNC_WETH` records, reflecting new contract addresses for the marketplace.

### Detailed summary
- Updated `MarketplaceV3` address for `[324]` from `0x88e7F2bEf0a23073b7Cc2c79D307Dbd9138Fa79c` to `0xFD5663B3Ed316aEB79c9b5c4A242172273cA3395`.
- Updated `MarketplaceV3` address for `[2741]` from `0x4b14569c7B79DBe686Ac3Ba5996131E7EDaB7a93` to `0x9Fdf34f552cdA8E2B0cD64e2EddcB732a5Ff8455`.
- Added new `MarketplaceV3` address for `[531050104]`: `0x292c324920d4120F928dA6f04548442fa10B8B0f`.
- Added new `MarketplaceV3` address for `[50104]`: `0x292c324920d4120F928dA6f04548442fa10B8B0f`.
- Added new `WETH` address for `[531050104]`: `0x9B7EA63EF5062661570afe548e3AD691D5652527`.
- Added new `WETH` address for `[50104]`: `0x2b1A859dE6A55c553520D7780Bc5805712B128F9`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added support for MarketplaceV3 contract on two additional networks.
  - Updated MarketplaceV3 contract addresses on existing networks for improved compatibility.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-13 13:50:20 +00:00
Jonas DanielsandGitHub 680b8cdb16 [Dashboard] Rename getMemberById to getMemberByAccountId and add onboarding banner (#7334) 2025-06-12 16:40:00 -07:00
saminacodes 787db9308d [Docs] UB (#7333)
CORE-0000

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the documentation for the `Universal Bridge` by adding troubleshooting information and FAQs regarding payment issues and token support.

### Detailed summary
- Added a section for handling "Error 500: Internal Server Error" with upgrade instructions for the SDK.
- Included a new FAQ on supporting tokens on the `Universal Bridge` with criteria and request instructions.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Documentation**
  - Added a new FAQ explaining how to request support for a token on Universal Bridge, including submission steps and expected processing time.
  - Introduced troubleshooting guidance for resolving "Error 500" or "Something went wrong" messages in Universal Bridge, with instructions to update the SDK and contact support if needed.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-12 23:32:30 +00:00
kilavvyandGitHub c152db3e2f Fix Typographical Errors in Documentation and Comments (#7331)
Signed-off-by: kilavvy <[email protected]>
2025-06-12 14:01:03 -07:00
Corey RiceandGitHub 5f57300cf9 docs: fix typos in doc string (#7301) 2025-06-11 23:44:15 -07:00
Jonas DanielsandGitHub db20bd17b1 update deps 2 (#7328) 2025-06-11 23:41:12 -07:00
Jonas DanielsandGitHub 0a85205e4a update storybook (#7327) 2025-06-11 23:24:45 -07:00
Jonas DanielsandGitHub cda390eefe update dependencies (#7326) 2025-06-11 21:23:36 -07:00
leopardracerandGitHub 305f738ff1 Fix typos in comments and test descriptions (#7323)
Signed-off-by: leopardracer <[email protected]>
2025-06-11 21:08:07 -07:00
Jonas DanielsandGitHub a1c875c596 [Dashboard] Add self-service dedicated engine deployment option (#7325) 2025-06-11 19:32:08 -07:00
Jonas DanielsandGitHub 38f1821eb9 [Dashboard] Add plan selection step to team onboarding flow (#7324) 2025-06-11 17:56:00 -07:00
jnsdls 848984481f [Dashboard] Add 7-day free trial for Growth plan (#7322)
# Add 7-day free trial for Growth plan and include Starter plan in team onboarding

This PR adds a 7-day free trial indicator for teams that have just signed up but haven't subscribed yet when selecting the Growth plan. It also updates the team onboarding flow to properly handle the Starter plan.

Changes include:
- Added logic to detect if a team qualifies for the 7-day trial (new signup with no payment on Growth plan)
- Added a "7 day free trial" text display for qualifying teams
- Updated the polling logic to consider both "free" and "starter" plans as non-paid plans
- Modified the onboarding description text to remove reference to "free Starter plan"

## Summary by CodeRabbit

- **New Features**
  - Added a clear "7 day free trial" indicator for eligible Growth plan users.

- **Enhancements**
  - Updated onboarding modal text to remove "free" from the Starter plan description.
  - Modified billing plan polling logic to exclude both "free" and "starter" plans before proceeding.

---

## PR-Codex overview
This PR focuses on updating the logic for determining billing plans and enhancing the user experience regarding trial periods in the `InviteTeamMembers` and `PricingCard` components.

### Detailed summary
- In `InviteTeamMembers.tsx`, updated the condition for `isNonFreePlan` to exclude both "free" and "starter" plans.
- Modified the text in `InviteModalContent` to clarify the Starter plan.
- In `pricing-card.tsx`, added a condition for a 7-day trial for teams with a "growth" billing plan and "noPayment" status.
- Changed the rendering logic for remaining trial days to include a message for the 7-day trial.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the pricing logic and UI in the dashboard. It modifies how pricing is handled, specifically for trial periods, and updates the display of pricing information to better reflect the available options and trials.

### Detailed summary
- Changed `price` type from `string | number` to `number`.
- Updated `isNonFreePlan` logic to exclude "starter" plans.
- Revised promotional text for the Growth plan.
- Added logic for a 7-day free trial for new teams on the Growth plan.
- Updated UI to display a "7 Day Free Trial" badge and adjusted pricing display accordingly.
- Modified call-to-action button text based on trial eligibility.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added a "7 Day Free Trial" badge and updated price display for eligible users on the Growth plan.
  - Call-to-action button now shows "Start 7 Day Free Trial" when applicable.

- **Bug Fixes**
  - Improved onboarding flow by updating plan detection logic to better handle "Starter" and "Free" plans.

- **Style**
  - Revised onboarding text to emphasize upgrading to the Growth plan and removed mention of the Starter plan.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-11 19:59:56 +00:00
Jonas DanielsandGitHub 950bbaee1c [Dashboard] Update URL rewrites for grants and superchain pages (#7320) 2025-06-10 18:09:35 -07:00
MananTank 99d6b3b151 Dashboard: Add NFT creation wizard in Asset page (#7315)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on improving the handling of file inputs and media uploads within the application. It introduces new functionalities for managing blob URLs, refines error handling, and enhances the user interface for uploading assets.

### Detailed summary
- Added `fileToBlobUrl` function to convert files to blob URLs.
- Enhanced `FileInput` components to accept a `client` prop for better integration.
- Updated error handling in several components to provide more detailed feedback.
- Refined layouts and styles for asset creation pages.
- Introduced new schemas for validating NFT and social URL data.
- Improved tracking functionality for NFT creation steps.
- Added support for handling multiple file types in uploads.
- Enhanced the user interface for attributes and social URLs input fields.

> The following files were skipped due to too many changes: `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/nft/upload-nfts/upload-nfts.tsx`, `apps/dashboard/src/core-ui/batch-upload/batch-table.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/page.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/nft/sales/sales-settings.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/nft/collection-info/nft-collection-info-fieldset.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/snapshot-upload.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/token-info-fieldset.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/airdrop-upload.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/modules/components/nft/NFTMediaFormGroup.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/nft/create-nft-page-ui.tsx`, `apps/dashboard/src/components/shared/FileInput.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/shared-metadata-form.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/distribution/token-airdrop.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/lazy-mint-form.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/nft/upload-nfts/batch-upload/process-files.ts`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/mint-form.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/nft/upload-nfts/single-upload/single-upload-nft.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/update-metadata-form.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/nft/upload-nfts/batch-upload/batch-upload-instructions.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/launch/launch-token.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/nft/create-nft-page.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/nft/upload-nfts/batch-upload/batch-upload-nfts.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/nft/launch/launch-nft.tsx`

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added logic to always display the selected token in token selectors.
  - Introduced a styled drag-and-drop file upload area with error handling and reset options.
  - Added reusable components: file preview for files and URLs, step cards with navigation and tracking, social URLs fieldset, downloadable file button, batch upload instructions, and NFT attribute management.
  - Launched a multi-step NFT creation workflow covering collection info, asset upload (single and batch), sales settings, and launch steps.
  - Added schema validation for NFT metadata including pricing, social URLs, and blockchain addresses.
  - Enabled batch uploading and inline editing of NFTs with price, currency, and supply management.
  - Added advanced sales and royalty settings with real-time validation and analytics tracking.

- **Enhancements**
  - Improved error reporting in toast notifications with detailed parsed messages.
  - Centralized and streamlined media file handling by removing redundant hooks and consolidating upload logic.
  - Enhanced navigation and event tracking across multi-step asset creation flows.
  - Updated UI components to accept client context for consistent file handling.
  - Refined form validation by reusing common schemas.

- **Bug Fixes**
  - Corrected NFT supply column label to "Circulating Supply".
  - Improved loading placeholder text in token selectors.

- **Refactor**
  - Consolidated CSV upload logic and replaced custom drag-and-drop UIs with a unified DropZone component.
  - Replaced multiple media preview components with a single FilePreview component.
  - Updated prop types and component interfaces for better consistency and maintainability.
  - Removed deprecated hooks and unused imports to reduce technical debt.
  - Reorganized and standardized import paths across modules.
  - Simplified step execution logic in launch flows and enhanced retry capabilities.

- **Documentation**
  - Added Storybook stories for DropZone, multi-step status, and NFT upload components to support visual testing and documentation.

- **Chores**
  - Exported utility functions and constants for broader use.
  - Improved styling flexibility by enhancing component className handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-10 21:50:05 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
81c66f4e7b chore(deps): bump github/codeql-action from 3.28.18 to 3.28.19 (#7308)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-10 12:22:11 -07:00
catalyst17 639356071c docs: add webhooks section to agents and LLMs documentation (#7317)
# [Portal] Feature: Add Webhooks Documentation for AI Agents + Example with multiple ABIs for decoding

## Notes for the reviewer

Example with multiple ABIs for decoding ([related PR](https://app.graphite.dev/github/pr/thirdweb-dev/insight-service/268/fix-improve-ABI-validation-to-support-array-format))

1. Added examples on creating events filters with multiple ABI options

Also, this PR adds comprehensive documentation about using webhooks with AI agents in the Insight platform. The documentation covers:

1. Use cases for webhooks with AI agents
2. How to create and manage webhooks
3. Filtering capabilities, including support for multiple ABIs
4. Information about webhook payloads

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Documentation**
  - Added a new "Webhooks" section describing how to use webhooks with the Insight API for real-time blockchain event notifications and event-driven AI agents.
  - Provided use cases, filter examples, and links to detailed webhook and payload documentation.
  - Enhanced webhook filter documentation to clarify support for multiple ABIs, including examples for handling ERC-20 and ERC-721 Transfer events.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-10 18:53:39 +00:00
saminacodes 62af256575 [Docs] Update Broken Link (#7319)
CORE-0000

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the content in the `page.mdx` file related to fees and support for the `Universal Bridge` feature.

### Detailed summary
- Updated the explanation of fees for "Buy With Crypto" to specify fee settings in the project page.
- Revised the link for supported cryptocurrencies to direct to a new URL with a specific service.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Documentation**
  - Clarified FAQ entry about setting additional fees for swap transactions and provided step-by-step instructions for configuring these fees.
  - Updated the "supported chains" link in the FAQ to use an absolute URL.
  - Removed an unnecessary blank line for improved readability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-10 18:46:55 +00:00
jnsdls eb1c705654 [Dashboard] Improve error handling in billing checkout process (#7318)
# Add "Go back" button to Stripe redirect error page and improve error handling

This PR enhances the Stripe redirect error page by adding a "Go back" button, allowing users to navigate away from error states. It also improves error handling in the billing checkout flow by:

1. Converting `getBillingCheckoutUrl()` to return structured responses with proper error messages
2. Adding specific error messages for different HTTP status codes (402 for outstanding invoices, 429 for rate limiting)
3. Displaying these more descriptive error messages to users when checkout fails

These changes provide a better user experience when errors occur during the checkout process.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Added a "Go back" button to the Stripe redirect error page, allowing users to easily return to the previous screen.

- **Bug Fixes**
  - Improved error handling during the checkout process, providing clearer error messages for issues such as authentication, outstanding invoices, rate limiting, and unknown errors. Error messages are now displayed directly on the error page when checkout fails.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-10 18:30:16 +00:00
arcoraven 9643b8d2c5 feat: create/delete teams (#7293)
# [Dashboard] Feature: Add Team Creation and Deletion

## Notes for the reviewer

This PR adds the ability for users to create and delete teams from the dashboard. It includes:

1. Server actions for creating and deleting teams
2. UI integration in the account header, team selector, and account teams page
3. Team deletion functionality in the team settings page

## How to test

- Try creating a new team from the account header dropdown
- Try creating a new team from the account teams page
- Try deleting a team from the team settings page (only available for team owners)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Enabled team creation directly from the account and team selection interfaces, allowing users to create a new team and be redirected to its page immediately.
  - Activated the "Create Team" button in relevant menus and headers, making team creation accessible across both desktop and mobile views.

- **Bug Fixes**
  - Improved error handling and user feedback with toast notifications when team creation fails.

- **Refactor**
  - Updated team deletion to use real permission checks and improved the user interface for deleting teams.
  - Added comprehensive handling of authorization and error messages for team creation and deletion operations.
  - Redirected users to the account page when no teams are available.

- **Documentation**
  - Marked the default team retrieval function as deprecated in the documentation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on enhancing team management functionalities within the application. It introduces the ability to create and delete teams, updates various UI components to accommodate these changes, and ensures proper redirection and error handling during team operations.

### Detailed summary
- Added `createTeam` function in `createTeam.ts` for team creation.
- Introduced `deleteTeam` function in `deleteTeam.ts` for team deletion.
- Updated UI components to include `createTeam` functionality.
- Modified `TeamHeaderLoggedIn`, `AccountHeader`, and other components to handle team creation and deletion.
- Implemented redirection upon successful team creation and deletion.
- Updated `DeleteTeamCard` and `TeamGeneralSettingsPageUI` to manage permissions for team deletion.
- Enhanced error handling and user feedback with toast notifications during team operations.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-06-10 01:44:58 +00:00
jnsdls 56d27d4c63 [Dashboard] Sort usage categories by total amount in descending order (#7314)
# Sort usage categories by total amount

This PR sorts the usage categories displayed on the team usage page by their total amount in descending order. Categories with higher total costs will now appear at the top of the list, making it easier for users to identify their most significant expenses at a glance.

The implementation:
- Creates a `sortedCategories` array by sorting the usage preview data
- Sorts based on the sum of `amountUsdCents` for each category's line items
- Uses the sorted array in the render function instead of the original data

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Enhancements**
  - Usage categories are now displayed in descending order based on their subtotal amounts, making it easier to identify the highest usage categories at a glance.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-09 20:52:46 +00:00
jnsdls e742929b2c [Dashboard] Add notifications system with unread tracking (#7302)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a new notifications system with a bell icon button displaying unread indicators.
  - Added a notifications panel with Inbox and Archive tabs supporting infinite scrolling and marking notifications as read.
  - Implemented a centralized notification state manager with optimized fetching, caching, and mutation handling.
  - Added a UI drawer component for mobile notification display with smooth open/close interactions.

- **Refactor**
  - Replaced legacy notification button and fetching logic with the new unified Notifications button using only account ID.
  - Removed legacy notification callbacks and components from header and navigation for simplified notification handling.

- **Chores**
  - Removed deprecated notification files and Storybook stories.
  - Updated dependencies by replacing "idb-keyval" with "vaul" for UI drawer functionality.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on removing the `NotificationButton` component and its related files, replacing it with a new `NotificationsButton` component that integrates notifications more effectively within the application.

### Detailed summary
- Deleted `NotificationButton.tsx`, `fetch-notifications.ts`, and `NotificationButton.stories.tsx`.
- Added `NotificationsButton` component with enhanced functionality for displaying notifications.
- Updated various components (`AccountHeaderUI`, `SecondaryNav`, `TeamHeaderUI`) to use `NotificationsButton`.
- Removed references to `getInboxNotifications` and `markNotificationAsRead` from multiple components.
- Introduced new notification management logic and UI components in `notification-list.tsx` and `notification-entry.tsx`.
- Updated dependencies in `package.json` to include the new `vaul` library for UI elements.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-06-09 20:09:05 +00:00
MananTank dacfd94af5 Nebula: Improved toasts in move funds page (#7307)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on improving error handling and user feedback in the `SendFunds` function by utilizing `toast.promise` for asynchronous operations, enhancing the user experience during token transfers.

### Detailed summary
- Replaced `toast.loading`, `toast.success`, and `toast.error` with `toast.promise` for batch and single token transfers.
- Improved error handling by catching errors without logging them.
- Simplified success and error messages in the toast notifications.
- Removed duration settings for toast notifications.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Refactor**
  - Toast notifications for sending funds are now streamlined using unified loading, success, and error messages, providing clearer and more consistent feedback during transactions. Error messages are also improved for better clarity.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-09 15:24:28 +00:00
catalyst17 2d15cd6a47 docs: update webhook parameter filtering types and add example (#7295)
# [Portal] Fix: Update webhook parameter filtering documentation

## Notes for the reviewer

This PR updates the webhook filtering documentation to:
1. Correct the type definition for `params` in both events and transactions filters
2. Add a practical example showing how to filter for `Transfer` events with specific parameters

## How to test

Review the updated documentation to ensure the type definitions are correct and the new example is clear and helpful.

<!-- start pr-codex -->

---

## PR-Codex overview
This PR updates the `params` property in the webhooks filtering configuration to allow for stricter type definitions, changing it from `Record<string, any>` to `Record<string, string | number>`. It also provides an example of how to filter `Transfer` events.

### Detailed summary
- Changed `params` type in the `webhooks` filtering from `Record<string, any>` to `Record<string, string | number>`.
- Updated `params` type in the `signatures` object from `string[]` to `Record<string, string | number>`.
- Added an example for filtering `Transfer` events with a specific `from` address.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-06-09 12:00:22 +00:00
catalyst17 62a3cd8348 fix: make contract and to addresses optional in webhook form (#7294)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on improving the handling of contract addresses in the `CreateWebhookModal` and `FilterDetailsStep` components, as well as refining validation logic for addresses in `webhookTypes.ts`. It enhances user experience by clarifying requirements and allowing optional inputs.

### Detailed summary
- In `CreateWebhookModal.tsx`, the `addresses` property now defaults to an empty string if not provided.
- In `FilterDetailsStep.tsx`, the `FormLabel` for "Contract Addresses" and "To Address" no longer includes a required indicator.
- Validation in `webhookTypes.ts` for `addresses`, `fromAddresses`, and `toAddresses` is updated to allow empty inputs and provide clearer error messages.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Bug Fixes**
	- Removed the required field indicator from "Contract Addresses" and "To Address" labels in the form UI.

- **New Features**
	- Updated form validation to allow "Contract Addresses", "From Address", and "To Address" fields to be optional and accept empty values, with improved error messages for clarity.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-09 12:00:22 +00:00
Phillip HoandGitHub 2a4195b4ad chore: pass authToken to analytics query (#7306) 2025-06-09 17:51:38 +08:00
Phillip HoandGitHub 6378f371c5 chore: pass allowImpersonation flag to /v2/keys/use (#7303) 2025-06-08 17:59:00 +08:00
Jonas DanielsandGitHub b3e42f7407 [Dashboard] Add audit log feature for Scale plan teams (#7300) 2025-06-07 00:37:21 -07:00
MananTank 044f897763 Nebula: Add Move funds page (#7298)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the user experience in the `Nebula` application by adding new features, improving existing components, and ensuring better handling of user authentication and fund transfers.

### Detailed summary
- Updated redirect logic in `middleware.ts` to preserve search params and prevent redirect from certain paths.
- Added `placeholder` prop to `DecimalInput` in `decimal-input.tsx`.
- Integrated `TWAutoConnect` component in `ChatPageLayout.tsx`.
- Enhanced `TransactionButton` in `TransactionButton.tsx` to manage a new `disableNoFundsPopup` prop.
- Refactored `NebulaProviders` to remove redundant `TWAutoConnect` usage.
- Created a new `RecoverPage` component in `move-funds/page.tsx` for fund recovery.
- Introduced `MoveFundsConnectButton` for connecting wallets in `connect-button.tsx`.
- Implemented a new `MoveFundsPage` for managing fund transfers.
- Updated `MismatchButton` to handle the new `disableNoFundsPopup` prop.
- Enhanced token management and user feedback within the `MoveFundsPage`.
- Added new components and functions for improved token selection and balance rendering.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
	- Introduced a Move Funds page for transferring cryptocurrency between wallets, featuring wallet connection, chain and token selection, and transaction handling.
	- Added a customizable connect button supporting multiple authentication methods and theme adaptation.
	- Added the ability to display placeholder text in decimal input fields.
	- Enhanced button components with an option to disable the "Not Enough Funds" popup.
	- Added a dedicated page layout combining header, theme toggle, support link, and Move Funds functionality.
	- Integrated automatic wallet connection within the chat page layout for seamless user experience.

- **Bug Fixes**
	- Updated middleware to allow unauthenticated access to the Move Funds page without redirecting to the login page.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-07 00:45:33 +00:00
jnsdls 7fa5be5317 [Dashboard] Add staff mode for viewing teams without membership (#7299)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added a "STAFF MODE" warning banner for users viewing a team they do not belong to, with a button to leave staff mode.
- **User Interface**
  - Adjusted the team header layout for improved accessibility, ensuring the team badge is no longer nested inside the team link.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on updating the `TeamHeaderUI` component and enhancing the `TeamLayout` and `ProjectLayout` components to incorporate a "Staff Mode" feature, which restricts user actions based on team membership.

### Detailed summary
- Modified the structure of the `TeamHeaderUI` component to wrap the `Link` inside a `span`.
- Added a "Staff Mode" warning message in `TeamLayout` if the user is not part of the team.
- Implemented the same "Staff Mode" warning in `ProjectLayout`.
- Updated API calls to include `getTeamBySlug` and `getProject`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-06-06 21:42:16 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2510c4f0c1 Version Packages (#7297)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-06 12:43:05 -05:00
gregandGitHub 69bf925c01 [SDK] Feature: preferred chan id pay embed (#7296) 2025-06-06 11:54:06 -05:00
Yash094 d4aed43b18 feat: add SIWA feedback functionality to dashboard chat (#7272)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on refactoring the `CustomChatContent` and `CustomChats` components to improve type safety and functionality in handling user messages and feedback. It introduces new types and methods for managing chat messages and feedback submission.

### Detailed summary
- Replaced `NebulaUserMessage` with `UserMessage` and `CustomChatMessage`.
- Added `handleFeedback` function for submitting feedback on messages.
- Updated `CustomChats` component to handle new message types and feedback.
- Modified `sendMessage` logic in `ChatBar` to use the new message structure.
- Introduced `RenderMessage` and `StyledMarkdownRenderer` for rendering chat messages.
- Added feedback buttons for assistant messages in `RenderMessage`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a new chat interface supporting multiple message types with distinct visual styles.
  - Enabled users to submit feedback (thumbs up/down) on assistant messages.
  - Enhanced chat experience with auto-scrolling that respects user interactions.

- **Bug Fixes**
  - Prevented duplicate feedback submissions on assistant messages.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-06 16:24:33 +00:00
Jonas DanielsandGitHub f89c15c517 [Dashboard] Add detailed usage breakdown and billing preview (#7290) 2025-06-06 00:11:11 -07:00
MananTank c95c0dc5bd Nebula: Fix AutoConnect not using nebula client in root layout (#7291)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the `TWAutoConnect` component to accept a `client` prop, allowing for better integration with different third-party clients in the application.

### Detailed summary
- Updated `TWAutoConnect` in `layout.tsx` to accept a `client` prop.
- Introduced `thirdwebClient` in `providers.tsx` to provide the client to `TWAutoConnect`.
- Modified `TWAutoConnect` component to require a `client` prop.
- Updated usage of `TWAutoConnect` in `nebula-app/providers.tsx` to pass in `nebulaAppThirdwebClient`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Improved integration of client context within the auto-connect wallet feature, enhancing authentication and connectivity for users.

- **Refactor**
  - Updated internal handling of client instances for wallet auto-connect components to streamline authentication processes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-06 00:45:26 +00:00
GiselleNessi c77b4d8f85 updated old support links to portal (#7286)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the `SupportPage` component in the `page.tsx` file by adding new features, modifying existing sections, and improving the overall structure and content of the help products.

### Detailed summary
- Added new icons: `HomeIcon`, `WalletIcon`, `EngineIcon`, `InsightIcon`, `PayIcon`, and `NebulaIcon`.
- Introduced a new help product: "Playground".
- Replaced "Connect" product icon and updated its description.
- Changed "Contracts" to "Universal Bridge" with a new icon and description.
- Updated "Engine" product with a new icon and description.
- Replaced "Account" with "Insight" and updated its details.
- Replaced "Misc" with "Nebula" and updated its details.
- Changed section title from "Knowledge base" to "Learning Resources".
- Modified product icon rendering from image to component usage.
- Updated button text from "View All" to "View".

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced new products "Playground" and "Nebula" to the support page.
- **Improvements**
  - Updated product categories, icons, and descriptions for a clearer overview.
  - Replaced static images with modern icon components for improved visual consistency.
  - Changed page heading to "Learning Resources."
  - Simplified layout by replacing help article lists with concise product descriptions.
  - Shortened the "View All" button text to "View."
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-05 21:13:38 +00:00
PaoloRollo 5c4c49b90a [Dashboard] Fix: Handle ping event (#7287)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

This PR adds a simple `ping` case in the `promptNebula` function to avoid `console.warn` logs in the browser console.

<!-- start pr-codex -->

---

## PR-Codex overview
This PR adds support for a new event type, `"ping"`, in the `ChatStreamedEvent` type definition and handles the `"ping"` event case in the event processing logic.

### Detailed summary
- Added a new case for `"ping"` in the event handling switch statement.
- Introduced a new type definition for the `"ping"` event in the `ChatStreamedEvent` type, including a `data` field of type `string`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Improved chat event handling with support for a new "ping" event type, enhancing the stability of real-time chat updates.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-05 18:55:15 +00:00
MananTank a5cd66439c [TOOL-4687] Dashboard: Show Special Price UI in claim tokens card (#7284)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on improving the `RecentTransfers` and `ClaimTokenCardUI` components in a React application by refining state management, simplifying logic, and enhancing UI elements.

### Detailed summary
- Removed `hasFetchedOnce` state in `RecentTransfers`.
- Changed condition for disabling the button in `RecentTransfersUI`.
- Simplified logic for claim parameters in `ClaimTokenCardUI`.
- Introduced `TokenPrice` component to handle price display.
- Added tooltip for special pricing indication.
- Adjusted UI elements for better clarity and organization.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-06-05 18:44:04 +00:00
MananTank 05f65ec150 [TOOL-4686] Dashboard: Add supply remaining progress in asset page claim tokens card (#7283)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR enhances the `ClaimTokenCardUI` component by adding a new `SupplyRemaining` feature, which displays the supply of tokens claimed and remaining. It also improves the UI with a progress bar and formatting adjustments.

### Detailed summary
- Added `Progress` component for visual representation of supply sold.
- Introduced `SupplyRemaining` function to show claimed and remaining supply.
- Formatted supply numbers using `compactNumberFormatter`.
- Updated headings and styling for better UI consistency.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added a visual display showing claimed versus total claimable token supply, including a progress bar and percentage sold.
- **Style**
  - Improved typography and spacing for headings and token symbol labels for better visual consistency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-05 18:44:04 +00:00
gregandGitHub 6610633799 [Dashboard] Fix: Async team analytics rendering (#7289) 2025-06-05 12:19:36 -05:00
MananTank 7ab79dc3bb Dashboard: Fix contract deploy page missing last used teamId in client object (#7285)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the authentication mechanism by replacing the usage of `getAuthToken` with `getUserThirdwebClient` in the `page.tsx` and `uri-based-deploy.tsx` files, enhancing the handling of third-party client connections.

### Detailed summary
- Removed `getAuthToken` and its associated logic in `page.tsx`.
- Introduced `getUserThirdwebClient` to fetch the client in `page.tsx`.
- Updated the `isLoggedIn` prop in `PublishedContract` to use `!!account`.
- Adjusted the logic in `uri-based-deploy.tsx` to fetch client and teams without `authToken`.
- Changed the `isLoggedIn` prop to always be `true` in `uri-based-deploy.tsx`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Refactor**
  - Streamlined the process for obtaining the user client, simplifying authentication and client initialization for published contract and deploy flows.
  - Improved logic for login checks and client handling, reducing redundant token fetching and making the login state determination more straightforward.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-05 14:07:42 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2b39669e46 Version Packages (#7277)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-05 05:30:01 -07:00
ArseniiandGitHub e3e3142f7e fix: handle short storage values in beacon proxy pattern (#7282) 2025-06-05 05:21:22 -07:00
ArseniiandGitHub 7c0b80d132 fix: handle short proxyStorage in getBeaconFromStorageSlot (#7281) 2025-06-05 04:45:21 -07:00
Joaquim VergesandGitHub 5c1db8bf27 [Dashboard] Add Sophon testnet to custom claim amounts (#7280) 2025-06-05 04:26:01 -07:00
Phillip HoandGitHub a87446a9e6 fix: invalid handling of ISO time in RPC graphs (#7279) 2025-06-05 17:01:04 +08:00
saminacodes 9493fda7bf [Docs] webhook and ub docs (#7278)
CORE-0000

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the user experience for webhooks in the thirdweb platform by updating sidebar navigation, adding troubleshooting information, and improving documentation on configuring webhooks.

### Detailed summary
- Updated sidebar in `insight` to rename "Getting Started" to "Get Started".
- Added "Troubleshoot" section with `WrenchIcon` in `pay` sidebar.
- Introduced troubleshooting content in `troubleshoot/page.mdx`.
- Enhanced `webhooks/page.mdx` with new structure and images for configuration steps.
- Revised descriptions and added a "Get started" section in `webhooks/page.mdx`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added a "Troubleshoot" section in the sidebar for Universal Bridge, providing guidance for resolving payment card issues.
  - Introduced a troubleshooting article explaining card support errors and suggesting alternative payment methods.

- **Documentation**
  - Enhanced the Insight Webhooks documentation with step-by-step setup instructions, visual aids, and improved metadata description.
  - Added a new "Get started" section to guide users through webhook creation and testing.

- **Style**
  - Renamed the "Getting Started" sidebar link under Webhooks to "Get Started" for consistency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-04 23:39:32 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
59dbae0c21 chore(deps): bump biomejs/setup-biome from 2.5.0 to 2.5.1 (#7250)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-04 16:00:50 -07:00
Jonas DanielsandGitHub 42236d43a8 [service-utils] Remove starter_legacy plan type (#7276) 2025-06-04 16:00:32 -07:00
jnsdls a920d1ee07 [Dashboard] Restrict billing actions to team owners only (#7274)
# Improve Billing UI for Non-Owner Team Members

This PR enhances the billing UI to properly handle permissions for team members who aren't owners. It:

- Restricts billing actions to team owners only
- Adds tooltips explaining why certain actions are disabled
- Properly disables buttons for non-owner team members
- Improves the Button component to handle disabled state for non-button elements

## Key Changes:

- Added `isOwnerAccount` flag to billing components to conditionally render or disable actions
- Enhanced Button component to properly handle disabled state for non-button elements (like anchor tags)
- Added tooltips to explain why actions are disabled for non-owners
- Restricted the following actions to team owners only:
  - Selecting/changing plans
  - Managing billing
  - Topping up credits
  - Paying invoices

These changes ensure a better UX for team members who don't have billing permissions while maintaining full functionality for team owners.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
	- Added owner-only restrictions for billing and payment actions. Only team owners can change plans, manage billing, top up credits, or pay invoices. Non-owner users now see disabled buttons with tooltips explaining these restrictions.
- **Accessibility**
	- Improved accessibility for disabled buttons by adding appropriate ARIA attributes and visual indicators.
- **User Interface**
	- Updated tooltips and button states to clearly communicate permission-based access to billing features.
	- Enhanced button behavior to consistently reflect disabled states across different elements and contexts.
- **Bug Fixes**
	- Standardized disabled state handling for buttons and interactive elements, ensuring consistent visual and functional behavior.
- **Chores**
	- Replaced internal navigation links with standard anchor elements for external billing-related links, improving security with added link attributes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on enhancing user experience by adding an `isOwnerAccount` boolean prop to various components. This prop controls the visibility and functionality of certain actions, ensuring that only team owners can perform specific tasks.

### Detailed summary
- Changed `<Link>` to `<a>` in `billing.tsx` for proper link behavior.
- Added `isOwnerAccount` prop to multiple components and updated their functionality based on ownership status.
- Introduced `ToolTipLabel` to provide contextual information for actions restricted to team owners.
- Updated button states to reflect whether actions are enabled or disabled based on `isOwnerAccount`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-06-04 14:02:54 -07:00
MananTank 2329a22bf1 Dashboard: Poll for active claim condition before continuing with minting in coin launch (#7273)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a polling mechanism to ensure that claim conditions are set before proceeding with token minting in the `CreateTokenAssetPage` component.

### Detailed summary
- Added import for `pollWithTimeout` utility.
- Implemented polling logic to check for active claim conditions before minting.
- Set a timeout of 30 seconds for the polling process.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Improved the token creation process by ensuring minting only begins after claim conditions are confirmed, providing a more reliable experience for users.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-04 20:14:51 +00:00
Jonas DanielsandGitHub b25d2afa8b [Dashboard] Fix peak rate date formatting with error handling (#7271) 2025-06-04 02:19:36 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
8910f55ca2 Version Packages (#7245)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-04 00:31:34 -07:00
Joaquim VergesandGitHub b237318b7d [SDK] Support EIP7702 execution for ecosystem wallets (#7268) 2025-06-04 00:09:00 -07:00
Jonas DanielsandGitHub 082d9630f4 [Dashboard] Add crypto payment option for invoices (#7270) 2025-06-04 00:03:15 -07:00
Jonas DanielsandGitHub d4bc8e199d [Dashboard] Fix Stripe balance calculation in getStripeBalance function (#7269) 2025-06-03 22:45:25 -07:00
Jonas DanielsandGitHub 4edf1ebe95 [Dashboard] Add crypto top-up functionality for account credits (#7239) 2025-06-03 22:07:33 -07:00
MananTank b12725b231 [TOOL-4673] Dashboard: Enable posthog session recording for nebula app (#7267)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the `PHProvider` component by introducing a `disable_session_recording` prop, allowing for more control over session recording. Additionally, it renames the `Providers` component to `PayProviders` and `BridgeProviders` in their respective files for clarity.

### Detailed summary
- Added `disable_session_recording` prop to `<PHProvider>` in multiple files.
- Renamed `Providers` to `PayProviders` in `Providers.client.tsx` and `pay/layout.tsx`.
- Renamed `Providers` to `BridgeProviders` in `Providers.client.tsx` and `bridge/layout.tsx`.
- Updated the `PHProvider` function signature to accept `disable_session_recording` as a prop.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
	- Added explicit controls to enable or disable session recording across different sections of the dashboard.
- **Refactor**
	- Renamed provider components for improved clarity in the Bridge and Pay sections.
	- Updated component usage to reflect new provider names.
- **Style**
	- No visual changes introduced.
- **Bug Fixes**
	- None.
- **Documentation**
	- None.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-04 04:13:00 +00:00
MananTank 3d1c321bd6 [TOOL-4642] Fix useTxNotifications errors not shown in project contract pages (#7259)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on adding an `ErrorProvider` wrapper around the layout of the `ContractLayout` component to handle errors more effectively.

### Detailed summary
- Added import for `ErrorProvider`.
- Wrapped the main layout in an `ErrorProvider`.
- Removed the previous layout structure while integrating the `ErrorProvider`.
- Maintained the structure of `TeamHeaderLoggedIn`, `SharedContractLayout`, `SaveLastUsedProject`, and `AppFooter` within the new error handling context.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Improved error handling on the contract page with the addition of enhanced error context support.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-03 22:50:54 +00:00
MananTank 810ec6efa7 [TOOL-4651] Dashboard: Show error on invalid token distribution in ERC20 asset creation form (#7260)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the `TokenDistributionFieldset` component by adding error handling for token distribution and improving the UI by displaying error messages. It also updates the `StepCard` and `DistributionBarChart` components to accommodate new features.

### Detailed summary
- Added `disabled` prop to `StepCard`.
- Integrated error handling in `TokenDistributionFieldset` for distribution validation.
- Displayed error messages when distribution exceeds supply.
- Improved `DistributionBarChart` to conditionally style text based on segment percentages.
- Introduced `getDistributionError` and `SafeNumber` utility functions for validation logic.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Added validation to ensure token distribution does not exceed total supply, with error messages displayed when limits are surpassed.
  - The "Next" submit button in the token creation flow can now be disabled based on validation errors.

- **Bug Fixes**
  - Owner percentage in the distribution chart is now prevented from displaying negative values.

- **Style**
  - Segment labels in the distribution chart legend are visually highlighted if their percentage is outside the 0–100% range.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-03 22:41:55 +00:00
MananTank 1c6ed9bd0d [TOOL-4667] Dashboard: Various improvements in ERC20 asset creation flow (#7264)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on renaming references from "Token" to "Coin" throughout the codebase, along with some improvements in error handling and component structure.

### Detailed summary
- Renamed "Token" to "Coin" in various components and UI elements.
- Updated the `parseError` function to be exported.
- Introduced `ContractTypeCellUI` for better contract type rendering.
- Modified multi-step status handling to include more structured error messages.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
	- Enhanced multi-step status tracking with structured error messages and improved error display in step-based processes.

- **Improvements**
	- Updated all user-facing terminology from "Token" to "Coin" across creation, distribution, and sale interfaces.
	- Distribution and allocation charts now reflect "Coin" terminology and dynamically display the coin symbol when available.
	- Improved contract table to show "Coin" for relevant contract types.
	- Refined event tracking to trigger only when relevant actions occur.

- **Bug Fixes**
	- Error messages in step-based processes are now displayed more clearly with detailed information.

- **Refactor**
	- Streamlined contract type badge rendering for consistency and maintainability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-03 22:33:11 +00:00
MananTank 82035fd0b7 [TOOL-4670] Dashboard: Update Managed Engine CTA, UI tweaks (#7266)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new `bottomLineClassName` prop to the `TabLinks` and `TabPathLinks` components, enhancing customization options for styling. It also simplifies the UI in the `EmptyEngineState` component and updates the layout for better structure and readability.

### Detailed summary
- Added `bottomLineClassName` prop to `TabLinks` and `TabPathLinks`.
- Updated `TabLinks` to use `bottomLineClassName` for styling.
- Simplified `EmptyEngineState` content and button structure.
- Refactored layout in `Layout` component for improved organization.
- Adjusted `EngineLegacyBannerUI` for better spacing and button layout.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Refactor**
  - Updated navigation layout for the engine section with new tabbed navigation and improved header structure.
  - Adjusted container layouts for more consistent spacing and alignment.

- **Style**
  - Restyled the legacy banner, including button order, alignment, and icon adjustments for a clearer appearance.
  - Enhanced tab navigation with customizable bottom line styling for better visual integration.

- **Bug Fixes**
  - Unified the messaging and actions for adding a managed engine instance, removing plan-based differences for a more consistent user experience.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-03 22:21:32 +00:00
gregandGitHub 8b324597fb Feature: Update UB Docs for latest SDK and Guides (#7265) 2025-06-03 15:53:14 -05:00
FirekeeperandGitHub 5ebb7549ca [SDK] Force legacy for Plume 98866 (#7263) 2025-06-03 12:14:33 -07:00
MananTank dfe4cf0928 [TOOL-4645] Dashboard: Improve Project overview page loading performance (#7261)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on improving the loading state of various chart components in the dashboard by enhancing the `LoadingChartState` component with additional styling and refactoring the code to utilize asynchronous components for better performance and user experience.

### Detailed summary
- Updated `LoadingChartState` to accept a `className` prop for custom styling.
- Modified `Suspense` components in `Transactions`, `RpcMethodBarChartCard`, and `EngineCloudChartCard` to use improved `LoadingChartState`.
- Introduced new async components: `AsyncTotalSponsoredCard`, `AsyncAuthMethodDistributionCard`, and `AsyncAppHighlightsCard`.
- Refactored fetching logic for analytics data to use async/await pattern, improving readability and maintainability.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Refactor**
  - Improved loading placeholders across analytics cards by simplifying their appearance and updating styling for a more consistent look.
  - Modularized data fetching for analytics cards, allowing each card to load and handle errors independently for a smoother user experience.

- **New Features**
  - Loading states for analytics cards now display updated visual styles with borders and adjusted heights.

- **Style**
  - Unified and customizable loading chart styles for better visual consistency.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-03 16:31:36 +00:00
Joaquim VergesandGitHub 6cce31d828 [Dashboard] Add support for Sophon mainnet (#7258) 2025-06-03 01:59:10 -07:00
Joaquim VergesandGitHub aaad876a6f [Docs] Add Bundler API reference documentation (#7257) 2025-06-03 01:31:16 -07:00
gregandGitHub de297e4c97 [SDK] Feature: Track insufficient funds error across SDK (#7256) 2025-06-02 17:16:29 -05:00
pinkhoodie 2114e5f91f Add text input option to airdrop setup (#7238)
- Add manual text input alternative to CSV upload for airdrop addresses

- Support multiple input formats: space, comma, equals, and tab separated

- Parse text input and convert to CSV format for existing validation system

- Reuse all existing functionality: ENS resolution, address validation, duplicate removal

- Maintain same validation flow and error handling as CSV upload

- Change button text from 'Upload CSV' to 'Set up Airdrop' for clarity

![image](https://github.com/user-attachments/assets/e43afdd8-eed8-491a-ac93-9f02eb6ee703)
![image](https://github.com/user-attachments/assets/42522fa1-3886-4d68-a113-079d58b3da96)

<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR enhances the CSV upload functionality in the `TokenAirdropSection` component. It introduces a new method for processing CSV data, allows manual entry of addresses and amounts, and updates UI labels to reflect the new functionality.

### Detailed summary
- Added `processData` function in `useCsvUpload` for processing parsed CSV data.
- Updated `TokenAirdropSection` UI labels from "CSV File Uploaded" to "Airdrop List Set".
- Changed button text from "View CSV" to "View List".
- Introduced `parseTextInput` function for manual input parsing.
- Added text input for entering addresses and amounts.
- Implemented handling for text input submission.
- Updated the layout to separate CSV upload and manual entry sections.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added the ability to manually enter airdrop addresses and amounts using a text area, supporting multiple input formats.
  - Users can now choose between uploading a CSV file or entering addresses and amounts directly.

- **UI Updates**
  - Updated labels and button texts to reflect support for both CSV and manual list input.
  - Improved layout with a visual divider and clear instructions for manual entry.
  - Enhanced reset and validation behaviors for both input methods.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-02 20:57:20 +00:00
Phillip HoandGitHub ca0841745a fix: docs formatting for verified domains page (#7248) 2025-06-02 17:08:03 +08:00
Phillip HoandGitHub d150ea1c91 docs: Add page on domain verification (#7247) 2025-06-02 16:33:17 +08:00
saminacodes f1479b85f0 [Docs] Improvements to acct, ec, and vault docs (#7246)
CORE-0000

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating various documentation and component links within the `thirdweb` portal, as well as enhancing the content of FAQs and guides to provide clearer information on account management, migration to the TypeScript SDK v5, and Engine Cloud features.

### Detailed summary
- Added `Account` link to `supportLinks` in `Header.tsx`.
- Included warning about deprecation of TypeScript SDK v4 in `page.mdx`.
- Added troubleshooting details for transactions in `Engine Troubleshoot Guide`.
- Updated FAQ on customized plans in `faq/page.mdx`.
- Introduced ownership transfer steps in `manage-team/page.mdx`.
- Removed outdated Nebula access details in `faqs/page.mdx`.
- Added details about server wallet keys and admin key usage in `vault/faqs/page.mdx`.
- Updated pricing explanation and added a new chains support detail in `Engine Cloud` FAQ.
- Introduced a new section on `Smart Server Wallets`, detailing benefits and security in `server-wallets/page.mdx`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a new documentation page for Smart Server Wallets, outlining features, security, and pricing.
  - Added new FAQ entries to the Vault documentation regarding key requirements for server wallets and admin key handling.
  - Added a new FAQ entry detailing supported chains for Engine Cloud.

- **Improvements**
  - Updated navigation links in the header, moving "Account" to support links and adding a "Status" link.
  - Enhanced troubleshooting guidance for Engine Cloud transactions.
  - Updated Engine Cloud pricing FAQ to reference the pricing page.
  - Added a warning callout about TypeScript SDK v4 deprecation to the migration guide.
  - Expanded billing documentation with a new section on transferring team ownership.
  - Clarified the FAQ about signing up for customized or enterprise plans.

- **Removals**
  - Removed outdated Nebula FAQ entries related to access approval and pricing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-02 02:24:09 +00:00
Joaquim VergesandGitHub 283dc289fc [SDK] Include native tokens in Insight.getOwnedTokens (#7243) 2025-06-01 01:31:23 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
8abf3cc777 Version Packages (#7231)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-31 03:55:10 -07:00
Joaquim VergesandGitHub f10fbc8383 [SDK] Optimize fiat conversion query (#7242) 2025-05-31 03:14:45 -07:00
Joaquim VergesandGitHub 38627d352b [SDK] Handle smart account detection for inApp and ecosystem wallets (#7241) 2025-05-31 02:39:26 -07:00
Joaquim VergesandGitHub ad8cc9a1d2 [SDK] Add Bridge.tokens() function to retrieve supported tokens (#7240) 2025-05-31 02:29:12 -07:00
Manan TankandGitHub 4e935392fa SDK: Fallback to insight response if RPC request fails in getNFTs extensions (#7234) 2025-05-30 23:54:24 -07:00
MananTank f44d6ba282 [TOOL-4648] Dashboard: Fix timestamp in sponsored transactions table (#7236)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on refactoring the rendering logic of the `SponsoredTransactionsTableUI` component to improve clarity and functionality, particularly around handling timestamps and the rendering of table rows.

### Detailed summary
- Changed the way `transaction.timestamp` is handled to ensure it always ends with "Z".
- Simplified the mapping of `props.sponsoredTransactions` to use a block body for clarity.
- Added `client` prop to `ProjectCell`.
- Enhanced readability of the table structure by removing unnecessary code.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Bug Fixes**
	- Improved accuracy of transaction timestamps by ensuring all are treated as UTC.
	- Enhanced consistency in date and time display for sponsored transactions.

- **New Features**
	- Added support for passing client information to project, chain, and wallet address displays in the transactions table.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-30 23:41:53 +00:00
MananTank 3678054931 Dashboard: Fix TS errors (#7235)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the `clientId` prop being passed to the `PayEmbedFTUX` component in the `PayAnalytics.tsx` file.

### Detailed summary
- Changed the `clientId` prop from `props.clientId` to `props.projectClientId` in the return statement of the `PayEmbedFTUX` component when both `hasVolume` and `hasWallet` are false.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Bug Fixes**
  - Corrected an issue where the wrong client ID was passed to the PayEmbedFTUX component, ensuring accurate display and functionality.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-30 21:25:13 +00:00
gregandGitHub 196cb54ae9 [Dashboard] Feature: Adds PostHog tracking to bridge page (#7232) 2025-05-30 13:23:12 -07:00
gregandGitHub ed2d64197d [Dashboard] add empty state for Pay analytics (#7206) 2025-05-30 13:08:28 -07:00
Manan TankandGitHub e141e11ff3 Dashboard import paths cleanup (#7229) 2025-05-30 13:07:39 -07:00
MananTank 6ae54ddd98 Dashboard: Remove useThirdwebClient hook (#7228)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on integrating the `ThirdwebClient` into various components and functions across the application, enhancing the client functionality for better interaction with smart contracts and improving the overall architecture.

### Detailed summary
- Added `client` prop to multiple components for better contract interactions.
- Updated functions to utilize the `contract.client` instead of a generic client.
- Removed redundant imports of `useThirdwebClient`.
- Enhanced several UI components to accept and use the `client` prop.
- Modified API calls to pass the `client` where necessary for fetching data.
- Improved handling of ENS queries by passing the `client` directly.

> The following files were skipped due to too many changes: `apps/dashboard/src/components/contract-components/hooks.ts`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/connect/account-abstraction/factories/page.tsx`, `apps/dashboard/src/components/embedded-wallets/Configure/index.tsx`, `apps/dashboard/src/components/contract-components/contract-publish-form/index.tsx`, `apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx`

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Many components now accept a client instance as a prop, enabling more explicit and flexible client management throughout the dashboard.
- **Refactor**
  - Shifted from internal client initialization to explicit client prop injection across the app, affecting forms, tables, media components, contract publishing, and wallet settings.
  - Updated hooks and utility functions to require a client parameter instead of relying on internal context.
- **Chores**
  - Removed unused files and internal hooks related to client management.
- **Bug Fixes**
  - Improved consistency and reliability when handling authentication and client context in user flows.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-30 18:51:17 +00:00
gregandGitHub 8245c06b3e [SDK] Fix: Token price cache to use proper address (#7230) 2025-05-30 11:48:33 -07:00
MananTank 1c11f84481 Dashboard: Reduce useThirdwebClient hook usage #4 (#7227)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the `TransactionButton` component across various files by adding a `client` prop and other relevant properties to improve transaction handling and user interactions.

### Detailed summary
- Added `client` prop to `TransactionButton` in multiple components for transaction management.
- Included `isLoggedIn`, `txChainID`, and `transactionCount` props where applicable.
- Enhanced user experience by managing loading states and enabling/disabling buttons based on conditions.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - All transaction-related buttons and components now require and receive an explicit client instance, improving reliability and consistency across the dashboard.
- **Refactor**
  - Updated component interfaces to accept a client prop instead of relying on internal hooks.
  - Standardized the way client instances are passed through component hierarchies, including in forms, modals, and tables.
  - Renamed certain props for clarity (e.g., `clientId` to `projectClientId`).
- **Style**
  - No user-facing visual changes.
- **Chores**
  - Improved code maintainability by removing internal hook usage for client access.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-30 18:35:01 +00:00
MananTank b84db126d7 Dashboard: Reduce useThirdwebClient hook usage #3 (#7226)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on updating the usage of the `client` prop throughout various components in the application, replacing instances of `useThirdwebClient` with a more consistent approach using `getClientThirdwebClient`.

### Detailed summary
- Added `client` prop to multiple components including `AccountPage`, `DeployedContractsPage`, and `FaucetButton`.
- Replaced `useThirdwebClient` with `getClientThirdwebClient` in various files.
- Ensured consistency in passing the `client` prop across components.
- Updated the `ProfileHeader`, `PayModalButton`, and `SendTestTransaction` to accept `client` as a prop.
- Modified forms and sections to utilize the `client` prop for better integration with Thirdweb services.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Refactor**
  - Updated various components to receive a client instance as a prop instead of obtaining it internally, improving consistency and making client usage more explicit throughout the app.
  - Enhanced component interfaces to require a client prop where necessary, ensuring proper client context is passed across the dashboard.
  - Adjusted authentication and layout flows to support explicit client instantiation and propagation.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-30 18:35:01 +00:00
MananTank a4406214c6 Dashboard: Reduce useThirdwebClient hook usage #2 (#7224)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the functionality of various components by integrating a `client` prop of type `ThirdwebClient` into multiple files. This change improves the interaction with the Thirdweb SDK across the application.

### Detailed summary
- Added `client` prop to components like `IconUpload`, `SolidityInput`, and `CustomConnectWallet`.
- Updated hooks and functions to accept `client` for better SDK integration.
- Modified forms to utilize the `client` for storage uploads and contract deployments.
- Enhanced `AddChainToWallet` and `EcosystemHeader` to support `client`.
- Updated various fieldsets to include `client`, improving data handling and validation.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Components and forms now consistently accept and require a `client` prop, providing improved client context across wallet connection, contract deployment, network configuration, and NFT interactions.

- **Refactor**
  - Updated numerous components and hooks to require explicit passing of the `client` instance, replacing previous internal retrieval methods.
  - Prop types and interfaces were extended to include the new `client` property, ensuring consistent usage throughout the app.

- **Bug Fixes**
  - Addressed missing `client` props in several components to prevent potential issues with client-dependent functionality.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-30 18:35:01 +00:00
MananTank 02627631b6 Dashboard: Reduce useThirdwebClient hook usage (#7223)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on adding a `client` prop to various components across the dashboard application, enhancing their functionality and ensuring consistent access to the `ThirdwebClient`.

### Detailed summary
- Added `client` prop to `BatchMetadata`, `Royalty`, `Mintable`, `Transferable`, `Claimable`, and other components.
- Updated `WalletAddress` component to accept `client` prop in multiple instances.
- Enhanced various tables and UI components to utilize the `client` prop for improved data handling.
- Refactored existing components to ensure compatibility with the new `client` prop, ensuring consistent client access throughout the application.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Enhanced all WalletAddress displays across the dashboard to accept and utilize a new client context for improved integration and consistency.
- **Refactor**
  - Updated multiple components and their props to require and pass a client instance, ensuring unified client handling throughout tables, modals, and analytics views.
  - Replaced static table column definitions with dynamic, memoized columns that respond to client changes.
  - Standardized prop names related to client identification for clarity and maintainability.
- **Bug Fixes**
  - Ensured client context is consistently propagated to all relevant child components, preventing potential rendering or data issues.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-30 18:35:00 +00:00
4715 changed files with 125726 additions and 84962 deletions
+9 -9
View File
@@ -1,5 +1,11 @@
{
"___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",
{
@@ -7,21 +13,15 @@
}
],
"commit": false,
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": [
"playground-web",
"thirdweb-dashboard",
"wallet-ui",
"portal",
"thirdweb-login"
"nebula-app"
],
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
"updateInternalDependents": "always",
"onlyUpdatePeerDependentsWhenOutOfRange": true
},
"snapshot": {
"prereleaseTemplate": "{tag}-{commit}-{datetime}"
}
},
"updateInternalDependencies": "patch"
}
+26 -1
View File
@@ -3,7 +3,6 @@ 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"`.
@@ -101,3 +100,29 @@ Guidelines:
- 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);
}
```
+2 -2
View File
@@ -61,9 +61,9 @@ jobs:
uses: ./.github/composite-actions/install
- name: Setup Biome
uses: biomejs/setup-biome@f382a98e582959e6aaac8e5f8b17b31749018780 # v2.5.0
uses: biomejs/setup-biome@a9763ed3d2388f5746f9dc3e1a55df7f4609bc89 # v2.5.1
with:
version: latest
version: 2.0.4
- run: pnpm lint
+3 -3
View File
@@ -46,7 +46,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
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@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
uses: github/codeql-action/autobuild@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
# ℹ️ 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@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
+26 -27
View File
@@ -1,11 +1,32 @@
{
"editor.formatOnSave": true,
"[css]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[json]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports.biome": "explicit",
"quickfix.biome": "explicit"
"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"
],
@@ -15,27 +36,5 @@
"next/dist",
"^lucide-react/dist/lucide-react.suffixed$"
],
"typescript.tsdk": "node_modules/typescript/lib",
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[json]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[css]": {
"editor.defaultFormatter": "biomejs.biome"
},
"eslint.workingDirectories": [
{ "pattern": "./packages/*/" },
{ "pattern": "./apps/*/" }
]
"typescript.tsdk": "node_modules/typescript/lib"
}
+38 -1
View File
@@ -20,6 +20,7 @@ Welcome, AI copilots! This guide captures the coding standards, architectural de
- 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`
⸻
@@ -39,7 +40,8 @@ Welcome, AI copilots! This guide captures the coding standards, architectural de
- 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; Jest is pre‑configured.
- Keep tests deterministic and side‑effect free; Vitest is pre‑configured.
- to run the tests: `cd packages thirdweb & pnpm test:dev <filename>`
⸻
@@ -102,6 +104,41 @@ Welcome, AI copilots! This guide captures the coding standards, architectural de
-- 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
+290
View File
@@ -0,0 +1,290 @@
# 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
-11
View File
@@ -44,9 +44,6 @@ NEXT_PUBLIC_STRIPE_KEY=
NEXT_PUBLIC_STRIPE_PAYMENT_METHOD_CFG_ID=
# Needed for contract analytics / blockchain data information
CHAINSAW_API_KEY=
#
# Private (server)
#
@@ -65,17 +62,11 @@ 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"
@@ -100,8 +91,6 @@ REDIS_URL=""
ANALYTICS_SERVICE_URL=""
# Required for Nebula Chat
NEXT_PUBLIC_NEBULA_URL=""
# required for billing parts of the dashboard (team -> settings -> billing / invoices)
STRIPE_SECRET_KEY=""
+124 -110
View File
@@ -1,52 +1,98 @@
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 chakra
{
files: "src/chakra/**/*",
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
// these are provided by chakra folder, so we don't want to import them from chakra directly
importNames: [
"Card",
"Button",
@@ -81,48 +127,72 @@ module.exports = {
"StackProps",
],
message:
'Use the equivalent component from "tw-components" instead.',
'import component from "chakra" folder instead if you have to use chakra component, But use shadcn component otherwise',
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"',
'Import "posthog-js" directly only within the analytics helpers ("src/@/analytics/*"). Use the exported helpers from "@/analytics/track" elsewhere.',
name: "posthog-js",
},
],
},
],
},
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint", "react-compiler"],
parserOptions: {
ecmaVersion: 2019,
ecmaFeatures: {
impliedStrict: true,
jsx: true,
},
warnOnUnsupportedTypeScriptVersion: true,
"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: {
@@ -131,60 +201,4 @@ 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,
},
};
+1 -2
View File
@@ -13,9 +13,8 @@ const config: StorybookConfig = {
addons: [
getAbsolutePath("@storybook/addon-onboarding"),
getAbsolutePath("@storybook/addon-links"),
getAbsolutePath("@storybook/addon-essentials"),
getAbsolutePath("@chromatic-com/storybook"),
getAbsolutePath("@storybook/addon-interactions"),
getAbsolutePath("@storybook/addon-docs"),
],
framework: {
name: getAbsolutePath("@storybook/nextjs"),
+1 -1
View File
@@ -1,4 +1,4 @@
import type { Preview } from "@storybook/react";
import type { Preview } from "@storybook/nextjs";
import "../src/global.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { MoonIcon, SunIcon } from "lucide-react";
+2 -14
View File
@@ -1,16 +1,4 @@
{
"$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"
}
}
}
}
]
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
"extends": "//"
}
+12 -12
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,
muted: false,
runtimeId: "2023.09",
frequency: Frequency.EVERY_24H,
locations: ["us-east-1", "eu-west-1"],
tags: ["website"],
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,
playwrightConfig: {
use: {
baseURL: process.env.ENVIRONMENT_URL || "https://thirdweb.com",
},
},
browserChecks: {
frequency: Frequency.EVERY_24H,
testMatch: "./tests/**/*.spec.ts",
},
runtimeId: "2023.09",
tags: ["website"],
},
cli: {
runLocation: "eu-west-1",
},
logicalId: "thirdweb-www",
projectName: "thirdweb.com",
repoUrl: "https://github.com/thirdweb-dev/dashboard",
});
+12 -12
View File
@@ -1,17 +1,17 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/global.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
},
"rsc": true,
"style": "default",
"tailwind": {
"baseColor": "neutral",
"config": "tailwind.config.js",
"css": "src/global.css",
"cssVariables": true,
"prefix": ""
},
"tsx": true
}
+11 -10
View File
@@ -6,13 +6,14 @@ module.exports = [
"/bounties",
"/contact-us",
// -- product landing pages --
// -- connect
"/connect",
"/connect/sign-in",
"/connect/account-abstraction",
"/connect/universal-bridge",
"/connect/auth",
"/connect/in-app-wallets",
// -- build category
"/wallets",
"/account-abstraction",
"/universal-bridge",
"/auth",
"/in-app-wallets",
"/transactions",
// -- end build category
// -- storage
"/storage",
// -- nebula
@@ -24,8 +25,7 @@ module.exports = [
"/contracts/modular-contracts",
"/contracts/explore",
"/contracts/deployment-tool",
// -- engine
"/engine",
// -- solutions pages --
"/solutions/:solution_slug",
// -- campaigns --
@@ -40,7 +40,8 @@ module.exports = [
"/community/ambassadors",
"/community/startup-program",
// -- grants --
"/grant/superchain",
"/grants",
"/superchain",
// -- templates --
"/templates",
"/templates/:template_slug",
+7 -8
View File
@@ -1,19 +1,18 @@
{
"$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"
"src/@/components/misc/AnnouncementBanner.tsx",
"src/@/components/cmd-k-search/index.tsx",
"src/@/lib/search.ts"
],
"project": ["src/**"],
"ignoreBinaries": ["only-allow", "biome"],
"ignoreBinaries": ["only-allow"],
"ignoreDependencies": [
"@storybook/blocks",
"@thirdweb-dev/service-utils",
"@thirdweb-dev/vault-sdk",
"@types/color",
"fast-xml-parser"
]
],
"next": true,
"project": ["src/**"]
}
+34 -34
View File
@@ -44,12 +44,37 @@ async function getSingleChain(chainIdOrSlug) {
/** @type {import('next-sitemap').IConfig} */
module.exports = {
siteUrl: process.env.SITE_URL || "https://thirdweb.com",
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"],
generateRobotsTxt: true,
robotsTxtOptions: {
policies: [
{
userAgent: "*",
// allow all if production
allow: process.env.VERCEL_ENV === "production" ? ["/"] : [],
// disallow all if not production
@@ -58,10 +83,11 @@ module.exports = {
? ["/"]
: // disallow `/team` and `/team/*` if production
["/team", "/team/*"],
userAgent: "*",
},
],
},
exclude: ["/chain/validate"],
siteUrl: process.env.SITE_URL || "https://thirdweb.com",
transform: async (config, _path) => {
let path = _path;
@@ -75,40 +101,14 @@ 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) => {
const [framerUrls, allChains] = await Promise.all([
getFramerXML(),
fetchChainsFromApi(),
]);
return [
...framerUrls.map((url) => {
return {
loc: url.loc,
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 +131,10 @@ async function createSearchRecordSitemaps(config) {
parsedLines.map((parsedLine) => {
return getSingleChain(parsedLine.chain_id)
.then((parsedLineChain) => ({
loc: `/${parsedLineChain.slug}/${parsedLine.contract_address}`,
changefreq: config.changefreq,
priority: config.priority,
lastmod: config.autoLastmod ? new Date().toISOString() : undefined,
loc: `/${parsedLineChain.slug}/${parsedLine.contract_address}`,
priority: config.priority,
}))
.catch(() => null);
}),
+65 -53
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;
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;
connect-src * data: blob:;
worker-src 'self' blob:;
block-all-mixed-content;
@@ -46,91 +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({
protocol: "https",
hostname: `${process.env.API_ROUTES_CLIENT_ID}.thirdwebstorage-dev.com`,
protocol: "https",
});
} else {
// this should only happen in development
remotePatterns.push({
protocol: "https",
hostname: "ipfs.io",
protocol: "https",
});
}
// 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({
protocol: "https",
hostname: `${process.env.NEXT_PUBLIC_DASHBOARD_CLIENT_ID}.thirdwebstorage-dev.com`,
protocol: "https",
});
}
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",
// 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: {
webpackBuildWorker: true,
webpackMemoryOptimizations: true,
serverSourceMaps: false,
taint: true,
webpackBuildWorker: true,
webpackMemoryOptimizations: true,
},
serverExternalPackages: ["pino-pretty"],
async headers() {
return [
{
// Apply these headers to all routes in your application.
source: "/(.*)",
headers: [
...securityHeaders,
{
@@ -138,56 +137,69 @@ 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 [
{
source: "/thirdweb.eth",
destination: "/deployer.thirdweb.eth",
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",
},
{
source: "/thirdweb.eth/:path*",
destination: "/deployer.thirdweb.eth/:path*",
source: "/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)
{
source: "/home",
destination: "https://landing.thirdweb.com",
source: "/home",
},
// flatmap the framer paths for the default language and the additional languages
...FRAMER_PATHS.flatMap((path) => [
{
source: path,
destination: `https://landing.thirdweb.com${path}`,
source: path,
},
// this is for additional languages
...FRAMER_ADDITIONAL_LANGUAGES.map((lang) => ({
source: `/${lang}${path}`,
destination: `https://landing.thirdweb.com/${lang}${path}`,
source: `/${lang}${path}`,
})),
]),
];
},
images: {
dangerouslyAllowSVG: true,
contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",
remotePatterns: [
{
protocol: "https",
hostname: "**.thirdweb.com",
},
...determineIpfsGateways(),
],
},
compiler: {
emotion: true,
},
reactStrictMode: true,
serverExternalPackages: ["pino-pretty"],
};
function getConfig(): NextConfig {
+68 -74
View File
@@ -1,26 +1,4 @@
{
"name": "thirdweb-dashboard",
"version": "3.0.0",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
"dev": "next dev --turbopack",
"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",
@@ -29,30 +7,30 @@
"@emotion/styled": "11.14.0",
"@hookform/resolvers": "^3.9.1",
"@marsidev/react-turnstile": "^1.1.0",
"@radix-ui/react-accordion": "^1.2.7",
"@radix-ui/react-alert-dialog": "^1.1.10",
"@radix-ui/react-avatar": "^1.1.6",
"@radix-ui/react-checkbox": "^1.2.2",
"@radix-ui/react-dialog": "1.1.10",
"@radix-ui/react-dropdown-menu": "^2.1.11",
"@radix-ui/react-hover-card": "^1.1.10",
"@radix-ui/react-label": "^2.1.4",
"@radix-ui/react-popover": "^1.1.10",
"@radix-ui/react-progress": "^1.1.4",
"@radix-ui/react-radio-group": "^1.3.3",
"@radix-ui/react-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.0",
"@radix-ui/react-switch": "^1.2.2",
"@radix-ui/react-tooltip": "1.2.3",
"@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.13.0",
"@shazow/whatsabi": "0.21.0",
"@tanstack/react-query": "5.74.4",
"@sentry/nextjs": "9.29.0",
"@shazow/whatsabi": "0.22.2",
"@tanstack/react-query": "5.80.7",
"@tanstack/react-table": "^8.21.3",
"@thirdweb-dev/service-utils": "workspace:*",
"@thirdweb-dev/vault-sdk": "workspace:*",
"@vercel/functions": "2.0.0",
"@vercel/functions": "2.1.0",
"@vercel/og": "^0.6.8",
"abitype": "1.0.8",
"chakra-react-select": "^4.7.6",
@@ -61,17 +39,14 @@
"color": "5.0.0",
"compare-versions": "^6.1.0",
"date-fns": "4.1.0",
"fast-xml-parser": "^5.2.1",
"fetch-event-stream": "0.1.5",
"flat": "^6.0.1",
"framer-motion": "12.9.2",
"fast-xml-parser": "^5.2.5",
"framer-motion": "12.17.0",
"fuse.js": "7.1.0",
"idb-keyval": "^6.2.1",
"input-otp": "^1.4.1",
"ioredis": "^5.6.1",
"ipaddr.js": "^2.2.0",
"lucide-react": "0.503.0",
"next": "15.3.2",
"lucide-react": "0.514.0",
"next": "15.3.3",
"next-plausible": "^3.12.4",
"next-themes": "^0.4.6",
"nextjs-toploader": "^1.6.12",
@@ -79,7 +54,7 @@
"p-limit": "^6.2.0",
"papaparse": "^5.5.3",
"pluralize": "^8.0.0",
"posthog-js": "1.67.1",
"posthog-js": "1.252.0",
"prettier": "3.5.3",
"qrcode": "^1.5.3",
"react": "19.1.0",
@@ -96,55 +71,74 @@
"responsive-rsc": "0.0.7",
"server-only": "^0.0.1",
"shiki": "1.27.0",
"sonner": "2.0.3",
"sonner": "2.0.5",
"spdx-correct": "^3.2.0",
"stripe": "17.7.0",
"swagger-ui-react": "^5.21.0",
"swagger-ui-react": "^5.24.1",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7",
"thirdweb": "workspace:*",
"tiny-invariant": "^1.3.3",
"use-debounce": "^10.0.4",
"zod": "3.25.24"
"use-debounce": "^10.0.5",
"vaul": "^1.1.2",
"zod": "3.25.62"
},
"devDependencies": {
"@biomejs/biome": "2.0.4",
"@chakra-ui/cli": "^2.4.1",
"@chromatic-com/storybook": "3.2.6",
"@next/bundle-analyzer": "15.3.2",
"@next/eslint-plugin-next": "15.3.2",
"@playwright/test": "1.52.0",
"@storybook/addon-essentials": "8.6.14",
"@storybook/addon-interactions": "8.6.14",
"@storybook/addon-links": "8.6.14",
"@storybook/addon-onboarding": "8.6.14",
"@storybook/addon-viewport": "8.6.14",
"@storybook/blocks": "8.6.14",
"@storybook/nextjs": "8.6.14",
"@storybook/react": "8.6.14",
"@storybook/test": "8.6.14",
"@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",
"@types/color": "4.2.0",
"@types/node": "22.14.1",
"@types/papaparse": "^5.3.16",
"@types/pluralize": "^0.0.33",
"@types/qrcode": "^1.5.5",
"@types/react": "19.1.4",
"@types/react-dom": "19.1.5",
"@types/react": "19.1.8",
"@types/react-dom": "19.1.6",
"@types/react-table": "^7.7.20",
"@types/spdx-correct": "^3.1.3",
"@types/swagger-ui-react": "^5.18.0",
"@typescript-eslint/eslint-plugin": "7.14.1",
"@typescript-eslint/parser": "7.14.1",
"autoprefixer": "^10.4.21",
"checkly": "5.2.0",
"checkly": "5.6.0",
"eslint": "8.57.0",
"eslint-config-biome": "1.9.4",
"eslint-plugin-react-compiler": "19.0.0-beta-e993439-20250405",
"eslint-plugin-storybook": "0.12.0",
"knip": "5.56.0",
"eslint-plugin-react-compiler": "19.1.0-rc.2",
"eslint-plugin-storybook": "9.0.8",
"knip": "5.60.2",
"next-sitemap": "^4.2.3",
"postcss": "8.5.3",
"storybook": "8.6.14",
"postcss": "8.5.5",
"storybook": "9.0.8",
"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/chakra/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"
}
+18 -18
View File
@@ -10,25 +10,10 @@ import { defineConfig, devices } from "@playwright/test";
* 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",
},
/* Run tests in files in parallel */
fullyParallel: true,
/* Configure projects for major browsers */
projects: [
@@ -67,13 +52,28 @@ 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",
url: "http://127.0.0.1:3000",
reuseExistingServer: !process.env.CI,
url: "http://127.0.0.1:3000",
},
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
});
+1 -1
View File
@@ -1,6 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
tailwindcss: {},
},
};
Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 626 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

@@ -1,3 +0,0 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.33106 4.3C8.33109 4.3 7.52045 5.11064 7.52045 6.11061C7.52045 7.11058 8.33109 7.92121 9.33106 7.92121C10.331 7.92121 11.1417 7.11058 11.1417 6.11061C11.1417 5.11064 10.331 4.3 9.33106 4.3ZM5.22045 6.11061C5.22045 3.84038 7.06084 2 9.33106 2C11.6013 2 13.4417 3.84038 13.4417 6.11061C13.4417 8.38083 11.6013 10.2212 9.33106 10.2212C7.06084 10.2212 5.22045 8.38083 5.22045 6.11061ZM4.20397 12.0858C4.97486 11.3149 6.0204 10.8818 7.11061 10.8818H11.5515C12.6417 10.8818 13.6873 11.3149 14.4581 12.0858C14.9073 12.5349 14.9073 13.263 14.4581 13.7121C14.009 14.1612 13.2809 14.1612 12.8318 13.7121C12.4922 13.3726 12.0317 13.1818 11.5515 13.1818H7.11061C6.6304 13.1818 6.16987 13.3726 5.83031 13.7121C5.49076 14.0517 5.3 14.5122 5.3 14.9924V16.4727C5.3 17.1079 4.78513 17.6227 4.15 17.6227C3.51487 17.6227 3 17.1079 3 16.4727V14.9924C3 13.9022 3.43308 12.8567 4.20397 12.0858ZM17.5457 14.9598C17.9948 14.5107 17.9948 13.7825 17.5457 13.3334C17.0966 12.8843 16.3685 12.8843 15.9194 13.3334L13.7719 15.4808L13.1048 14.8137C12.6557 14.3646 11.9276 14.3646 11.4785 14.8137C11.0294 15.2628 11.0294 15.991 11.4785 16.4401L12.9588 17.9204C13.4079 18.3695 14.136 18.3695 14.5851 17.9204L17.5457 14.9598Z" fill="#646D7A"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

@@ -1,4 +0,0 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.4444 3H4.55556C3.69645 3 3 3.69645 3 4.55556V15.4444C3 16.3036 3.69645 17 4.55556 17H15.4444C16.3036 17 17 16.3036 17 15.4444V4.55556C17 3.69645 16.3036 3 15.4444 3Z" stroke="#646D7A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.3333 7.66666L7.66663 12.3333" stroke="#646D7A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 492 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

+157 -101
View File
@@ -1,64 +1,114 @@
const legacyDashboardToTeamRedirects = [
{
source: "/dashboard",
destination: "/team",
permanent: false,
source: "/dashboard",
},
{
source: "/dashboard/contracts/:path*",
destination: "/team/~/~/contracts",
permanent: false,
source: "/dashboard/contracts/:path*",
},
{
source: "/dashboard/connect/ecosystem/:path*",
destination: "/team/~/~/ecosystem/:path*",
permanent: false,
source: "/dashboard/connect/ecosystem/:path*",
},
{
source: "/dashboard/engine/:path*",
destination: "/team/~/~/engine/:path*",
permanent: false,
source: "/dashboard/engine/:path*",
},
{
source: "/dashboard/settings/api-keys",
destination: "/team",
permanent: false,
source: "/dashboard/settings/api-keys",
},
{
source: "/dashboard/settings/devices",
destination: "/account/devices",
permanent: false,
source: "/dashboard/settings/devices",
},
{
source: "/dashboard/settings/billing",
destination: "/team/~/~/settings/billing",
permanent: false,
source: "/dashboard/settings/billing",
},
{
source: "/dashboard/settings/gas-credits",
destination: "/team/~/~/settings/credits",
permanent: false,
source: "/dashboard/settings/gas-credits",
},
{
source: "/dashboard/settings/usage",
destination: "/team/~/~/usage",
permanent: false,
source: "/dashboard/settings/usage",
},
{
source: "/dashboard/settings/storage",
destination: "/team/~/~/usage/storage",
permanent: false,
source: "/dashboard/settings/storage",
},
{
source: "/dashboard/settings/notifications",
destination: "/team/~/~/settings/notifications",
permanent: false,
source: "/dashboard/settings/notifications",
},
// rest of the /dashboard/* routes
{
source: "/dashboard/:path*",
destination: "/team",
permanent: false,
source: "/dashboard/:path*",
},
];
const projectRoute = "/team/:team_slug/:project_slug";
const projectPageRedirects = [
{
destination: `${projectRoute}/universal-bridge/:path*`,
permanent: false,
source: `${projectRoute}/connect/pay/:path*`,
},
{
destination: `${projectRoute}/universal-bridge/:path*`,
permanent: false,
source: `${projectRoute}/connect/universal-bridge/:path*`,
},
{
destination: `${projectRoute}/account-abstraction/:path*`,
permanent: false,
source: `${projectRoute}/connect/account-abstraction/:path*`,
},
{
destination: `${projectRoute}/wallets/:path*`,
permanent: false,
source: `${projectRoute}/connect/in-app-wallets/:path*`,
},
{
destination: `${projectRoute}/vault/:path*`,
permanent: false,
source: `${projectRoute}/engine/cloud/vault/:path*`,
},
{
destination: `${projectRoute}/transactions/:path*`,
permanent: false,
source: `${projectRoute}/engine/cloud/:path*`,
},
{
destination: `${projectRoute}/tokens/:path*`,
permanent: false,
source: `${projectRoute}/assets/:path*`,
},
{
destination: projectRoute,
permanent: false,
source: `${projectRoute}/nebula/:path*`,
},
{
source: `${projectRoute}/connect/analytics`,
destination: `${projectRoute}`,
permanent: false,
},
];
@@ -66,310 +116,316 @@ const legacyDashboardToTeamRedirects = [
async function redirects() {
return [
{
source: "/portal/:match*",
destination: "https://portal.thirdweb.com/:match*",
permanent: true,
source: "/portal/:match*",
},
{
source: "/solutions/appchain-api",
destination: "/solutions/chains",
permanent: true,
source: "/solutions/appchain-api",
},
{
source: "/contracts/release",
destination: "/contracts/publish",
permanent: false,
source: "/contracts/release",
},
{
source: "/contracts/release/:path*",
destination: "/contracts/publish/:path*",
permanent: false,
source: "/contracts/release/:path*",
},
{
source: "/release",
destination: "/publish",
permanent: false,
source: "/release",
},
{
source: "/release/:path*",
destination: "/publish/:path*",
permanent: false,
source: "/release/:path*",
},
{
source: "/authentication",
destination: "/auth",
permanent: false,
source: "/authentication",
},
{
source: "/extensions",
destination: "/build",
permanent: false,
source: "/extensions",
},
{
source: "/contractkit",
destination: "/build",
permanent: true,
source: "/contractkit",
},
// old (deprecated) routes
{
source:
"/:network/(edition|nft-collection|token|nft-drop|signature-drop|edition-drop|token-drop|vote)/:address",
destination: "/:network/:address",
permanent: false,
source:
"/:network/(edition|nft-collection|token|nft-drop|signature-drop|edition-drop|token-drop|vote)/:address",
},
// prebuilt contract deploys
{
source: "/contracts/new/:slug*",
destination: "/explore",
permanent: false,
source: "/contracts/new/:slug*",
},
// deployer to non-deployer url
// handled directly in SSR as well
{
source: "/deployer.thirdweb.eth",
destination: "/thirdweb.eth",
permanent: true,
source: "/deployer.thirdweb.eth",
},
{
source: "/deployer.thirdweb.eth/:path*",
destination: "/thirdweb.eth/:path*",
permanent: true,
source: "/deployer.thirdweb.eth/:path*",
},
{
source: "/chains",
destination: "/chainlist",
permanent: true,
source: "/chains",
},
// polygon zkevm beta to non-beta
{
source: "/polygon-zkevm-beta",
destination: "/polygon-zkevm",
permanent: false,
source: "/polygon-zkevm-beta",
},
// backwards compat: page moved to pages/settings/devices
{
source: "/template/nft-drop",
destination: "/template/erc721",
permanent: false,
source: "/template/nft-drop",
},
{
destination: "/team",
permanent: false,
source: "/create-api-key",
destination: "/team",
permanent: false,
},
{
destination: "/team",
permanent: false,
source: "/dashboard/settings",
destination: "/team",
permanent: false,
},
{
source: "/dashboard/connect/playground",
destination: "https://playground.thirdweb.com/connect/sign-in/button",
permanent: false,
source: "/dashboard/connect/playground",
},
{
source: "/dashboard/infrastructure/storage",
destination: "/dashboard/settings/storage",
permanent: false,
source: "/dashboard/infrastructure/storage",
},
{
source: "/dashboard/infrastructure/rpc-edge",
destination: "/chainlist",
permanent: false,
source: "/dashboard/infrastructure/rpc-edge",
},
{
source: "/solutions/commerce",
destination: "/solutions/loyalty",
permanent: false,
source: "/solutions/commerce",
},
{
source: "/hackathon/base-consumer-crypto",
destination: "/hackathon/consumer-crypto",
permanent: false,
source: "/hackathon/base-consumer-crypto",
},
{
source: "/bear-market-airdrop",
destination: "/",
permanent: false,
source: "/bear-market-airdrop",
},
{
source: "/drops/optimism",
destination: "/optimism",
permanent: false,
source: "/drops/optimism",
},
// Redirecting as ambassadors lives in community now
{
source: "/ambassadors",
destination: "/community/ambassadors",
permanent: false,
source: "/ambassadors",
},
{
source: "/embedded-wallets",
destination: "/in-app-wallets",
permanent: false,
source: "/embedded-wallets",
},
// temporarily redirect cli login to support page
{
source: "/cli/login",
destination:
"https://support.thirdweb.com/troubleshooting-errors/7Y1BqKNvtLdBv5fZkRZZB3/issue-linking-device-on-the-authorization-page-via-thirdweb-cli/cn9LRA3ax7XCP6uxwRYdvx",
"https://portal.thirdweb.com/knowledge-base/onchain-common-errors/thirdweb-cli/device-link-error",
permanent: false,
source: "/cli/login",
},
// temporary redirect gas -> explore page
{
source: "/gas",
destination: "/explore",
permanent: false,
source: "/gas",
},
{
destination: "/contracts/deployment-tool",
permanent: false,
source: "/deploy",
destination: "/contracts/deployment-tool",
permanent: false,
},
{
destination: "/contracts/deployment-tool",
permanent: false,
source: "/publish",
destination: "/contracts/deployment-tool",
permanent: false,
},
{
source: "/smart-contracts",
destination: "/contracts/explore",
permanent: false,
source: "/smart-contracts",
},
{
source: "/build",
destination: "/contracts/modular-contracts",
destination: "/sdk",
permanent: false,
},
{
source: "/ui-components",
destination: "/sdk",
permanent: false,
},
{
destination: "/sdk",
permanent: false,
source: "/interact",
destination: "/sdk",
permanent: false,
},
{
source: "/sponsored-transactions",
destination: "/account-abstraction",
permanent: false,
source: "/sponsored-transactions",
},
// redirect /solutions/chains to /solutions/ecosystem
{
source: "/solutions/chains",
destination: "/solutions/ecosystem",
permanent: false,
source: "/solutions/chains",
},
// redirect /rpc to portal
{
source: "/rpc-edge",
destination:
"https://portal.thirdweb.com/infrastructure/rpc-edge/overview",
permanent: false,
source: "/rpc-edge",
},
// redirect /sdk to portal
{
source: "/sdk",
destination: "https://portal.thirdweb.com/connect/blockchain-api",
permanent: false,
source: "/sdk",
},
// redirect `/events` to homepage
{
source: "/events",
destination: "/",
permanent: false,
source: "/events",
},
// redirect /community to /community/ambassadors
{
source: "/community",
destination: "/community/ambassadors",
permanent: false,
source: "/community",
},
// redirect `/tos` to `/terms`
{
source: "/tos",
destination: "/terms",
permanent: false,
source: "/tos",
},
// redirect `/privacy` to `/privacy-policy`
{
source: "/privacy",
destination: "/privacy-policy",
permanent: false,
source: "/privacy",
},
// redirect `/mission` to `/home`
{
source: "/mission",
destination: "/home",
permanent: false,
source: "/mission",
},
// redirect "/open-source" to "/bounties"
{
source: "/open-source",
destination: "/bounties",
permanent: false,
source: "/open-source",
},
// redirect /template/<slug> to /templates/<slug>
{
source: "/template/:slug",
destination: "/templates/:slug",
permanent: false,
source: "/template/:slug",
},
// redirect /account-abstraction to /connect/account-abstraction
// redirect /connect/pay to /universal-bridge
{
source: "/account-abstraction",
destination: "/connect/account-abstraction",
destination: "/universal-bridge",
permanent: false,
},
// redirect /connect/pay to /connect/universal-bridge
{
source: "/connect/pay",
destination: "/connect/universal-bridge",
permanent: false,
},
// redirect /auth to /connect/auth
{
source: "/auth",
destination: "/connect/auth",
permanent: false,
},
// redirect /in-app-wallets to /connect/in-app-wallets
{
source: "/in-app-wallets",
destination: "/connect/in-app-wallets",
permanent: false,
},
// PREVIOUS CAMPAIGNS
{
source: "/unlimited-wallets",
destination: "/",
permanent: false,
source: "/unlimited-wallets",
},
// pay > universal-bridge redirect
{
source: "/team/:team_slug/:project_slug/connect/pay/:path*",
destination:
"/team/:team_slug/:project_slug/connect/universal-bridge/:path*",
permanent: false,
},
// all /learn/tutorials (and sub-routes) -> /learn/guides
{
source: "/learn/tutorials/:path*",
destination: "/learn/guides/:path*",
permanent: false,
source: "/learn/tutorials/:path*",
},
{
source: "/learn/tutorials",
destination: "/learn/guides",
permanent: false,
source: "/learn/tutorials",
},
// redirect to /grant/superchain to /superchain
{
destination: "/superchain",
permanent: false,
source: "/grant/superchain",
},
// connect -> build redirects
{
destination: "/wallets",
permanent: false,
source: "/connect",
},
{
destination: "/account-abstraction",
permanent: false,
source: "/connect/account-abstraction",
},
{
destination: "/universal-bridge",
permanent: false,
source: "/connect/universal-bridge",
},
{
destination: "/auth",
permanent: false,
source: "/connect/auth",
},
{
destination: "/in-app-wallets",
permanent: false,
source: "/connect/in-app-wallets",
},
{
destination: "/transactions",
permanent: false,
source: "/engine",
},
...legacyDashboardToTeamRedirects,
...projectPageRedirects,
];
}
+9 -9
View File
@@ -1,7 +1,7 @@
"use server";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
import { getAuthToken } from "@/api/auth-token";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
export async function acceptInvite(options: {
teamId: string;
@@ -11,20 +11,20 @@ export async function acceptInvite(options: {
if (!token) {
return {
ok: false,
errorMessage: "You are not authorized to perform this action",
ok: false,
};
}
const res = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${options.teamId}/invites/${options.inviteId}/accept`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({}),
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
method: "POST",
},
);
@@ -42,8 +42,8 @@ export async function acceptInvite(options: {
} catch {}
return {
ok: false,
errorMessage,
ok: false,
};
}
+7 -7
View File
@@ -1,7 +1,7 @@
"use server";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
import { getAuthToken } from "@/api/auth-token";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
export async function reSubscribePlan(options: {
teamId: string;
@@ -16,12 +16,12 @@ export async function reSubscribePlan(options: {
const res = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${options.teamId}/checkout/resubscribe-plan`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({}),
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
method: "PUT",
},
);
+7 -7
View File
@@ -1,7 +1,7 @@
"use server";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
import { getAuthToken } from "@/api/auth-token";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
export async function confirmEmailWithOTP(otp: string) {
const token = await getAuthToken();
@@ -15,14 +15,14 @@ export async function confirmEmailWithOTP(otp: string) {
const res = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/account/confirmEmail`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
confirmationToken: otp,
}),
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
method: "PUT",
},
);
@@ -0,0 +1,72 @@
"use server";
import "server-only";
// biome-ignore lint/style/useNodejsImportProtocol: breaks storybook if it's `node:` prefixed
import { randomBytes } from "crypto";
import { format } from "date-fns";
import { getAuthToken } from "@/api/auth-token";
import type { Team } from "@/api/team";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
export async function createTeam(options?: { name?: string; slug?: string }) {
const token = await getAuthToken();
if (!token) {
return {
errorMessage: "You are not authorized to perform this action",
status: "error",
} as const;
}
const res = await fetch(`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams`, {
body: JSON.stringify({
billingEmail: null,
image: null,
name:
options?.name ?? `Your Projects ${format(new Date(), "MMM d yyyy")}`,
slug: options?.slug ?? randomBytes(20).toString("hex"),
}),
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
method: "POST",
});
if (!res.ok) {
const reason = await res.text();
console.error("failed to create team", {
reason,
status: res.status,
});
switch (res.status) {
case 400: {
return {
errorMessage: "Invalid team name or slug.",
status: "error",
} as const;
}
case 401: {
return {
errorMessage: "You are not authorized to perform this action.",
status: "error",
} as const;
}
default: {
return {
errorMessage: "An unknown error occurred.",
status: "error",
} as const;
}
}
}
const json = (await res.json()) as {
result: Team;
};
return {
data: json.result,
status: "success",
} as const;
}
@@ -0,0 +1,68 @@
"use server";
import "server-only";
import { getAuthToken } from "@/api/auth-token";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
export async function deleteTeam(options: { teamId: string }) {
const token = await getAuthToken();
if (!token) {
return {
errorMessage: "You are not authorized to perform this action.",
status: "error",
} as const;
}
const res = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${options.teamId}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
method: "DELETE",
},
);
// handle errors
if (!res.ok) {
const reason = await res.text();
console.error("failed to delete team", {
reason,
status: res.status,
});
switch (res.status) {
case 400: {
return {
errorMessage: "Invalid team ID.",
status: "error",
} as const;
}
case 401: {
return {
errorMessage: "You are not authorized to perform this action.",
status: "error",
} as const;
}
case 403: {
return {
errorMessage: "You do not have permission to delete this team.",
status: "error",
} as const;
}
case 404: {
return {
errorMessage: "Team not found.",
status: "error",
} as const;
}
default: {
return {
errorMessage: "An unknown error occurred.",
status: "error",
} as const;
}
}
}
return {
status: "success",
} as const;
}
@@ -1,9 +1,9 @@
"use server";
import { defineDashboardChain } from "lib/defineDashboardChain";
import { ZERO_ADDRESS, isAddress, toTokens } from "thirdweb";
import { isAddress, toTokens, ZERO_ADDRESS } from "thirdweb";
import { getWalletBalance } from "thirdweb/wallets";
import { MORALIS_API_KEY } from "../constants/server-envs";
import { serverThirdwebClient } from "../constants/thirdweb-client.server";
import { MORALIS_API_KEY } from "@/constants/server-envs";
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
import { defineDashboardChain } from "@/lib/defineDashboardChain";
type BalanceQueryResponse = Array<{
balance: string;
@@ -43,12 +43,12 @@ export async function getTokenBalancesFromMoralis(params: {
});
return [
{
token_address: ZERO_ADDRESS,
symbol: balance.symbol,
name: "Native Token",
decimals: balance.decimals,
balance: balance.value.toString(),
decimals: balance.decimals,
display_balance: toTokens(balance.value, balance.decimals),
name: "Native Token",
symbol: balance.symbol,
token_address: ZERO_ADDRESS,
},
];
};
@@ -59,10 +59,10 @@ export async function getTokenBalancesFromMoralis(params: {
const tokenBalanceEndpoint = `https://deep-index.moralis.io/api/v2/${_address}/erc20?chain=${_chain}`;
const resp = await fetch(tokenBalanceEndpoint, {
method: "GET",
headers: {
"x-api-key": MORALIS_API_KEY,
},
method: "GET",
});
if (!resp.ok) {
@@ -83,7 +83,7 @@ export async function getTokenBalancesFromMoralis(params: {
]);
return {
error: undefined,
data: [...nativeBalance, ...tokenBalances],
error: undefined,
};
}
+24 -24
View File
@@ -1,19 +1,19 @@
"use server";
import { NEXT_PUBLIC_DASHBOARD_CLIENT_ID } from "@/constants/public-envs";
import { MORALIS_API_KEY } from "@/constants/server-envs";
import {
generateAlchemyUrl,
transformAlchemyResponseToNFT,
} from "lib/wallet/nfts/alchemy";
} from "@/lib/wallet/nfts/alchemy";
import {
generateMoralisUrl,
transformMoralisResponseToNFT,
} from "lib/wallet/nfts/moralis";
import type { WalletNFT } from "lib/wallet/nfts/types";
import { getVercelEnv } from "../../lib/vercel-utils";
import { isAlchemySupported } from "../../lib/wallet/nfts/isAlchemySupported";
import { isMoralisSupported } from "../../lib/wallet/nfts/isMoralisSupported";
import { NEXT_PUBLIC_DASHBOARD_CLIENT_ID } from "../constants/public-envs";
import { MORALIS_API_KEY } from "../constants/server-envs";
} from "@/lib/wallet/nfts/moralis";
import type { WalletNFT } from "@/lib/wallet/nfts/types";
import { getVercelEnv } from "@/utils/vercel";
import { isAlchemySupported } from "../lib/wallet/nfts/isAlchemySupported";
import { isMoralisSupported } from "../lib/wallet/nfts/isMoralisSupported";
type WalletNFTApiReturn =
| { result: WalletNFT[]; error?: undefined }
@@ -53,7 +53,7 @@ export async function getWalletNFTs(params: {
const parsedResponse = await response.json();
const result = await transformAlchemyResponseToNFT(parsedResponse, owner);
return { result, error: undefined };
return { error: undefined, result };
} catch (err) {
console.error("Error fetching NFTs", err);
return { error: "error parsing response" };
@@ -64,10 +64,10 @@ export async function getWalletNFTs(params: {
const url = generateMoralisUrl({ chainId, owner });
const response = await fetch(url, {
method: "GET",
headers: {
"X-API-Key": MORALIS_API_KEY,
},
method: "GET",
next: {
revalidate: 10, // cache for 10 seconds
},
@@ -156,8 +156,8 @@ async function getWalletNFTsFromInsight(params: {
if (!response.ok) {
const errorMessage = await response.text();
return {
ok: false,
error: errorMessage,
ok: false,
};
}
@@ -172,32 +172,32 @@ async function getWalletNFTsFromInsight(params: {
const walletNFTs = nftsResponse.data.map((nft) => {
const walletNFT: WalletNFT = {
id: nft.token_id,
chainId: nft.contract.chain_id,
contractAddress: nft.contract.address,
id: nft.token_id,
metadata: {
uri: isDev
? nft.metadata_url.replace("ipfscdn.io/", "thirdwebstorage-dev.com/")
: nft.metadata_url,
name: nft.name,
description: nft.description,
image: isDev
? nft.image_url.replace("ipfscdn.io/", "thirdwebstorage-dev.com/")
: nft.image_url,
animation_url: isDev
? nft.extra_metadata.animation_original_url?.replace(
"ipfscdn.io/",
"thirdwebstorage-dev.com/",
)
: nft.extra_metadata.animation_original_url,
external_url: nft.external_url,
background_color: nft.background_color,
description: nft.description,
external_url: nft.external_url,
image: isDev
? nft.image_url.replace("ipfscdn.io/", "thirdwebstorage-dev.com/")
: nft.image_url,
name: nft.name,
uri: isDev
? nft.metadata_url.replace("ipfscdn.io/", "thirdwebstorage-dev.com/")
: nft.metadata_url,
},
owner: params.owner,
tokenURI: nft.metadata_url,
type: nft.token_type === "erc721" ? "ERC721" : "ERC1155",
supply: nft.balance,
tokenAddress: nft.contract.address,
chainId: nft.contract.chain_id,
tokenURI: nft.metadata_url,
type: nft.token_type === "erc721" ? "ERC721" : "ERC1155",
};
return walletNFT;
+11 -11
View File
@@ -1,11 +1,11 @@
"use server";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { getAuthToken } from "@/api/auth-token";
import {
NEXT_PUBLIC_ENGINE_CLOUD_URL,
NEXT_PUBLIC_THIRDWEB_API_HOST,
} from "../constants/public-envs";
import { ANALYTICS_SERVICE_URL } from "../constants/server-envs";
} from "@/constants/public-envs";
import { ANALYTICS_SERVICE_URL } from "@/constants/server-envs";
type ProxyActionParams = {
pathname: string;
@@ -47,35 +47,35 @@ async function proxy<T>(
}
const res = await fetch(url, {
method: params.method,
body: params.body,
headers: {
...params.headers,
...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
},
body: params.body,
method: params.method,
});
if (!res.ok) {
try {
const errorMessage = await res.text();
return {
status: res.status,
ok: false,
error: errorMessage || res.statusText,
ok: false,
status: res.status,
};
} catch {
return {
status: res.status,
ok: false,
error: res.statusText,
ok: false,
status: res.status,
};
}
}
return {
status: res.status,
ok: true,
data: params.parseAsText ? await res.text() : await res.json(),
ok: true,
status: res.status,
};
}
@@ -1,7 +1,7 @@
"use server";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
import { getAuthToken } from "@/api/auth-token";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
export async function sendTeamInvites(options: {
teamId: string;
@@ -20,8 +20,8 @@ export async function sendTeamInvites(options: {
if (!token) {
return {
ok: false,
errorMessage: "You are not authorized to perform this action",
ok: false,
};
}
@@ -43,15 +43,15 @@ async function sendInvite(
const res = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${teamId}/invites`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
inviteEmail: invite.email,
inviteRole: invite.role,
}),
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
method: "POST",
},
);
@@ -59,8 +59,8 @@ async function sendInvite(
const errorMessage = await res.text();
return {
email: invite.email,
ok: false,
errorMessage,
ok: false,
};
}
+15 -2
View File
@@ -1,8 +1,8 @@
import "server-only";
import Stripe from "stripe";
import type { Team } from "../api/team";
import { STRIPE_SECRET_KEY } from "../constants/server-envs";
import type { Team } from "@/api/team";
import { STRIPE_SECRET_KEY } from "@/constants/server-envs";
let existingStripe: Stripe | undefined;
@@ -59,3 +59,16 @@ export async function getTeamInvoices(
throw new Error("Failed to fetch billing history");
}
}
async function getStripeCustomer(customerId: string) {
return await getStripe().customers.retrieve(customerId);
}
export async function getStripeBalance(customerId: string) {
const customer = await getStripeCustomer(customerId);
if (customer.deleted) {
return 0;
}
// Stripe returns a positive balance for credits, so we need to divide by -100 to get the actual balance (as long as the balance is not 0)
return customer.balance === 0 ? 0 : customer.balance / -100;
}
@@ -1,6 +1,6 @@
"use server";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
import { getAuthToken } from "@/api/auth-token";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
export async function updateAccount(values: {
name?: string;
@@ -14,12 +14,12 @@ export async function updateAccount(values: {
}
const res = await fetch(`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/account`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(values),
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
method: "PUT",
});
if (!res.ok) {
+67
View File
@@ -0,0 +1,67 @@
# Analytics Guidelines
This folder centralises the **PostHog** tracking logic for the dashboard app.
Most developers will only need to add or extend _event-reporting_ functions in `report.ts`.
---
## 1. When to add an event
1. Ask yourself if the data will be **actionable**. Every event should have a clear product or business question it helps answer.
2. Check if a similar event already exists in `report.ts`. Avoid duplicates.
---
## 2. Naming conventions
| Concept | Convention | Example |
|---------|------------|---------|
| **Event name** (string sent to PostHog) | Human-readable phrase formatted as `<subject> <verb>` | `"contract deployed"` |
| **Reporting function** | `report<Subject><Verb>` (PascalCase) | `reportContractDeployed` |
| **File** | All event functions live in the shared `report.ts` file (for now) | — |
> Keeping names predictable makes it easy to search both code and analytics.
---
## 3. Boilerplate / template
Add a new function to `report.ts` following this pattern:
```ts
/**
* ### Why do we need to report this event?
* - _Add bullet points explaining the product metrics/questions this event answers._
*
* ### Who is responsible for this event?
* @your-github-handle
*/
export function reportExampleEvent(properties: {
/* Add typed properties here */
}) {
posthog.capture("example event", {
/* Pass the same properties here */
});
}
```
Guidelines:
1. **Explain the "why".** The JSDoc block is mandatory so future contributors know the purpose.
2. **Type everything.** The `properties` object should be fully typed—this doubles as documentation.
3. **Client-side only.** `posthog-js` must never run on the server. Call these reporting helpers from client components, event handlers, etc.
---
## 4. Editing or removing events
1. Update both the function and the PostHog event definition (if required).
2. Inform the core services team before removing or renaming an event.
---
## 5. Identification & housekeeping (FYI)
Most devs can ignore this section, but for completeness:
- `hooks/identify-account.ts` and `hooks/identify-team.ts` wrap `posthog.identify`/`group` calls.
- `resetAnalytics` clears identity state (used on logout).
---
## 6. Need help?
Ping #eng-core-services in slack.
@@ -0,0 +1,20 @@
"use client";
import posthog from "posthog-js";
import { useEffect } from "react";
export function useIdentifyAccount(opts?: {
accountId: string;
email?: string;
}) {
// eslint-disable-next-line no-restricted-syntax
useEffect(() => {
// if no accountId, don't identify
if (!opts?.accountId) {
return;
}
// if email is provided, add it to the identify
posthog.identify(opts.accountId, opts.email ? { email: opts.email } : {});
}, [opts?.accountId, opts?.email]);
}
@@ -0,0 +1,17 @@
"use client";
import posthog from "posthog-js";
import { useEffect } from "react";
export function useIdentifyTeam(opts?: { teamId: string }) {
// eslint-disable-next-line no-restricted-syntax
useEffect(() => {
// if no teamId, don't identify
if (!opts?.teamId) {
return;
}
// identify the team
posthog.group("team", opts.teamId);
}, [opts?.teamId]);
}
+382
View File
@@ -0,0 +1,382 @@
import posthog from "posthog-js";
import type { Team } from "@/api/team";
// ----------------------------
// CONTRACTS
// ----------------------------
/**
* ### Why do we need to report this event?
* - To track the number of contracts deployed
* - To track the number of contracts deployed on each chain
* - To track if the contract was deployed on the token page vs on the deploy page
*
* ### Who is responsible for this event?
* @jnsdls
*
*/
export function reportContractDeployed(properties: {
address: string;
chainId: number;
publisher: string | undefined;
contractName: string | undefined;
deploymentType?: "asset";
}) {
posthog.capture("contract deployed", properties);
}
/**
* ### Why do we need to report this event?
* - To track the number of contracts that failed to deploy
* - To track the error message of the failed contract deployment (so we can fix it / add workarounds)
*
* ### Who is responsible for this event?
* @jnsdls
*
*/
export function reportContractDeployFailed(properties: {
errorMessage: string;
chainId: number;
publisher: string | undefined;
contractName: string | undefined;
}) {
posthog.capture("contract deploy failed", properties);
}
/**
* ### Why do we need to report this event?
* - To track the number of contracts published
* - To understand the type of contracts published
* - To understand who publishes contracts
*
* ### Who is responsible for this event?
* @jnsdls
*
*/
export function reportContractPublished(properties: {
publisher: string;
contractName: string;
version: string;
deployType: string | undefined;
}) {
posthog.capture("contract published", properties);
}
// ----------------------------
// ONBOARDING (TEAM)
// ----------------------------
/**
* ### Why do we need to report this event?
* - To track the number of teams that enter the onboarding flow
*
* ### Who is responsible for this event?
* @jnsdls
*
*/
export function reportOnboardingStarted() {
posthog.capture("onboarding started");
}
/**
* ### Why do we need to report this event?
* - To track the number of teams that select a paid plan during onboarding
* - To know **which** plan was selected
*
* ### Who is responsible for this event?
* @jnsdls
*
*/
export function reportOnboardingPlanSelected(properties: {
plan: Team["billingPlan"];
}) {
posthog.capture("onboarding plan selected", properties);
}
/**
* ### Why do we need to report this event?
* - To track the number of teams that skip the plan-selection step during onboarding
*
* ### Who is responsible for this event?
* @jnsdls
*
*/
export function reportOnboardingPlanSelectionSkipped() {
posthog.capture("onboarding plan selection skipped");
}
/**
* ### Why do we need to report this event?
* - To track the number of teams that invite members during onboarding
* - To track **how many** members were invited
*
* ### Who is responsible for this event?
* @jnsdls
*
*/
export function reportOnboardingMembersInvited(properties: { count: number }) {
posthog.capture("onboarding members invited", {
count: properties.count,
});
}
/**
* ### Why do we need to report this event?
* - To track the number of teams that skip inviting members during onboarding
*
* ### Who is responsible for this event?
* @jnsdls
*
*/
export function reportOnboardingMembersSkipped() {
posthog.capture("onboarding members skipped");
}
/**
* ### Why do we need to report this event?
* - To track how many teams click the upsell (upgrade) button on the member-invite step during onboarding
*
* ### Who is responsible for this event?
* @jnsdls
*
*/
export function reportOnboardingMembersUpsellButtonClicked() {
posthog.capture("onboarding members upsell clicked");
}
/**
* ### Why do we need to report this event?
* - To track which plan is selected from the members-step upsell during onboarding
*
* ### Who is responsible for this event?
* @jnsdls
*
*/
export function reportOnboardingMembersUpsellPlanSelected(properties: {
plan: Team["billingPlan"];
}) {
posthog.capture("onboarding members upsell plan selected", properties);
}
/**
* ### Why do we need to report this event?
* - To track the number of teams that completed onboarding
*
* ### Who is responsible for this event?
* @jnsdls
*
*/
export function reportOnboardingCompleted() {
posthog.capture("onboarding completed");
}
// ----------------------------
// FAUCET
// ----------------------------
/**
* ### Why do we need to report this event?
* - To track which chain the faucet was used on
* - To track how popular specific faucets are
*
* ### Who is responsible for this event?
* @jnsdls
*
*/
export function reportFaucetUsed(properties: { chainId: number }) {
posthog.capture("faucet used", {
chainId: properties.chainId,
});
}
// ----------------------------
// CHAIN CONFIGURATION
// ----------------------------
/**
* ### Why do we need to report this event?
* - To track which custom chains customers are adding that we may want to add to the app
*
* ### Who is responsible for this event?
* @jnsdls
*
*/
export function reportChainConfigurationAdded(properties: {
chainId: number;
chainName: string;
rpcURLs: readonly string[];
nativeCurrency: {
name: string;
symbol: string;
decimals: number;
};
}) {
posthog.capture("chain configuration added", {
chainId: properties.chainId,
chainName: properties.chainName,
nativeCurrency: properties.nativeCurrency,
rpcURLs: properties.rpcURLs,
});
}
// ----------------------------
// ASSETS
// ----------------------------
type AssetContractType = "DropERC20" | "DropERC1155" | "DropERC721";
/**
* ### Why do we need to report this event?
* - To track number of successful asset purchases from the token page
* - To track which asset and contract types are being purchased the most
*
* ### Who is responsible for this event?
* @MananTank
*/
export function reportAssetBuySuccessful(properties: {
chainId: number;
contractType: AssetContractType;
assetType: "nft" | "coin";
}) {
posthog.capture("asset buy successful", {
assetType: properties.assetType,
chainId: properties.chainId,
contractType: properties.contractType,
});
}
/**
* ### Why do we need to report this event?
* - To track number of failed asset purchases from the token page
* - To track the errors that users encounter when trying to purchase an asset
*
* ### Who is responsible for this event?
* @MananTank
*/
export function reportAssetBuyFailed(properties: {
chainId: number;
contractType: AssetContractType;
assetType: "nft" | "coin";
error: string;
}) {
posthog.capture("asset buy failed", {
assetType: properties.assetType,
chainId: properties.chainId,
contractType: properties.contractType,
error: properties.error,
});
}
// Assets Landing Page ----------------------------
/**
* ### Why do we need to report this event?
* - To track number of asset creation started from the assets page
* - To track which asset types are being created the most
*
* ### Who is responsible for this event?
* @MananTank
*/
export function reportAssetCreationStarted(properties: {
assetType: "nft" | "coin";
}) {
posthog.capture("asset creation started", {
assetType: properties.assetType,
});
}
/**
* ### Why do we need to report this event?
* - To track number of assets imported successfully from the assets page
*
* ### Who is responsible for this event?
* @MananTank
*/
export function reportAssetImportSuccessful() {
posthog.capture("asset import successful");
}
/**
* ### Why do we need to report this event?
* - To track number of asset import started in the assets page
*
* ### Who is responsible for this event?
* @MananTank
*/
export function reportAssetImportStarted() {
posthog.capture("asset import started");
}
/**
* ### Why do we need to report this event?
* - To track the steps users are configuring in the asset creation to understand if there are any drop-offs
*
* ### Who is responsible for this event?
* @MananTank
*/
export function reportAssetCreationStepConfigured(
properties:
| {
assetType: "nft";
step: "collection-info" | "upload-assets" | "sales-settings";
}
| {
assetType: "coin";
step: "coin-info" | "token-distribution" | "launch-coin";
},
) {
posthog.capture("asset creation step configured", {
assetType: properties.assetType,
step: properties.step,
});
}
/**
* ### Why do we need to report this event?
* - To track number of successful asset creations
* - To track which asset types are being created the most
*
* ### Who is responsible for this event?
* @MananTank
*/
export function reportAssetCreationSuccessful(properties: {
assetType: "nft" | "coin";
contractType: AssetContractType;
}) {
posthog.capture("asset creation successful", {
assetType: properties.assetType,
contractType: properties.contractType,
});
}
/**
* ### Why do we need to report this event?
* - To track number of failed asset creations
* - To track the errors that users encounter when trying to create an asset
* - To track the step that is failing in the asset creation
*
* ### Who is responsible for this event?
* @MananTank
*/
export function reportAssetCreationFailed(
properties: { contractType: AssetContractType; error: string } & (
| {
assetType: "nft";
step: "deploy-contract" | "mint-nfts" | "set-claim-conditions";
}
| {
assetType: "coin";
step:
| "deploy-contract"
| "set-claim-conditions"
| "mint-tokens"
| "airdrop-tokens";
}
),
) {
posthog.capture("asset creation failed", {
assetType: properties.assetType,
contractType: properties.contractType,
error: properties.error,
step: properties.step,
});
}
+7
View File
@@ -0,0 +1,7 @@
"use client";
import posthog from "posthog-js";
export function resetAnalytics() {
posthog.reset();
}
+13 -42
View File
@@ -1,5 +1,6 @@
import "server-only";
import { ANALYTICS_SERVICE_URL } from "@/constants/server-envs";
import type {
AnalyticsQueryParams,
EcosystemWalletStats,
@@ -12,9 +13,8 @@ import type {
UserOpStats,
WalletStats,
WalletUserStats,
} from "types/analytics";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { ANALYTICS_SERVICE_URL } from "../constants/server-envs";
} from "@/types/analytics";
import { getAuthToken } from "./auth-token";
import { getChains } from "./chain";
async function fetchAnalytics(
@@ -22,7 +22,6 @@ async function fetchAnalytics(
init?: RequestInit,
): Promise<Response> {
const token = await getAuthToken();
if (!token) {
throw new Error("You are not authorized to perform this action");
}
@@ -48,22 +47,11 @@ async function fetchAnalytics(
decodeURIComponent(value),
);
}
// client id DEBUG OVERRIDE
// analyticsServiceUrl.searchParams.delete("projectId");
// analyticsServiceUrl.searchParams.delete("teamId");
// analyticsServiceUrl.searchParams.append(
// "teamId",
// "team_cm0lde33r02344w129k5hm2xz",
// );
// analyticsServiceUrl.searchParams.append(
// "projectId",
// "prj_cm4rqwx9b002qrnsnr37wqpo6",
// );
return fetch(analyticsServiceUrl, {
...init,
headers: {
"content-type": "application/json",
Authorization: `Bearer ${token}`,
...init?.headers,
},
});
@@ -99,7 +87,6 @@ export async function getWalletConnections(
`v2/sdk/wallet-connects?${searchParams.toString()}`,
{
method: "GET",
headers: { "Content-Type": "application/json" },
},
);
@@ -123,7 +110,6 @@ export async function getInAppWalletUsage(
`v2/wallet/connects?${searchParams.toString()}`,
{
method: "GET",
headers: { "Content-Type": "application/json" },
},
);
@@ -147,7 +133,6 @@ export async function getUserOpUsage(
`v2/bundler/usage?${searchParams.toString()}`,
{
method: "GET",
headers: { "Content-Type": "application/json" },
},
);
@@ -190,9 +175,9 @@ export async function getAggregateUserOpUsage(
},
{
date: (params.from || new Date()).toISOString(),
successful: 0,
failed: 0,
sponsoredUsd: 0,
successful: 0,
},
);
}
@@ -205,7 +190,6 @@ export async function getClientTransactions(
`v2/sdk/contract-transactions?${searchParams.toString()}`,
{
method: "GET",
headers: { "Content-Type": "application/json" },
},
);
@@ -229,7 +213,6 @@ export async function getRpcMethodUsage(
`v2/rpc/evm-methods?${searchParams.toString()}`,
{
method: "GET",
headers: { "Content-Type": "application/json" },
},
);
@@ -250,7 +233,6 @@ export async function getWalletUsers(
`v2/sdk/wallet-connects/users?${searchParams.toString()}`,
{
method: "GET",
headers: { "Content-Type": "application/json" },
},
);
@@ -287,7 +269,6 @@ export async function isProjectActive(params: {
`v2/active-usage?${searchParams.toString()}`,
{
method: "GET",
headers: { "Content-Type": "application/json" },
},
);
@@ -298,14 +279,14 @@ export async function isProjectActive(params: {
);
return {
bundler: false,
storage: false,
rpc: false,
nebula: false,
sdk: false,
insight: false,
pay: false,
inAppWallet: false,
ecosystemWallet: false,
inAppWallet: false,
insight: false,
nebula: false,
pay: false,
rpc: false,
sdk: false,
storage: false,
} as ActiveStatus;
}
@@ -357,9 +338,6 @@ export async function getEcosystemWalletUsage(args: {
`v2/wallet/connects?${searchParams.toString()}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
},
);
@@ -386,9 +364,6 @@ export async function getUniversalBridgeUsage(args: {
const searchParams = buildSearchParams(args);
const res = await fetchAnalytics(`v2/universal?${searchParams.toString()}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (res?.status !== 200) {
@@ -415,9 +390,6 @@ export async function getUniversalBridgeWalletUsage(args: {
`v2/universal/wallets?${searchParams.toString()}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
},
);
@@ -426,7 +398,7 @@ export async function getUniversalBridgeWalletUsage(args: {
console.error(
`Failed to fetch universal bridge wallet stats: ${res?.status} - ${res.statusText} - ${reason}`,
);
return null;
return [];
}
const json = await res.json();
@@ -441,7 +413,6 @@ export async function getEngineCloudMethodUsage(
`v2/engine-cloud/requests?${searchParams.toString()}`,
{
method: "GET",
headers: { "Content-Type": "application/json" },
},
);
+91
View File
@@ -0,0 +1,91 @@
"use server";
import "server-only";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { getAuthToken } from "./auth-token";
export type AuditLogEntry = {
who: {
text: string;
metadata?: {
email?: string;
image?: string;
wallet?: string;
clientId?: string;
};
type: "user" | "apikey" | "system";
path?: string;
};
what: {
text: string;
action: "create" | "update" | "delete";
path?: string;
in?: {
text: string;
path?: string;
};
description?: string;
resourceType:
| "team"
| "project"
| "team-member"
| "team-invite"
| "contract"
| "secret-key";
};
when: string;
};
type AuditLogApiResponse = {
result: AuditLogEntry[];
nextCursor?: string;
hasMore: boolean;
};
export async function getAuditLogs(teamSlug: string, cursor?: string) {
const authToken = await getAuthToken();
if (!authToken) {
throw new Error("No auth token found");
}
const url = new URL(
`/v1/teams/${teamSlug}/audit-log`,
NEXT_PUBLIC_THIRDWEB_API_HOST,
);
if (cursor) {
url.searchParams.set("cursor", cursor);
}
// artifically limit page size to 15 for now
url.searchParams.set("take", "15");
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${authToken}`,
},
next: {
// revalidate this query once per 10 seconds (does not need to be more granular than that)
revalidate: 10,
},
});
if (!response.ok) {
// if the status is 402, the most likely reason is that the team is on a free plan
if (response.status === 402) {
return {
reason: "higher_plan_required",
status: "error",
} as const;
}
const body = await response.text();
return {
body,
reason: "unknown",
status: "error",
} as const;
}
const data = (await response.json()) as AuditLogApiResponse;
return {
data,
status: "success",
} as const;
}
@@ -1,7 +1,7 @@
import { COOKIE_ACTIVE_ACCOUNT, COOKIE_PREFIX_TOKEN } from "@/constants/cookie";
import { LAST_USED_TEAM_ID } from "constants/cookies";
import { cookies } from "next/headers";
import { getClientThirdwebClient } from "../../../../@/constants/thirdweb-client.client";
import { COOKIE_ACTIVE_ACCOUNT, COOKIE_PREFIX_TOKEN } from "@/constants/cookie";
import { LAST_USED_TEAM_ID } from "@/constants/cookies";
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
export async function getAuthToken() {
const cookiesManager = await cookies();
+3 -3
View File
@@ -1,8 +1,8 @@
import "server-only";
import type { ChainMetadata } from "thirdweb/chains";
import type { ChainService } from "../../app/(app)/(dashboard)/(chain)/types/chain";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
import { API_SERVER_SECRET } from "../constants/server-envs";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { API_SERVER_SECRET } from "@/constants/server-envs";
import type { ChainService } from "@/types/chain";
export async function getGasSponsoredChains() {
if (!API_SERVER_SECRET) {
@@ -0,0 +1,40 @@
"use server";
import "server-only";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { getAuthToken } from "./auth-token";
export async function createDedicatedSupportChannel(
teamIdOrSlug: string,
channelType: "slack" | "telegram",
): Promise<{ error: string | null }> {
const token = await getAuthToken();
if (!token) {
return { error: "Unauthorized" };
}
const res = await fetch(
new URL(
`/v1/teams/${teamIdOrSlug}/dedicated-support-channel`,
NEXT_PUBLIC_THIRDWEB_API_HOST,
),
{
body: JSON.stringify({
type: channelType,
}),
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
method: "POST",
},
);
if (!res.ok) {
const json = await res.json();
return {
error:
json.error?.message ?? "Failed to create dedicated support channel.",
};
}
return { error: null };
}
@@ -1,4 +1,5 @@
import "server-only";
import { getAddress } from "thirdweb";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
export type ProjectContract = {
@@ -43,3 +44,43 @@ export async function getProjectContracts(options: {
return data.result;
}
export type PartialProject = {
id: string;
name: string;
slug: string;
image: string | null;
};
/**
* get a list of projects within a team that have a given contract imported
*/
export async function getContractImportedProjects(options: {
teamId: string;
authToken: string;
chainId: number;
contractAddress: string;
}) {
const url = new URL(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${options.teamId}/projects/contracts/lookup?chainId=${options.chainId}&contractAddress=${getAddress(options.contractAddress)}`,
);
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${options.authToken}`,
},
});
if (!res.ok) {
const errorMessage = await res.text();
console.error("Error fetching: /projects/contracts/lookup");
console.error(errorMessage);
return [];
}
const data = (await res.json()) as {
result: PartialProject[];
};
return data.result;
}
+15 -15
View File
@@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
"use server";
import { getAuthToken } from "app/(app)/api/lib/getAuthToken";
import { THIRDWEB_INSIGHT_API_DOMAIN } from "constants/urls";
import { getAuthToken } from "@/api/auth-token";
import { THIRDWEB_INSIGHT_API_DOMAIN } from "@/constants/urls";
export interface WebhookResponse {
id: string;
@@ -80,13 +80,13 @@ export async function createWebhook(
const response = await fetch(
`https://${THIRDWEB_INSIGHT_API_DOMAIN}/v1/webhooks`,
{
method: "POST",
body: JSON.stringify(payload),
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"x-client-id": clientId,
Authorization: `Bearer ${authToken}`,
},
body: JSON.stringify(payload),
method: "POST",
},
);
@@ -115,11 +115,11 @@ export async function getWebhooks(
const response = await fetch(
`https://${THIRDWEB_INSIGHT_API_DOMAIN}/v1/webhooks`,
{
method: "GET",
headers: {
"x-client-id": clientId,
Authorization: `Bearer ${authToken}`,
"x-client-id": clientId,
},
method: "GET",
},
);
@@ -149,11 +149,11 @@ export async function deleteWebhook(
const response = await fetch(
`https://${THIRDWEB_INSIGHT_API_DOMAIN}/v1/webhooks/${encodeURIComponent(webhookId)}`,
{
method: "DELETE",
headers: {
"x-client-id": clientId,
Authorization: `Bearer ${authToken}`,
"x-client-id": clientId,
},
method: "DELETE",
},
);
@@ -183,29 +183,29 @@ export async function testWebhook(
const response = await fetch(
`https://${THIRDWEB_INSIGHT_API_DOMAIN}/v1/webhooks/test`,
{
method: "POST",
body: JSON.stringify(payload),
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"x-client-id": clientId,
Authorization: `Bearer ${authToken}`,
},
body: JSON.stringify(payload),
method: "POST",
},
);
if (!response.ok) {
const errorText = await response.text();
return {
success: false,
error: `Failed to test webhook: ${errorText}`,
success: false,
};
}
return (await response.json()) as TestWebhookResponse;
} catch (error) {
return {
success: false,
error: `Network or parsing error: ${error instanceof Error ? error.message : "Unknown error"}`,
success: false,
};
}
}
@@ -215,10 +215,10 @@ export async function getSupportedWebhookChains(): Promise<SupportedWebhookChain
const response = await fetch(
`https://${THIRDWEB_INSIGHT_API_DOMAIN}/service/chains`,
{
method: "GET",
headers: {
"x-service-api-key": process.env.INSIGHT_SERVICE_API_KEY || "",
},
method: "GET",
},
);
+2 -2
View File
@@ -1,5 +1,5 @@
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { getAuthToken } from "./auth-token";
export type LinkedWallet = {
createdAt: string;
+157
View File
@@ -0,0 +1,157 @@
"use server";
import "server-only";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { getAuthToken } from "./auth-token";
export type Notification = {
id: string;
createdAt: string;
accountId: string;
teamId: string | null;
description: string;
readAt: string | null;
ctaText: string;
ctaUrl: string;
};
export type NotificationsApiResponse = {
result: Notification[];
nextCursor?: string;
};
export async function getUnreadNotifications(cursor?: string) {
const authToken = await getAuthToken();
if (!authToken) {
throw new Error("No auth token found");
}
const url = new URL(
"/v1/dashboard-notifications/unread",
NEXT_PUBLIC_THIRDWEB_API_HOST,
);
if (cursor) {
url.searchParams.set("cursor", cursor);
}
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${authToken}`,
},
});
if (!response.ok) {
const body = await response.text();
return {
body,
reason: "unknown",
status: "error",
} as const;
}
const data = (await response.json()) as NotificationsApiResponse;
return {
data,
status: "success",
} as const;
}
export async function getArchivedNotifications(cursor?: string) {
const authToken = await getAuthToken();
if (!authToken) {
throw new Error("No auth token found");
}
const url = new URL(
"/v1/dashboard-notifications/archived",
NEXT_PUBLIC_THIRDWEB_API_HOST,
);
if (cursor) {
url.searchParams.set("cursor", cursor);
}
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${authToken}`,
},
});
if (!response.ok) {
const body = await response.text();
return {
body,
reason: "unknown",
status: "error",
} as const;
}
const data = (await response.json()) as NotificationsApiResponse;
return {
data,
status: "success",
} as const;
}
export async function getUnreadNotificationsCount() {
const authToken = await getAuthToken();
if (!authToken) {
throw new Error("No auth token found");
}
const url = new URL(
"/v1/dashboard-notifications/unread-count",
NEXT_PUBLIC_THIRDWEB_API_HOST,
);
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${authToken}`,
},
});
if (!response.ok) {
const body = await response.text();
return {
body,
reason: "unknown",
status: "error",
} as const;
}
const data = (await response.json()) as {
result: {
unreadCount: number;
};
};
return {
data,
status: "success",
} as const;
}
export async function markNotificationAsRead(notificationId?: string) {
const authToken = await getAuthToken();
if (!authToken) {
throw new Error("No auth token found");
}
const url = new URL(
"/v1/dashboard-notifications/mark-as-read",
NEXT_PUBLIC_THIRDWEB_API_HOST,
);
const response = await fetch(url, {
// if notificationId is provided, mark it as read, otherwise mark all as read
body: JSON.stringify(notificationId ? { notificationId } : {}),
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
},
method: "PUT",
});
if (!response.ok) {
const body = await response.text();
return {
body,
reason: "unknown",
status: "error",
} as const;
}
return {
status: "success",
} as const;
}
+2 -2
View File
@@ -1,7 +1,7 @@
import "server-only";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import type { ProjectResponse } from "@thirdweb-dev/service-utils";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { getAuthToken } from "./auth-token";
export type Project = ProjectResponse;
+2 -2
View File
@@ -1,6 +1,6 @@
import "server-only";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
import { API_SERVER_SECRET } from "../constants/server-envs";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { API_SERVER_SECRET } from "@/constants/server-envs";
export type SMSCountryTiers = {
tier1: string[];
+2 -2
View File
@@ -1,5 +1,5 @@
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { getAuthToken } from "./auth-token";
export type TeamInvite = {
id: string;
+11 -5
View File
@@ -1,10 +1,10 @@
import "server-only";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { getAuthToken } from "./auth-token";
const TeamAccountRole = {
OWNER: "OWNER",
MEMBER: "MEMBER",
OWNER: "OWNER",
} as const;
export type TeamAccountRole =
@@ -33,7 +33,7 @@ export async function getMembers(teamSlug: string) {
}
const teamsRes = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${teamSlug}/members`,
new URL(`/v1/teams/${teamSlug}/members`, NEXT_PUBLIC_THIRDWEB_API_HOST),
{
headers: {
Authorization: `Bearer ${token}`,
@@ -48,7 +48,10 @@ export async function getMembers(teamSlug: string) {
return undefined;
}
export async function getMemberById(teamSlug: string, memberId: string) {
export async function getMemberByAccountId(
teamSlug: string,
accountId: string,
) {
const token = await getAuthToken();
if (!token) {
@@ -56,7 +59,10 @@ export async function getMemberById(teamSlug: string, memberId: string) {
}
const res = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${teamSlug}/members/${memberId}`,
new URL(
`/v1/teams/${teamSlug}/members/${accountId}`,
NEXT_PUBLIC_THIRDWEB_API_HOST,
),
{
headers: {
Authorization: `Bearer ${token}`,
@@ -1,6 +1,6 @@
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
import type { ProductSKU } from "../lib/billing";
import { getAuthToken } from "@/api/auth-token";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import type { ProductSKU } from "@/types/billing";
type InvoiceLine = {
// amount for this line item
+27 -4
View File
@@ -1,10 +1,12 @@
import "server-only";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { API_SERVER_SECRET } from "@/constants/server-envs";
import type { TeamResponse } from "@thirdweb-dev/service-utils";
import { cookies } from "next/headers";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { LAST_USED_TEAM_ID } from "../../constants/cookies";
import { LAST_USED_TEAM_ID } from "@/constants/cookies";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { API_SERVER_SECRET } from "@/constants/server-envs";
import { getValidAccount } from "../../app/(app)/account/settings/getAccount";
import { getAuthToken } from "./auth-token";
import { getMemberByAccountId } from "./team-members";
export type Team = TeamResponse & { stripeCustomerId: string | null };
@@ -67,6 +69,7 @@ export async function getTeams() {
return null;
}
/** @deprecated */
export async function getDefaultTeam() {
const token = await getAuthToken();
if (!token) {
@@ -102,3 +105,23 @@ export async function getLastVisitedTeam() {
return null;
}
export async function hasToCompleteTeamOnboarding(
team: Team,
pagePath: string,
) {
// if the team is already onboarded, we don't need to check anything else here
if (team.isOnboarded) {
return false;
}
const account = await getValidAccount(pagePath);
const teamMember = await getMemberByAccountId(team.slug, account.id);
// if the team member is not an owner (or we cannot find them), they cannot complete onboarding anyways
if (teamMember?.role !== "OWNER") {
return false;
}
// if we get here the team is not onboarded and the team member is an owner, so we need to show the onboarding page
return true;
}
@@ -1,6 +1,6 @@
"use server";
import { getAuthToken } from "app/(app)/api/lib/getAuthToken";
import { NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST } from "../../constants/public-envs";
import { getAuthToken } from "@/api/auth-token";
import { NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST } from "@/constants/public-envs";
const UB_BASE_URL = NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST;
@@ -14,19 +14,16 @@ type Webhook = {
version?: number; // TODO (UB) make this mandatory after migration
};
export async function getWebhooks(props: {
clientId: string;
teamId: string;
}) {
export async function getWebhooks(props: { clientId: string; teamId: string }) {
const authToken = await getAuthToken();
const res = await fetch(`${UB_BASE_URL}/v1/developer/webhooks`, {
method: "GET",
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"x-client-id": props.clientId,
"x-team-id": props.teamId,
Authorization: `Bearer ${authToken}`,
},
method: "GET",
});
if (!res.ok) {
@@ -47,27 +44,21 @@ export async function createWebhook(props: {
secret?: string;
}) {
const authToken = await getAuthToken();
console.log(
"UB_BASE_URL",
UB_BASE_URL,
props.clientId,
props.teamId,
authToken,
);
const res = await fetch(`${UB_BASE_URL}/v1/developer/webhooks`, {
method: "POST",
body: JSON.stringify({
url: props.url,
label: props.label,
version: props.version,
secret: props.secret,
url: props.url,
version: props.version,
}),
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"x-client-id": props.clientId,
"x-team-id": props.teamId,
Authorization: `Bearer ${authToken}`,
},
method: "POST",
});
if (!res.ok) {
@@ -87,13 +78,13 @@ export async function deleteWebhook(props: {
const res = await fetch(
`${UB_BASE_URL}/v1/developer/webhooks/${props.webhookId}`,
{
method: "DELETE",
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"x-client-id": props.clientId,
"x-team-id": props.teamId,
Authorization: `Bearer ${authToken}`,
},
method: "DELETE",
},
);
@@ -112,19 +103,16 @@ export type Fee = {
updatedAt: string;
};
export async function getFees(props: {
clientId: string;
teamId: string;
}) {
export async function getFees(props: { clientId: string; teamId: string }) {
const authToken = await getAuthToken();
const res = await fetch(`${UB_BASE_URL}/v1/developer/fees`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"x-team-id": props.teamId,
"x-client-id": props.clientId,
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"x-client-id": props.clientId,
"x-team-id": props.teamId,
},
method: "GET",
});
if (!res.ok) {
@@ -144,17 +132,17 @@ export async function updateFee(props: {
}) {
const authToken = await getAuthToken();
const res = await fetch(`${UB_BASE_URL}/v1/developer/fees`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
"x-team-id": props.teamId,
"x-client-id": props.clientId,
Authorization: `Bearer ${authToken}`,
},
body: JSON.stringify({
feeRecipient: props.feeRecipient,
feeBps: props.feeBps,
feeRecipient: props.feeRecipient,
}),
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"x-client-id": props.clientId,
"x-team-id": props.teamId,
},
method: "PUT",
});
if (!res.ok) {
@@ -231,13 +219,13 @@ export async function getPayments(props: {
}
const res = await fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"x-client-id": props.clientId,
"x-team-id": props.teamId,
Authorization: `Bearer ${authToken}`,
},
method: "GET",
});
if (!res.ok) {
@@ -18,15 +18,13 @@ type PaymentLink = {
purchaseData: Record<string, unknown> | undefined;
};
export async function getPaymentLink(props: {
paymentId: string;
}) {
export async function getPaymentLink(props: { paymentId: string }) {
const res = await fetch(`${UB_BASE_URL}/v1/links/${props.paymentId}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"x-secret-key": DASHBOARD_THIRDWEB_SECRET_KEY,
},
method: "GET",
});
if (!res.ok) {
@@ -36,12 +34,12 @@ export async function getPaymentLink(props: {
const { data } = await res.json();
return {
clientId: data.clientId,
title: data.title,
imageUrl: data.imageUrl,
receiver: data.receiver,
destinationToken: data.destinationToken,
amount: data.amount ? BigInt(data.amount) : undefined,
clientId: data.clientId,
destinationToken: data.destinationToken,
imageUrl: data.imageUrl,
purchaseData: data.purchaseData,
receiver: data.receiver,
title: data.title,
} as PaymentLink;
}
@@ -1,7 +1,7 @@
"use server";
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/server-envs";
import type { ProjectResponse } from "@thirdweb-dev/service-utils";
import { getAuthToken } from "app/(app)/api/lib/getAuthToken";
import { getAuthToken } from "@/api/auth-token";
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/server-envs";
import { UB_BASE_URL } from "./constants";
export type TokenMetadata = {
@@ -13,9 +13,7 @@ export type TokenMetadata = {
iconUri?: string;
};
export async function getUniversalBridgeTokens(props: {
chainId?: number;
}) {
export async function getUniversalBridgeTokens(props: { chainId?: number }) {
const url = new URL(`${UB_BASE_URL}/v1/tokens`);
if (props.chainId) {
@@ -24,11 +22,11 @@ export async function getUniversalBridgeTokens(props: {
url.searchParams.append("limit", "1000");
const res = await fetch(url.toString(), {
method: "GET",
headers: {
"Content-Type": "application/json",
"x-secret-key": DASHBOARD_THIRDWEB_SECRET_KEY,
} as Record<string, string>,
method: "GET",
});
if (!res.ok) {
@@ -49,16 +47,16 @@ export async function addUniversalBridgeTokenRoute(props: {
const url = new URL(`${UB_BASE_URL}/v1/tokens`);
const res = await fetch(url.toString(), {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${authToken}`,
"x-client-id": props.project.publishableKey,
} as Record<string, string>,
body: JSON.stringify({
chainId: props.chainId,
tokenAddress: props.tokenAddress,
}),
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"x-client-id": props.project.publishableKey,
} as Record<string, string>,
method: "POST",
});
if (!res.ok) {
@@ -0,0 +1,66 @@
import "server-only";
import { getAuthToken } from "@/api/auth-token";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
type LineItem = {
quantity: number;
amountUsdCents: number;
unitAmountUsdCents: string;
description: string;
};
export type UsageCategory = {
category: string;
unitName: string;
lineItems: LineItem[];
};
type UsageApiResponse = {
result: UsageCategory[];
periodStart: string;
periodEnd: string;
planVersion: number;
};
export async function getBilledUsage(teamSlug: string) {
const authToken = await getAuthToken();
if (!authToken) {
throw new Error("No auth token found");
}
const response = await fetch(
new URL(
`/v1/teams/${teamSlug}/billing/billed-usage`,
NEXT_PUBLIC_THIRDWEB_API_HOST,
),
{
headers: {
Authorization: `Bearer ${authToken}`,
},
next: {
// revalidate this query once per minute (does not need to be more granular than that)
revalidate: 60,
},
},
);
if (!response.ok) {
// if the status is 404, the most likely reason is that the team is on a free plan
if (response.status === 404) {
return {
reason: "free_plan",
status: "error",
} as const;
}
const body = await response.text();
return {
body,
reason: "unknown",
status: "error",
} as const;
}
const data = (await response.json()) as UsageApiResponse;
return {
data,
status: "success",
} as const;
}
+4 -60
View File
@@ -1,58 +1,6 @@
import "server-only";
import { unstable_cache } from "next/cache";
import { ANALYTICS_SERVICE_URL } from "../../constants/server-envs";
export type RPCUsageDataItem = {
date: string;
usageType: "included" | "overage" | "rate-limit";
count: string;
};
export const fetchRPCUsage = unstable_cache(
async (params: {
teamId: string;
projectId?: string;
authToken: string;
from: string;
to: string;
period: "day" | "week" | "month" | "year" | "all";
}) => {
const analyticsEndpoint = ANALYTICS_SERVICE_URL;
const url = new URL(`${analyticsEndpoint}/v2/rpc/usage-types`);
url.searchParams.set("teamId", params.teamId);
if (params.projectId) {
url.searchParams.set("projectId", params.projectId);
}
url.searchParams.set("from", params.from);
url.searchParams.set("to", params.to);
url.searchParams.set("period", params.period);
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${params.authToken}`,
},
});
if (!res.ok) {
const error = await res.text();
return {
ok: false as const,
error: error,
};
}
const resData = await res.json();
return {
ok: true as const,
data: resData.data as RPCUsageDataItem[],
};
},
["rpc-usage"],
{
revalidate: 60 * 60, // 1 hour
},
);
import { ANALYTICS_SERVICE_URL } from "@/constants/server-envs";
type Last24HoursRPCUsageApiResponse = {
peakRate: {
@@ -75,11 +23,7 @@ type Last24HoursRPCUsageApiResponse = {
};
export const getLast24HoursRPCUsage = unstable_cache(
async (params: {
teamId: string;
projectId?: string;
authToken: string;
}) => {
async (params: { teamId: string; projectId?: string; authToken: string }) => {
const analyticsEndpoint = ANALYTICS_SERVICE_URL;
const url = new URL(`${analyticsEndpoint}/v2/rpc/24-hours`);
url.searchParams.set("teamId", params.teamId);
@@ -96,16 +40,16 @@ export const getLast24HoursRPCUsage = unstable_cache(
if (!res.ok) {
const error = await res.text();
return {
ok: false as const,
error: error,
ok: false as const,
};
}
const resData = await res.json();
return {
ok: true as const,
data: resData.data as Last24HoursRPCUsageApiResponse,
ok: true as const,
};
},
["rpc-usage-last-24-hours:v2"],
+4 -4
View File
@@ -1,8 +1,8 @@
"use server";
import "server-only";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { getAuthToken } from "./auth-token";
export type VerifiedDomainResponse =
| {
@@ -56,12 +56,12 @@ export async function createDomainVerification(
const res = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${teamIdOrSlug}/verified-domain`,
{
method: "POST",
body: JSON.stringify({ domain }),
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
method: "POST",
},
);
@@ -1,3 +1,4 @@
import { differenceInCalendarDays, format, subDays } from "date-fns";
import { DatePickerWithRange } from "@/components/ui/DatePickerWithRange";
import {
Select,
@@ -7,7 +8,6 @@ import {
SelectValue,
} from "@/components/ui/select";
import { normalizeTime } from "@/lib/time";
import { differenceInCalendarDays, format, subDays } from "date-fns";
export function DateRangeSelector(props: {
range: Range;
@@ -26,30 +26,15 @@ export function DateRangeSelector(props: {
return (
<DatePickerWithRange
className="w-auto bg-card"
from={range.from}
to={range.to}
popoverAlign={props.popoverAlign}
setFrom={(from) =>
setRange({
from,
to: range.to,
type: "custom",
})
}
setTo={(to) =>
setRange({
from: range.from,
to,
type: "custom",
})
}
header={
<div className="mb-2 border-border border-b p-4">
<Select
value={rangeType}
onValueChange={(id: DurationId) => {
setRange(getLastNDaysRange(id));
}}
value={rangeType}
>
<SelectTrigger className="flex bg-transparent">
<SelectValue placeholder="Select" />
@@ -72,7 +57,22 @@ export function DateRangeSelector(props: {
</div>
}
labelOverride={rangeLabel}
className="w-auto bg-card"
popoverAlign={props.popoverAlign}
setFrom={(from) =>
setRange({
from,
to: range.to,
type: "custom",
})
}
setTo={(to) =>
setRange({
from: range.from,
to,
type: "custom",
})
}
to={range.to}
/>
);
}
@@ -86,10 +86,10 @@ export function getLastNDaysRange(id: DurationId) {
const todayDate = new Date(Date.now() + 1000 * 60 * 60 * 24); // add 1 day to avoid timezone issues
const value: Range = {
type: id,
from: subDays(todayDate, durationInfo.days),
to: todayDate,
label: durationInfo.name,
to: todayDate,
type: id,
};
return value;
@@ -97,24 +97,24 @@ export function getLastNDaysRange(id: DurationId) {
const durationPresets = [
{
name: "Last 7 Days",
id: "last-7",
days: 7,
id: "last-7",
name: "Last 7 Days",
},
{
name: "Last 30 Days",
id: "last-30",
days: 30,
id: "last-30",
name: "Last 30 Days",
},
{
name: "Last 60 Days",
id: "last-60",
days: 60,
id: "last-60",
name: "Last 60 Days",
},
{
name: "Last 120 Days",
id: "last-120",
days: 120,
id: "last-120",
name: "Last 120 Days",
},
] as const;
@@ -1,8 +1,9 @@
"use client";
import { LoadingDots } from "@/components/ui/LoadingDots";
import { type ChartConfig, ChartContainer } from "@/components/ui/chart";
import { useMemo } from "react";
import { useId, useMemo } from "react";
import { Area, AreaChart, Bar, BarChart } from "recharts";
import { type ChartConfig, ChartContainer } from "@/components/ui/chart";
import { LoadingDots } from "@/components/ui/LoadingDots";
import { cn } from "@/lib/utils";
type FakeCartData = {
value: number;
@@ -16,15 +17,18 @@ function generateRandomData(): FakeCartData[] {
const skeletonChartConfig = {
value: {
label: "Value",
color: "hsl(var(--muted)/90%)",
label: "Value",
},
} satisfies ChartConfig;
export function EmptyChartState({
children,
type,
}: { children?: React.ReactNode; type: "bar" | "area" }) {
}: {
children?: React.ReactNode;
type: "bar" | "area";
}) {
const barChartData = useMemo(() => generateRandomData(), []);
return (
@@ -37,9 +41,14 @@ export function EmptyChartState({
);
}
export function LoadingChartState() {
export function LoadingChartState({ className }: { className?: string }) {
return (
<div className="pointer-events-none flex h-full w-full items-center justify-center rounded-lg bg-muted/30">
<div
className={cn(
"pointer-events-none flex h-full w-full items-center justify-center rounded-lg bg-muted/30",
className,
)}
>
<LoadingDots />
</div>
);
@@ -49,10 +58,11 @@ function SkeletonBarChart(props: {
data: FakeCartData[];
type: "bar" | "area";
}) {
const fillAreaSkeletonId = useId();
return (
<ChartContainer
config={skeletonChartConfig}
className="pointer-events-none h-full w-full blur-[5px]"
config={skeletonChartConfig}
>
{props.type === "bar" ? (
<BarChart
@@ -71,7 +81,7 @@ function SkeletonBarChart(props: {
}}
>
<defs>
<linearGradient id="fill_area_skeleton" x1="0" y1="0" x2="0" y2="1">
<linearGradient id={fillAreaSkeletonId} x1="0" x2="0" y1="0" y2="1">
<stop
offset="5%"
stopColor={"hsl(var(--muted-foreground)/0.5)"}
@@ -81,11 +91,11 @@ function SkeletonBarChart(props: {
</linearGradient>
</defs>
<Area
type="natural"
dataKey="value"
stroke="hsl(var(--muted-foreground))"
fill="url(#fill_area_skeleton)"
fill={`url(#${fillAreaSkeletonId})`}
radius={8}
stroke="hsl(var(--muted-foreground))"
type="natural"
/>
</AreaChart>
)}
@@ -14,10 +14,10 @@ export function IntervalSelector(props: {
}) {
return (
<Select
value={props.intervalType}
onValueChange={(value: "day" | "week") => {
props.setIntervalType(value);
}}
value={props.intervalType}
>
<SelectTrigger
className={cn("w-auto bg-card md:w-[120px]", props.className)}
@@ -1,22 +1,25 @@
"use client";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { useQuery } from "@tanstack/react-query";
import {
DateRangeSelector,
getLastNDaysRange,
} from "components/analytics/date-range-selector";
import { usePathname, useSearchParams } from "next/navigation";
import { useState } from "react";
import type {
DurationId,
Range,
} from "components/analytics/date-range-selector";
import { IntervalSelector } from "components/analytics/interval-selector";
import { usePathname, useSearchParams } from "next/navigation";
import { useState } from "react";
} from "@/components/analytics/date-range-selector";
import {
DateRangeSelector,
getLastNDaysRange,
} from "@/components/analytics/date-range-selector";
import { IntervalSelector } from "@/components/analytics/interval-selector";
import { useDashboardRouter } from "@/lib/DashboardRouter";
export function RangeSelector({
range,
interval,
}: { range?: Range; interval: "day" | "week" }) {
}: {
range?: Range;
interval: "day" | "week";
}) {
const pathname = usePathname();
const searchParams = useSearchParams();
const router = useDashboardRouter();
@@ -26,7 +29,6 @@ export function RangeSelector({
const [localInterval, setInterval] = useState<"day" | "week">(interval);
useQuery({
queryKey: ["analytics-range", searchParams?.toString(), range],
queryFn: async () => {
if (range) {
setRange(range);
@@ -46,11 +48,11 @@ export function RangeSelector({
}
return getLastNDaysRange("last-120");
},
queryKey: ["analytics-range", searchParams?.toString(), range],
});
// prefetch for each interval and default range
useQuery({
queryKey: ["analytics-range", searchParams?.toString()],
queryFn: async () => {
const newSearchParams = new URLSearchParams(searchParams || {});
for (const interval of ["day", "week"] as const) {
@@ -70,6 +72,7 @@ export function RangeSelector({
}
return true;
},
queryKey: ["analytics-range", searchParams?.toString()],
});
return (
@@ -1,4 +1,4 @@
import { Skeleton } from "../../@/components/ui/skeleton";
import { Skeleton } from "@/components/ui/skeleton";
export const StatCard: React.FC<{
label: string;
@@ -1,6 +1,5 @@
"use client";
import { Checkbox, CheckboxWithLabel } from "@/components/ui/checkbox";
import {
Alert,
AlertIcon,
@@ -12,27 +11,24 @@ import {
Textarea,
} from "@chakra-ui/react";
import { zodResolver } from "@hookform/resolvers/zod";
import { TransactionButton } from "components/buttons/TransactionButton";
import { FileInput } from "components/shared/FileInput";
import { useImageFileOrUrl } from "hooks/useImageFileOrUrl";
import { Button } from "chakra/button";
import { FormErrorMessage, FormHelperText, FormLabel } from "chakra/form";
import { Heading } from "chakra/heading";
import { Text } from "chakra/text";
import { ChevronLeftIcon, EyeIcon, EyeOffIcon } from "lucide-react";
import { useRef, useState } from "react";
import { useDropzone } from "react-dropzone";
import { useForm } from "react-hook-form";
import type { ThirdwebClient } from "thirdweb";
import type { CreateDelayedRevealBatchParams } from "thirdweb/extensions/erc721";
import type { NFTInput } from "thirdweb/utils";
import {
Button,
FormErrorMessage,
FormHelperText,
FormLabel,
Heading,
Text,
TrackedLink,
} from "tw-components";
import type { ComponentWithChildren } from "types/component-with-children";
import { processInputData, shuffleData } from "utils/batch";
import { z } from "zod";
import { FileInput } from "@/components/blocks/FileInput";
import { TransactionButton } from "@/components/tx-button";
import { Checkbox, CheckboxWithLabel } from "@/components/ui/checkbox";
import { UnderlineLink } from "@/components/ui/UnderlineLink";
import type { ComponentWithChildren } from "@/types/component-with-children";
import { processInputData, shuffleData } from "@/utils/batch";
import { BatchTable } from "./batch-table";
import { SelectOption } from "./lazy-mint-form/select-option";
import { UploadStep } from "./upload-step";
@@ -53,33 +49,34 @@ interface BatchLazyMintEVMProps {
canCreateDelayedRevealBatch: boolean;
onSubmit: (formData: SubmitType) => Promise<unknown>;
chainId: number;
client: ThirdwebClient;
}
type BatchLazyMintProps = BatchLazyMintEVMProps;
const BatchLazyMintFormSchema = z
.object({
// delayed reveal placeholder
placeHolder: z
.object({
name: z.string().min(1, "A name is required"),
image: z.any().optional(),
description: z.string().or(z.string().length(0)).optional(),
})
.optional(),
// delayed reveal password logic
password: z.string().min(1, "A password is required.").optional(),
confirmPassword: z
.string()
.min(1, "Please confirm your password.")
.optional(),
// shared logic
shuffle: z.boolean().default(false),
revealType: z.literal("instant").or(z.literal("delayed")).optional(),
// metadata
metadatas: z.array(z.any()),
// delayed reveal password logic
password: z.string().min(1, "A password is required.").optional(),
// delayed reveal placeholder
placeHolder: z
.object({
description: z.string().or(z.string().length(0)).optional(),
image: z.any().optional(),
name: z.string().min(1, "A name is required"),
})
.optional(),
revealType: z.literal("instant").or(z.literal("delayed")).optional(),
// shared logic
shuffle: z.boolean().default(false),
})
.refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
@@ -92,12 +89,12 @@ type BatchLazyMintFormType = z.output<typeof BatchLazyMintFormSchema> & {
function useBatchLazyMintForm() {
return useForm<BatchLazyMintFormType>({
resolver: zodResolver(BatchLazyMintFormSchema),
defaultValues: {
metadatas: [],
revealType: undefined,
shuffle: false,
},
resolver: zodResolver(BatchLazyMintFormSchema),
});
}
@@ -149,8 +146,8 @@ export const BatchLazyMint: ComponentWithChildren<
// check submit is instant
if (data.revealType === "instant") {
return props.onSubmit({
revealType: "instant",
data: { metadatas: shuffledMetadatas },
revealType: "instant",
});
}
// validate password
@@ -170,16 +167,16 @@ export const BatchLazyMint: ComponentWithChildren<
}
// submit
return props.onSubmit({
revealType: "delayed",
data: {
metadata: shuffledMetadatas,
password: data.password,
placeholderMetadata: {
name: data.placeHolder?.name,
description: data.placeHolder?.description,
image: data.placeHolder?.image,
name: data.placeHolder?.name,
},
},
revealType: "delayed",
});
})}
>
@@ -188,9 +185,10 @@ export const BatchLazyMint: ComponentWithChildren<
{nftMetadatas.length > 0 ? (
<>
<BatchTable
portalRef={paginationPortalRef}
client={props.client}
data={nftMetadatas}
nextTokenIdToMint={props.nextTokenIdToMint}
portalRef={paginationPortalRef}
/>
<div className="border-border border-t">
<div className="flex flex-col items-center justify-between p-0 md:flex-row md:p-4">
@@ -220,8 +218,8 @@ export const BatchLazyMint: ComponentWithChildren<
</>
) : (
<UploadStep
getRootProps={getRootProps}
getInputProps={getInputProps}
getRootProps={getRootProps}
hasFailed={hasError}
isDragActive={isDragActive}
/>
@@ -229,30 +227,31 @@ export const BatchLazyMint: ComponentWithChildren<
</div>
) : (
<>
<Flex align="center" justify="space-between" py={4} w="100%" mb={2}>
<Flex align="center" justify="space-between" mb={2} py={4} w="100%">
<div className="flex flex-row items-center gap-2">
<Button
className="text-muted-foreground"
variant="ghost"
onClick={() => setStep(0)}
variant="ghost"
>
<ChevronLeftIcon className="size-5 cursor-pointer" />
</Button>
<Heading size="title.md" className="my-auto">
<Heading className="my-auto" size="title.md">
When will you reveal your NFTs?
</Heading>
</div>
</Flex>
<SelectReveal
form={form}
canCreateDelayedRevealBatch={props.canCreateDelayedRevealBatch}
client={props.client}
form={form}
/>
{form.watch("revealType") && (
<>
<CheckboxWithLabel>
<Checkbox
className="mt-3"
checked={form.watch("shuffle")}
className="mt-3"
onCheckedChange={(val) => form.setValue("shuffle", !!val)}
/>
<div className="flex flex-col items-center gap-1 md:flex-row">
@@ -262,13 +261,14 @@ export const BatchLazyMint: ComponentWithChildren<
</CheckboxWithLabel>
<div className="w-full md:w-[61%]">
<TransactionButton
txChainID={props.chainId}
className="mt-4 w-full"
transactionCount={1}
client={props.client}
disabled={!nftMetadatas.length}
type="submit"
isPending={form.formState.isSubmitting}
isLoggedIn={props.isLoggedIn}
isPending={form.formState.isSubmitting}
transactionCount={1}
txChainID={props.chainId}
type="submit"
>
{form.formState.isSubmitting
? `Uploading ${nftMetadatas.length} NFTs`
@@ -276,15 +276,15 @@ export const BatchLazyMint: ComponentWithChildren<
</TransactionButton>
{props.children}
</div>
<Text size="body.sm" mt={2}>
<TrackedLink
<Text mt={2} size="body.sm">
<UnderlineLink
className="text-primary-500"
href="https://support.thirdweb.com/dashboard/n5evQ4EfEjEifczEQaZ1hL/batch-upload-troubleshooting/5WMQFqfaUTU1C8NM8FtJ2X"
isExternal
category="batch-upload"
label="issues"
rel="noopener noreferrer"
target="_blank"
>
Experiencing issues uploading your files?
</TrackedLink>
</UnderlineLink>
</Text>
</>
)}
@@ -297,42 +297,45 @@ export const BatchLazyMint: ComponentWithChildren<
interface SelectRevealProps {
form: ReturnType<typeof useBatchLazyMintForm>;
canCreateDelayedRevealBatch: boolean;
client: ThirdwebClient;
}
const SelectReveal: React.FC<SelectRevealProps> = ({
form,
client,
canCreateDelayedRevealBatch,
}) => {
const [show, setShow] = useState(false);
const imageUrl = useImageFileOrUrl(form.watch("placeHolder.image"));
const imageUrl = form.watch("placeHolder.image");
return (
<Flex flexDir="column">
<Flex
flexDir={{ base: "column", md: "row" }}
gap={{ base: 3, md: 6 }}
mb={6}
flexDir={{ base: "column", md: "row" }}
>
<SelectOption
name="Reveal upon mint"
description="Collectors will immediately see the final NFT when they complete the minting"
isActive={form.watch("revealType") === "instant"}
name="Reveal upon mint"
onClick={() => form.setValue("revealType", "instant")}
/>
<SelectOption
name="Delayed Reveal"
description="Collectors will mint your placeholder image, then you reveal at a later time"
isActive={form.watch("revealType") === "delayed"}
onClick={() => form.setValue("revealType", "delayed")}
disabled={!canCreateDelayedRevealBatch}
disabledText="This contract doesn't implement Delayed Reveal"
isActive={form.watch("revealType") === "delayed"}
name="Delayed Reveal"
onClick={() => form.setValue("revealType", "delayed")}
/>
</Flex>
<div className="flex flex-col gap-3">
{form.watch("revealType") === "delayed" && (
<>
<Heading size="title.sm">Let&apos;s set a password</Heading>
<Alert status="warning" borderRadius="lg">
<Alert borderRadius="lg" status="warning">
<AlertIcon />
You&apos;ll need this password to reveal your NFTs. Please save it
somewhere safe.
@@ -343,10 +346,10 @@ const SelectReveal: React.FC<SelectRevealProps> = ({
gap={{ base: 4, md: 0 }}
>
<FormControl
isRequired
isInvalid={
!!form.getFieldState("password", form.formState).error
}
isRequired
mr={4}
>
<FormLabel>Password</FormLabel>
@@ -359,13 +362,13 @@ const SelectReveal: React.FC<SelectRevealProps> = ({
<InputRightElement cursor="pointer">
{show ? (
<EyeIcon
onClick={() => setShow(!show)}
className="size-3"
onClick={() => setShow(!show)}
/>
) : (
<EyeOffIcon
onClick={() => setShow(!show)}
className="size-3"
onClick={() => setShow(!show)}
/>
)}
</InputRightElement>
@@ -379,10 +382,10 @@ const SelectReveal: React.FC<SelectRevealProps> = ({
</FormErrorMessage>
</FormControl>
<FormControl
isRequired
isInvalid={
!!form.getFieldState("confirmPassword", form.formState).error
}
isRequired
>
<FormLabel>Confirm password</FormLabel>
<Input
@@ -410,12 +413,13 @@ const SelectReveal: React.FC<SelectRevealProps> = ({
<div className="w-auto md:w-[350px]">
<FileInput
accept={{ "image/*": [] }}
value={imageUrl}
showUploadButton
className="rounded border border-border transition-all duration-200"
client={client}
setValue={(file) =>
form.setValue("placeHolder.image", file)
}
className="rounded border border-border transition-all duration-200"
showUploadButton
value={imageUrl}
/>
</div>
<FormHelperText>
@@ -429,10 +433,10 @@ const SelectReveal: React.FC<SelectRevealProps> = ({
</FormErrorMessage>
</FormControl>
<FormControl
isRequired
isInvalid={
!!form.getFieldState("placeHolder.name", form.formState).error
}
isRequired
>
<FormLabel>Name</FormLabel>
<Input
@@ -1,12 +1,9 @@
import { CodeClient } from "@/components/ui/code/code.client";
import { ToolTipLabel } from "@/components/ui/tooltip";
/** biome-ignore-all lint/nursery/noNestedComponentDefinitions: FIXME */
import { FilePreview } from "@app/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/_common/file-preview";
import {
Box,
type BoxProps,
Flex,
IconButton,
Image,
type ImageProps,
Portal,
Select,
Table,
@@ -17,8 +14,6 @@ import {
Thead,
Tr,
} from "@chakra-ui/react";
import { useImageFileOrUrl } from "hooks/useImageFileOrUrl";
import { replaceIpfsUrl } from "lib/sdk";
import {
ChevronFirstIcon,
ChevronLastIcon,
@@ -27,84 +22,58 @@ import {
} from "lucide-react";
import { useMemo } from "react";
import { type Column, usePagination, useTable } from "react-table";
import type { ThirdwebClient } from "thirdweb";
import type { NFTInput } from "thirdweb/utils";
import { useThirdwebClient } from "../../@/constants/thirdweb.client";
import { CodeClient } from "@/components/ui/code/code.client";
import { ToolTipLabel } from "@/components/ui/tooltip";
const FileImage: React.FC<ImageProps> = ({ src, ...props }) => {
const client = useThirdwebClient();
const img = useImageFileOrUrl(
typeof src === "string" && src.startsWith("ipfs://")
? replaceIpfsUrl(src, client)
: src,
);
return <Image alt="" {...props} src={img} />;
};
const FileVideo: React.FC<
BoxProps &
Omit<React.ComponentProps<"video">, "ref" | "src"> & { src: string | File }
> = ({ src, ...props }) => {
const client = useThirdwebClient();
const video = useImageFileOrUrl(
typeof src === "string" && src.startsWith("ipfs://")
? replaceIpfsUrl(src, client)
: src,
);
return <Box as="video" {...props} src={video} />;
};
interface BatchTableProps {
data: NFTInput[];
portalRef: React.RefObject<HTMLDivElement | null>;
nextTokenIdToMint?: bigint;
client: ThirdwebClient;
}
export const BatchTable: React.FC<BatchTableProps> = ({
data,
portalRef,
nextTokenIdToMint,
client,
}) => {
const columns = useMemo(() => {
let cols: Column<NFTInput>[] = [];
if (nextTokenIdToMint !== undefined) {
cols = cols.concat({
Header: "Token ID",
accessor: (_row, index) => String(nextTokenIdToMint + BigInt(index)),
Header: "Token ID",
});
}
cols = cols.concat([
{
Header: "Image",
accessor: (row) => row.image,
Cell: ({ cell: { value } }: { cell: { value?: string } }) => (
<FileImage
flexShrink={0}
boxSize={24}
objectFit="contain"
src={value}
alt=""
<FilePreview
className="size-24 shrink-0 rounded-lg object-contain"
client={client}
srcOrFile={value}
/>
),
Header: "Image",
},
{
Header: "Animation Url",
accessor: (row) => row.animation_url,
Cell: ({ cell: { value } }: { cell: { value?: string } }) => (
<FileVideo
flexShrink={0}
boxSize={24}
objectFit="contain"
src={value || ""}
autoPlay
playsInline
muted
loop
<FilePreview
className="size-24 shrink-0 rounded-lg"
client={client}
srcOrFile={value}
/>
),
Header: "Animation Url",
},
{ Header: "Name", accessor: (row) => row.name },
{ accessor: (row) => row.name, Header: "Name" },
{
Header: "Description",
accessor: (row) => (
<ToolTipLabel label={row.description}>
<p className="line-clamp-6 whitespace-pre-wrap">
@@ -112,9 +81,9 @@ export const BatchTable: React.FC<BatchTableProps> = ({
</p>
</ToolTipLabel>
),
Header: "Description",
},
{
Header: "Attributes",
accessor: (row) => row.attributes || row.properties,
// biome-ignore lint/suspicious/noExplicitAny: FIXME
Cell: ({ cell }: { cell: any }) =>
@@ -125,12 +94,13 @@ export const BatchTable: React.FC<BatchTableProps> = ({
scrollableClassName="max-w-[300px]"
/>
) : null,
Header: "Attributes",
},
{ Header: "External URL", accessor: (row) => row.external_url },
{ Header: "Background Color", accessor: (row) => row.background_color },
{ accessor: (row) => row.external_url, Header: "External URL" },
{ accessor: (row) => row.background_color, Header: "Background Color" },
]);
return cols;
}, [nextTokenIdToMint]);
}, [nextTokenIdToMint, client]);
const {
getTableProps,
@@ -156,8 +126,8 @@ export const BatchTable: React.FC<BatchTableProps> = ({
columns,
data,
initialState: {
pageSize: 50,
pageIndex: 0,
pageSize: 50,
},
},
// will be fixed with @tanstack/react-table v8
@@ -168,7 +138,7 @@ export const BatchTable: React.FC<BatchTableProps> = ({
// Render the UI for your table
return (
<Flex flexGrow={1} overflow="auto">
<TableContainer maxW="100%">
<TableContainer className="w-full" maxW="100%">
<Table {...getTableProps()}>
<Thead>
{headerGroups.map((headerGroup, index) => (
@@ -191,8 +161,8 @@ export const BatchTable: React.FC<BatchTableProps> = ({
return (
<Tr
{...row.getRowProps()}
borderBottomWidth={1}
_last={{ borderBottomWidth: 0 }}
borderBottomWidth={1}
// biome-ignore lint/suspicious/noArrayIndexKey: FIXME
key={rowIndex}
>
@@ -219,15 +189,15 @@ export const BatchTable: React.FC<BatchTableProps> = ({
<div className="flex w-full items-center justify-center">
<div className="flex flex-row items-center gap-2">
<IconButton
isDisabled={!canPreviousPage}
aria-label="first page"
icon={<ChevronFirstIcon className="size-4" />}
isDisabled={!canPreviousPage}
onClick={() => gotoPage(0)}
/>
<IconButton
isDisabled={!canPreviousPage}
aria-label="previous page"
icon={<ChevronLeftIcon className="size-4" />}
isDisabled={!canPreviousPage}
onClick={() => previousPage()}
/>
<p className="whitespace-nowrap">
@@ -235,15 +205,15 @@ export const BatchTable: React.FC<BatchTableProps> = ({
<strong>{pageOptions.length}</strong>
</p>
<IconButton
isDisabled={!canNextPage}
aria-label="next page"
icon={<ChevronRightIcon className="size-4" />}
isDisabled={!canNextPage}
onClick={() => nextPage()}
/>
<IconButton
isDisabled={!canNextPage}
aria-label="last page"
icon={<ChevronLastIcon className="size-4" />}
isDisabled={!canNextPage}
onClick={() => gotoPage(pageCount - 1)}
/>
@@ -1,8 +1,8 @@
import { InfoIcon } from "lucide-react";
import type { JSX, MouseEventHandler } from "react";
import { Card } from "@/components/ui/card";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { InfoIcon } from "lucide-react";
import type { JSX, MouseEventHandler } from "react";
interface SelectOptionProps extends React.HTMLAttributes<HTMLDivElement> {
name: string;
@@ -1,7 +1,7 @@
import { UnorderedList } from "@/components/ui/List/List";
import { InlineCode } from "@/components/ui/inline-code";
import { cn } from "@/lib/utils";
import { UploadIcon } from "lucide-react";
import { InlineCode } from "@/components/ui/inline-code";
import { UnorderedList } from "@/components/ui/List/List";
import { cn } from "@/lib/utils";
interface UploadStepProps {
// biome-ignore lint/suspicious/noExplicitAny: FIXME
@@ -61,16 +61,16 @@ export const UploadStep: React.FC<UploadStepProps> = ({
<li>
Files <em>must</em> contain one .csv or .json file with metadata. -{" "}
<a
download
className="text-link-foreground hover:text-foreground"
download
href="/assets/examples/example.csv"
>
Download example.csv
</a>
.{" "}
<a
download
className="text-link-foreground hover:text-foreground"
download
href="/assets/examples/example.json"
>
Download example.json
@@ -111,8 +111,8 @@ export const UploadStep: React.FC<UploadStepProps> = ({
<InlineCode code="animation_url" /> column and add the IPFS hashes
there.{" "}
<a
download
className="text-link-foreground hover:text-foreground"
download
href="/assets/examples/example-with-ipfs.csv"
>
Download example.csv
@@ -123,8 +123,8 @@ export const UploadStep: React.FC<UploadStepProps> = ({
the name of your files to the <InlineCode code="image" /> and
<InlineCode code="animation_url" /> column.{" "}
<a
download
className="text-link-foreground hover:text-foreground"
download
href="/assets/examples/example-with-maps.csv"
>
Download example.csv
@@ -1,7 +1,11 @@
"use client";
import { CircleXIcon, ExternalLinkIcon } from "lucide-react";
import Link from "next/link";
import { useState, useTransition } from "react";
import { toast } from "sonner";
import type { Team } from "@/api/team";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import {
Sheet,
SheetContent,
@@ -9,16 +13,12 @@ import {
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet";
import { PRO_CONTACT_US_URL } from "@/constants/pro";
import { useStripeRedirectEvent } from "@/hooks/stripe/redirect-event";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { CircleXIcon, ExternalLinkIcon } from "lucide-react";
import Link from "next/link";
import { useState, useTransition } from "react";
import { toast } from "sonner";
import { useStripeRedirectEvent } from "../../../../../app/(app)/(stripe)/stripe-redirect/stripeRedirectChannel";
import { buildCancelPlanUrl } from "../../../../../app/(app)/(stripe)/utils/build-url";
import { PRO_CONTACT_US_URL } from "../../../../../constants/pro";
import { pollWithTimeout } from "../../../../../utils/pollWithTimeout";
import { tryCatch } from "../../../../../utils/try-catch";
import { pollWithTimeout } from "@/utils/pollWithTimeout";
import { buildCancelPlanUrl } from "@/utils/stripe/build-url";
import { tryCatch } from "@/utils/try-catch";
export function CancelPlanButton(props: {
teamId: string;
@@ -26,13 +26,15 @@ export function CancelPlanButton(props: {
currentPlan: Team["billingPlan"];
billingStatus: Team["billingStatus"];
getTeam: () => Promise<Team>;
disabled?: boolean;
}) {
// shortcut the sheet in case the user is in the default state
if (props.billingStatus !== "invalidPayment" && props.currentPlan !== "pro") {
return (
<ImmediateCancelPlanButton
teamId={props.teamId}
disabled={props.disabled}
getTeam={props.getTeam}
teamId={props.teamId}
/>
);
}
@@ -40,7 +42,12 @@ export function CancelPlanButton(props: {
return (
<Sheet>
<SheetTrigger asChild>
<Button variant="outline" size="sm" className="gap-2 bg-background">
<Button
className="gap-2 bg-background"
disabled={props.disabled}
size="sm"
variant="outline"
>
<CircleXIcon className="size-4 text-muted-foreground" />
Cancel Plan
</Button>
@@ -73,9 +80,10 @@ function UnpaidInvoicesWarning({ teamSlug }: { teamSlug: string }) {
You have unpaid invoices. Please pay them before cancelling your plan.
</p>
<Button variant="outline" asChild className="w-full gap-2 bg-card">
<Button asChild className="w-full gap-2 bg-card" variant="outline">
<Link
href={`/team/${teamSlug}/~/settings/invoices?status=open`}
rel="noopener noreferrer"
target="_blank"
>
See Invoices
@@ -95,7 +103,7 @@ function ProPlanCancelPlanSheetContent() {
Please contact us to cancel your Pro plan
</p>
<Button variant="outline" asChild className="w-full gap-2 bg-card">
<Button asChild className="w-full gap-2 bg-card" variant="outline">
<Link href={PRO_CONTACT_US_URL} target="_blank">
Contact Us <ExternalLinkIcon className="size-4" />
</Link>
@@ -107,6 +115,7 @@ function ProPlanCancelPlanSheetContent() {
function ImmediateCancelPlanButton(props: {
teamId: string;
getTeam: () => Promise<Team>;
disabled?: boolean;
}) {
const router = useDashboardRouter();
const [isRoutePending, startTransition] = useTransition();
@@ -141,11 +150,11 @@ function ImmediateCancelPlanButton(props: {
return (
<Button
variant="outline"
size="sm"
className="gap-2 bg-background"
disabled={showPlanSpinner}
asChild
className="gap-2 bg-background"
disabled={showPlanSpinner || props.disabled}
size="sm"
variant="outline"
>
<Link href={buildCancelPlanUrl({ teamId: props.teamId })} target="_blank">
{showPlanSpinner ? (
@@ -1,14 +1,13 @@
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import type { Account, BillingCredit } from "@3rdweb-sdk/react/hooks/useApi";
import { ChainIconClient } from "components/icons/ChainIcon";
import { formatDistance } from "date-fns";
import { useTrack } from "hooks/analytics/useTrack";
import { useLocalStorage } from "hooks/useLocalStorage";
import { CircleAlertIcon } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import type { ThirdwebClient } from "thirdweb";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import type { Account, BillingCredit } from "@/hooks/useApi";
import { useLocalStorage } from "@/hooks/useLocalStorage";
import { ChainIconClient } from "@/icons/ChainIcon";
import { formatToDollars } from "./formatToDollars";
interface CreditsItemProps {
@@ -28,8 +27,6 @@ export const CreditsItem: React.FC<CreditsItemProps> = ({
client,
teamSlug,
}) => {
const trackEvent = useTrack();
const [hasAppliedForOpGrant] = useLocalStorage(
`appliedForOpGrant-${twAccount.id}`,
false,
@@ -55,23 +52,23 @@ export const CreditsItem: React.FC<CreditsItemProps> = ({
<div className="absolute top-0 right-0">
{isOpCredit ? (
<ChainIconClient
src="ipfs://QmcxZHpyJa8T4i63xqjPYrZ6tKrt55tZJpbXcjSDKuKaf9/optimism/512.png"
className="size-6"
client={client}
src="ipfs://QmcxZHpyJa8T4i63xqjPYrZ6tKrt55tZJpbXcjSDKuKaf9/optimism/512.png"
/>
) : isTwCredit ? (
<Image
src={require("../../../../../public/brand/thirdweb-icon.svg")}
alt="tw-credit"
className="size-6"
objectFit="contain"
src={require("../../../../public/brand/thirdweb-icon.svg")}
/>
) : isStartupCredit ? (
<Image
src={require("../../../../../public/brand/thirdweb-icon.svg")}
alt="tw-credit"
className="size-6"
objectFit="contain"
src={require("../../../../public/brand/thirdweb-icon.svg")}
/>
) : null}
</div>
@@ -134,11 +131,6 @@ export const CreditsItem: React.FC<CreditsItemProps> = ({
<Link
href={`/team/${teamSlug}/~/settings/credits`}
onClick={() => {
trackEvent({
category: "op-sponsorship",
action: "click",
label: "apply-now",
});
if (onClickApply) {
onClickApply();
}
@@ -2,11 +2,8 @@
// TODO - convert to RSC
import {
type Account,
useAccountCredits,
} from "@3rdweb-sdk/react/hooks/useApi";
import type { ThirdwebClient } from "thirdweb";
import { type Account, useAccountCredits } from "@/hooks/useApi";
import { CreditsItem } from "./CreditsItem";
export const CreditsInfoCard = (props: {
@@ -26,19 +23,19 @@ export const CreditsInfoCard = (props: {
return (
<section className="flex flex-col gap-4">
<CreditsItem
client={props.client}
credit={opCredit}
isOpCreditDefault={true}
twAccount={props.twAccount}
client={props.client}
teamSlug={props.teamSlug}
twAccount={props.twAccount}
/>
{restCredits?.map((credit) => (
<CreditsItem
key={credit.couponId}
credit={credit}
twAccount={props.twAccount}
client={props.client}
credit={credit}
key={credit.couponId}
teamSlug={props.teamSlug}
twAccount={props.twAccount}
/>
))}
</section>
@@ -1,25 +1,23 @@
"use client";
import { useTransition } from "react";
import type { Team } from "@/api/team";
import { PricingCard } from "@/components/blocks/pricing-card";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { PRO_CONTACT_US_URL } from "@/constants/pro";
import { useStripeRedirectEvent } from "@/hooks/stripe/redirect-event";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { useTransition } from "react";
import { useStripeRedirectEvent } from "../../../../app/(app)/(stripe)/stripe-redirect/stripeRedirectChannel";
import { getValidTeamPlan } from "../../../../app/(app)/team/components/TeamHeader/getValidTeamPlan";
import { PRO_CONTACT_US_URL } from "../../../../constants/pro";
import { getValidTeamPlan } from "@/utils/getValidTeamPlan";
// this is used to determine whether to show "Upgrade" or "Downgrade" label based on tier level
const planToTierRecord: Record<Team["billingPlan"], number> = {
free: 0,
starter_legacy: 1,
starter: 2,
growth_legacy: 3,
growth: 4,
accelerate: 5,
scale: 6,
free: 0,
growth: 4,
growth_legacy: 3,
pro: 7,
scale: 6,
starter: 2,
};
interface BillingPricingProps {
@@ -71,11 +69,7 @@ export const BillingPricing: React.FC<BillingPricingProps> = ({
validTeamPlan === "starter" ||
validTeamPlan === "growth_legacy"));
const highlightStarterPlan =
highlightPlan === "starter" ||
(!highlightPlan &&
!isCurrentPlanScheduledToCancel &&
validTeamPlan === "starter_legacy");
const highlightStarterPlan = highlightPlan === "starter";
const highlightScalePlan =
highlightPlan === "scale" ||
(!highlightPlan &&
@@ -105,63 +99,63 @@ export const BillingPricing: React.FC<BillingPricingProps> = ({
<PricingCard
billingPlan="starter"
billingStatus={team.billingStatus}
current={validTeamPlan === "starter"}
cta={getPlanCta(
validTeamPlan,
"starter",
isCurrentPlanScheduledToCancel,
)}
teamSlug={team.slug}
teamId={team.id}
highlighted={highlightStarterPlan}
current={validTeamPlan === "starter"}
getTeam={getTeam}
highlighted={highlightStarterPlan}
teamId={team.id}
teamSlug={team.slug}
/>
{/* Growth */}
<PricingCard
billingPlan="growth"
billingStatus={team.billingStatus}
activeTrialEndsAt={
validTeamPlan === "growth" ? trialPeriodEndedAt : undefined
}
current={validTeamPlan === "growth"}
billingPlan="growth"
billingStatus={team.billingStatus}
cta={getPlanCta(
validTeamPlan,
"growth",
isCurrentPlanScheduledToCancel,
)}
highlighted={highlightGrowthPlan}
teamSlug={team.slug}
teamId={team.id}
current={validTeamPlan === "growth"}
getTeam={getTeam}
highlighted={highlightGrowthPlan}
teamId={team.id}
teamSlug={team.slug}
/>
{/* Scale */}
<PricingCard
billingPlan="scale"
billingStatus={team.billingStatus}
teamSlug={team.slug}
teamId={team.id}
current={validTeamPlan === "scale"}
cta={getPlanCta(
validTeamPlan,
"scale",
isCurrentPlanScheduledToCancel,
)}
highlighted={highlightScalePlan}
current={validTeamPlan === "scale"}
getTeam={getTeam}
highlighted={highlightScalePlan}
teamId={team.id}
teamSlug={team.slug}
/>
{/* Pro */}
<PricingCard
billingPlan="pro"
billingStatus={team.billingStatus}
teamSlug={team.slug}
teamId={team.id}
current={validTeamPlan === "pro"}
cta={getPlanCta(validTeamPlan, "pro", isCurrentPlanScheduledToCancel)}
highlighted={highlightProPlan}
current={validTeamPlan === "pro"}
getTeam={getTeam}
highlighted={highlightProPlan}
teamId={team.id}
teamSlug={team.slug}
/>
</div>
</div>
@@ -176,8 +170,8 @@ function getPlanCta(
// if the CURRENT plan is pro, show contact us link
if (currentPlan === "pro") {
return {
label: "Contact us",
href: PRO_CONTACT_US_URL,
label: "Contact us",
type: "link",
};
}
@@ -1,13 +1,13 @@
import type { Meta, StoryObj } from "@storybook/nextjs";
import { useId, useMemo, useState } from "react";
import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Switch } from "@/components/ui/switch";
import type { Meta, StoryObj } from "@storybook/react";
import { useMemo, useState } from "react";
import { type CouponData, CouponsUI } from "./CouponsUI";
const meta = {
title: "Billing/Coupons",
component: Story,
title: "Billing/Coupons",
} satisfies Meta<typeof CouponsUI>;
export default meta;
@@ -16,54 +16,54 @@ type Story = StoryObj<typeof CouponsUI>;
const couponStubs: CouponData[] = [
// Forever
{
id: "di_1QAE7jCQUO4TBFqFQgnS4Qj4",
start: 1729011691,
end: null,
coupon: {
id: "forever_active",
name: "Example 1",
duration: "forever",
duration_in_months: null,
id: "forever_active",
name: "Example 1",
},
end: null,
id: "di_1QAE7jCQUO4TBFqFQgnS4Qj4",
start: 1729011691,
},
// Once
{
id: "once",
start: 1733391052,
end: null,
coupon: {
id: "once_active",
name: "Example 2",
duration: "once",
duration_in_months: null,
id: "once_active",
name: "Example 2",
},
end: null,
id: "once",
start: 1733391052,
},
// Repeating - 3 months
{
id: "repeating-3",
start: 1733391629,
end: 1743391629, // Will end in the future
coupon: {
id: "repeating_active",
name: "Example 3",
duration: "repeating",
duration_in_months: 3,
id: "repeating_active",
name: "Example 3",
},
end: 1743391629,
id: "repeating-3", // Will end in the future
start: 1733391629,
},
// Repeating - 1 month
{
id: "repeating-1",
start: 1733391713,
end: 1736391713,
coupon: {
id: "repeating_single",
name: "Example 4",
duration: "repeating",
duration_in_months: 1,
id: "repeating_single",
name: "Example 4",
},
end: 1736391713,
id: "repeating-1",
start: 1733391713,
},
];
@@ -102,6 +102,9 @@ function Story() {
200 | 400 | 401 | 409 | 429 | 500
>(200);
const hasAccountCouponId = useId();
const paymentSetupId = useId();
return (
<div className="container max-w-[1100px] py-10">
<div className="mb-8 space-y-6">
@@ -109,22 +112,22 @@ function Story() {
<div className="space-y-3">
<h3 className="font-semibold text-lg">Status</h3>
<RadioGroup
value={status}
className="flex gap-4"
onValueChange={(value) =>
setStatus(value as "pending" | "error" | "success")
}
className="flex gap-4"
value={status}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="pending" id="status-pending" />
<RadioGroupItem value="pending" />
<Label htmlFor="status-pending">Pending</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="error" id="status-error" />
<RadioGroupItem value="error" />
<Label htmlFor="status-error">Error</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="success" id="status-success" />
<RadioGroupItem value="success" />
<Label htmlFor="status-success">Success</Label>
</div>
</RadioGroup>
@@ -133,22 +136,22 @@ function Story() {
<div className="space-y-3">
<h3 className="font-semibold text-lg">Active Coupons</h3>
<RadioGroup
value={activeCouponsType}
className="flex gap-4"
onValueChange={(value) =>
setActiveCouponsType(value as "multiple" | "zero" | "one")
}
className="flex gap-4"
value={activeCouponsType}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="multiple" id="coupons-multiple" />
<RadioGroupItem value="multiple" />
<Label htmlFor="coupons-multiple">Multiple</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="zero" id="coupons-zero" />
<RadioGroupItem value="zero" />
<Label htmlFor="coupons-zero">Zero</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="one" id="coupons-one" />
<RadioGroupItem value="one" />
<Label htmlFor="coupons-one">One</Label>
</div>
</RadioGroup>
@@ -157,19 +160,19 @@ function Story() {
<div className="space-y-3">
<h3 className="font-semibold text-lg">Apply Coupon Status</h3>
<RadioGroup
value={applyCouponStatus.toString()}
className="flex gap-4"
onValueChange={(value) =>
setApplyCouponStatus(
Number.parseInt(value) as 200 | 400 | 401 | 409 | 429 | 500,
)
}
value={applyCouponStatus.toString()}
>
{[200, 400, 401, 409, 429, 500].map((code) => (
<div key={code} className="flex items-center space-x-2">
<div className="flex items-center space-x-2" key={code}>
<RadioGroupItem
value={code.toString()}
id={`status-${code}`}
value={code.toString()}
/>
<Label htmlFor={`status-${code}`}>{code}</Label>
</div>
@@ -183,52 +186,52 @@ function Story() {
<div className="space-y-4">
<div className="flex items-center gap-4">
<Switch
id="has-account-coupon"
checked={hasAccountCoupon}
id={hasAccountCouponId}
onCheckedChange={setHasAccountCoupon}
/>
<Label htmlFor="has-account-coupon">Has Account Coupon</Label>
<Label htmlFor={hasAccountCouponId}>Has Account Coupon</Label>
</div>
<div className="flex items-center gap-4">
<Switch
id="payment-setup"
checked={isPaymentSetup}
id={paymentSetupId}
onCheckedChange={setIsPaymentSetup}
/>
<Label htmlFor="payment-setup">Payment Setup</Label>
<Label htmlFor={paymentSetupId}>Payment Setup</Label>
</div>
</div>
</div>
</div>
<CouponsUI
status={status}
activeCoupons={activeCoupons}
accountCouponId={hasAccountCoupon ? activeCoupons[0]?.id : undefined}
isPaymentSetup={isPaymentSetup}
activeCoupons={activeCoupons}
applyCoupon={async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
return {
status: applyCouponStatus,
data:
applyCouponStatus === 200
? {
id: "xyz",
start: 1727992716,
end: 1759528716,
coupon: {
id: "test",
name: "TEST COUPON",
duration: "repeating",
duration_in_months: 12,
id: "test",
name: "TEST COUPON",
},
end: 1759528716,
id: "xyz",
start: 1727992716,
}
: null,
status: applyCouponStatus,
};
}}
deleteCoupon={async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
}}
isPaymentSetup={isPaymentSetup}
status={status}
/>
</div>
);
@@ -1,6 +1,6 @@
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { Suspense } from "react";
import { getAuthToken } from "../../../../../app/(app)/api/lib/getAuthToken";
import { getAuthToken } from "@/api/auth-token";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { CouponsClient } from "./CouponsClient";
import type { CouponData } from "./CouponsUI";
@@ -39,10 +39,10 @@ async function AsyncCoupons(props: {
) {
return (
<CouponsClient
status="error"
activeCoupons={[]}
accountCouponId={undefined}
activeCoupons={[]}
isPaymentSetup={props.isPaymentSetup}
status="error"
teamId={props.teamId}
/>
);
@@ -58,34 +58,31 @@ async function AsyncCoupons(props: {
return (
<CouponsClient
status="success"
activeCoupons={allCouponsData.data}
accountCouponId={accountCouponData.data?.id}
activeCoupons={allCouponsData.data}
isPaymentSetup={props.isPaymentSetup}
status="success"
teamId={props.teamId}
/>
);
}
export function Coupons(props: {
teamId: string;
isPaymentSetup: boolean;
}) {
export function Coupons(props: { teamId: string; isPaymentSetup: boolean }) {
return (
<Suspense
fallback={
<CouponsClient
status="pending"
activeCoupons={[]}
accountCouponId={undefined}
activeCoupons={[]}
isPaymentSetup={true}
status="pending"
teamId={props.teamId}
/>
}
>
<AsyncCoupons
teamId={props.teamId}
isPaymentSetup={props.isPaymentSetup}
teamId={props.teamId}
/>
</Suspense>
);
@@ -16,6 +16,35 @@ export function CouponsClient(
return (
<CouponsUI
{...props}
applyCoupon={async (promoCode: string) => {
const res = await apiServerProxy<{ data: CouponData }>({
body: JSON.stringify({
promoCode,
teamId: props.teamId,
}),
headers: {
"Content-Type": "application/json",
},
method: "POST",
pathname: "/v1/coupons/redeem",
});
if (!res.ok) {
return {
data: null,
status: res.status,
};
}
const json = res.data;
router.refresh();
return {
data: json.data,
status: 200,
};
}}
deleteCoupon={async () => {
const res = await apiServerProxy({
method: "DELETE",
@@ -29,35 +58,6 @@ export function CouponsClient(
router.refresh();
}}
applyCoupon={async (promoCode: string) => {
const res = await apiServerProxy<{ data: CouponData }>({
method: "POST",
pathname: "/v1/coupons/redeem",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
promoCode,
teamId: props.teamId,
}),
});
if (!res.ok) {
return {
status: res.status,
data: null,
};
}
const json = res.data;
router.refresh();
return {
status: 200,
data: json.data,
};
}}
/>
);
}
@@ -1,17 +1,5 @@
"use client";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Button } from "@/components/ui/button";
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { useMutation } from "@tanstack/react-query";
import { format, fromUnixTime } from "date-fns";
import {
@@ -22,6 +10,18 @@ import {
Trash2Icon,
} from "lucide-react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { ToolTipLabel } from "@/components/ui/tooltip";
type Coupon = {
id: string;
@@ -211,17 +211,17 @@ function DeleteCouponButton(props: {
return (
<Button
variant="outline"
size="sm"
aria-label="Remove coupon"
disabled={props.disabled}
onClick={() => {
const promise = deleteCoupon.mutateAsync();
toast.promise(promise, {
success: "Coupon removed successfully",
error: "Failed to remove coupon",
success: "Coupon removed successfully",
});
}}
aria-label="Remove coupon"
size="sm"
variant="outline"
>
{deleteCoupon.isPending ? (
<Spinner className="size-4" />
@@ -1,14 +1,14 @@
"use client";
import { AlertTriangleIcon } from "lucide-react";
import Link from "next/link";
import type { Team } from "@/api/team";
import { Button, type ButtonProps } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import type { ProductSKU } from "@/types/billing";
import {
buildBillingPortalUrl,
buildCheckoutUrl,
} from "../../app/(app)/(stripe)/utils/build-url";
import type { Team } from "../api/team";
import type { ProductSKU } from "../lib/billing";
import { cn } from "../lib/utils";
import { Button, type ButtonProps } from "./ui/button";
} from "@/utils/stripe/build-url";
export function CheckoutButton(props: {
buttonProps?: Omit<ButtonProps, "children">;
@@ -38,11 +38,12 @@ export function CheckoutButton(props: {
}}
>
<Link
target="_blank"
href={buildCheckoutUrl({
teamSlug: props.teamSlug,
sku: props.sku,
teamSlug: props.teamSlug,
})}
rel="noopener noreferrer"
target="_blank"
>
{props.children}
</Link>
@@ -58,8 +59,8 @@ function BillingWarning({ teamSlug }: { teamSlug: string }) {
<p className="text-sm">
You have outstanding invoices. Please{" "}
<Link
href={`/team/${teamSlug}/~/settings/invoices?status=open`}
className="font-medium text-amber-700 underline transition-colors hover:text-amber-900 dark:text-amber-400 dark:hover:text-amber-300"
href={`/team/${teamSlug}/~/settings/invoices?status=open`}
>
pay them
</Link>{" "}
@@ -77,19 +78,20 @@ export function BillingPortalButton(props: {
return (
<Button
{...props.buttonProps}
asChild
className={cn(props.buttonProps?.className, "gap-2")}
disabled={props.buttonProps?.disabled}
asChild
onClick={async (e) => {
props.buttonProps?.onClick?.(e);
}}
>
<Link
<a
href={buildBillingPortalUrl({ teamSlug: props.teamSlug })}
rel="noopener noreferrer"
target="_blank"
>
{props.children}
</Link>
</a>
</Button>
);
}
@@ -1,7 +1,7 @@
export const formatToDollars = (cents: number) => {
const dollars = cents / 100;
return new Intl.NumberFormat(undefined, {
style: "currency",
currency: "USD",
style: "currency",
}).format(dollars);
};
@@ -1,20 +1,21 @@
"use client";
import { reSubscribePlan } from "@/actions/billing";
import type { Team } from "@/api/team";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Button } from "@/components/ui/button";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { useMutation } from "@tanstack/react-query";
import { RefreshCwIcon } from "lucide-react";
import { useState, useTransition } from "react";
import { toast } from "sonner";
import { pollWithTimeout } from "../../../../../utils/pollWithTimeout";
import { tryCatch } from "../../../../../utils/try-catch";
import { reSubscribePlan } from "@/actions/billing";
import type { Team } from "@/api/team";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { pollWithTimeout } from "../../../utils/pollWithTimeout";
import { tryCatch } from "../../../utils/try-catch";
export function RenewSubscriptionButton(props: {
teamId: string;
getTeam: () => Promise<Team>;
disabled?: boolean;
}) {
const router = useDashboardRouter();
const [isRoutePending, setTransition] = useTransition();
@@ -67,11 +68,11 @@ export function RenewSubscriptionButton(props: {
return (
<Button
onClick={handleRenewSubscription}
variant="default"
size="sm"
className="gap-2"
disabled={showSpinner}
disabled={showSpinner || props.disabled}
onClick={handleRenewSubscription}
size="sm"
variant="default"
>
{showSpinner ? (
<Spinner className="size-4" />
@@ -1,7 +1,9 @@
/* eslint-disable no-restricted-syntax */
import { Input } from "@/components/ui/input";
import { PercentIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
type InputProps = React.ComponentProps<typeof Input>;
@@ -44,10 +46,13 @@ export const BasisPointsInput: React.FC<BasisPointsInputProps> = ({
return (
<div className="flex overflow-hidden rounded-lg border border-border ring-offset-background focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2">
<Input
className="rounded-r-none border-none focus-visible:ring-0 focus-visible:ring-offset-0"
value={stringValue}
{...restInputProps}
onChange={(e) => setStringValue(e.target.value)}
className={cn(
"rounded-r-none border-none focus-visible:ring-0 focus-visible:ring-offset-0",
restInputProps.className,
)}
maxLength={5}
onBlur={(e) => {
const val = e.target.value;
const validValue = val.match(
@@ -59,7 +64,7 @@ export const BasisPointsInput: React.FC<BasisPointsInputProps> = ({
setStringValue("0.00");
}
}}
maxLength={5}
onChange={(e) => setStringValue(e.target.value)}
/>
<div className="flex items-center justify-center border-border border-l bg-card px-3 font-medium">
<PercentIcon className="size-4" />
@@ -1,3 +1,5 @@
import { useMemo, useState } from "react";
import { isAddress, NATIVE_TOKEN_ADDRESS, ZERO_ADDRESS } from "thirdweb";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
@@ -7,11 +9,9 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { CURRENCIES, type CurrencyMetadata } from "@/constants/currencies";
import { useAllChainsData } from "@/hooks/chains/allChains";
import { cn } from "@/lib/utils";
import { CURRENCIES, type CurrencyMetadata } from "constants/currencies";
import { useMemo, useState } from "react";
import { NATIVE_TOKEN_ADDRESS, ZERO_ADDRESS, isAddress } from "thirdweb";
import { useAllChainsData } from "../../hooks/chains/allChains";
interface CurrencySelectorProps {
value: string;
@@ -113,15 +113,15 @@ export function CurrencySelector({
</Button>
<Input
className="w-full rounded-none"
required
placeholder="ERC20 Address"
value={editCustomCurrency}
onChange={(e) => setEditCustomCurrency(e.target.value)}
placeholder="ERC20 Address"
required
value={editCustomCurrency}
/>
<Button
className="rounded-r-md rounded-l-none"
onClick={addCustomCurrency}
disabled={!isAddress(editCustomCurrency)}
onClick={addCustomCurrency}
>
Save
</Button>
@@ -139,13 +139,6 @@ export function CurrencySelector({
>
<Select
disabled={isDisabled}
value={
isPaymentsSelector
? value
: value?.toLowerCase() === ZERO_ADDRESS.toLowerCase()
? NATIVE_TOKEN_ADDRESS.toLowerCase()
: value?.toLowerCase()
}
onValueChange={(val) => {
if (val === "custom") {
setIsAddingCurrency(true);
@@ -155,6 +148,13 @@ export function CurrencySelector({
} as React.ChangeEvent<HTMLSelectElement>);
}
}}
value={
isPaymentsSelector
? value
: value?.toLowerCase() === ZERO_ADDRESS.toLowerCase()
? NATIVE_TOKEN_ADDRESS.toLowerCase()
: value?.toLowerCase()
}
>
<SelectTrigger>
<SelectValue placeholder="Select Currency" />
@@ -1,15 +1,15 @@
import type { Meta, StoryObj } from "@storybook/react";
import { BadgeContainer } from "../../../stories/utils";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { BadgeContainer } from "@/storybook/utils";
import { DangerSettingCard } from "./DangerSettingCard";
const meta = {
title: "blocks/Cards/DangerSettingCard",
component: Story,
parameters: {
nextjs: {
appDirectory: true,
},
},
title: "blocks/Cards/DangerSettingCard",
} satisfies Meta<typeof Story>;
export default meta;
@@ -24,29 +24,29 @@ function Story() {
<div className="container flex max-w-[1000px] flex-col gap-8 py-10">
<BadgeContainer label="Base">
<DangerSettingCard
title="This is a title"
description="This is a description"
buttonLabel="Some Action"
buttonOnClick={() => {}}
isPending={false}
confirmationDialog={{
title: "This is confirmation title",
description: "This is confirmation description",
title: "This is confirmation title",
}}
description="This is a description"
isPending={false}
title="This is a title"
/>
</BadgeContainer>
<BadgeContainer label="Loading">
<DangerSettingCard
title="This is a title"
description="This is a description"
buttonLabel="Some Action"
buttonOnClick={() => {}}
isPending={true}
confirmationDialog={{
title: "This is confirmation title",
description: "This is confirmation description",
title: "This is confirmation title",
}}
description="This is a description"
isPending={true}
title="This is a title"
/>
</BadgeContainer>
</div>
@@ -1,4 +1,4 @@
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import {
@@ -10,7 +10,7 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { useState } from "react";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { cn } from "../../lib/utils";
import { DynamicHeight } from "../ui/DynamicHeight";
@@ -58,19 +58,19 @@ export function DangerSettingCard(props: {
)}
>
<Dialog
open={isConfirmationDialogOpen}
onOpenChange={(v) => {
setIsConfirmationDialogOpen(v);
if (!v) {
props.confirmationDialog.onClose?.();
}
}}
open={isConfirmationDialogOpen}
>
<DialogTrigger asChild>
<Button
variant="destructive"
className="gap-2 bg-red-600 font-semibold text-white hover:bg-red-600/80"
disabled={props.isDisabled || props.isPending}
variant="destructive"
>
{props.isPending && <Spinner className="size-3" />}
{props.buttonLabel}
@@ -99,10 +99,10 @@ export function DangerSettingCard(props: {
</DialogClose>
<Button
variant="destructive"
className="gap-2 bg-red-600 font-semibold text-white hover:bg-red-600/80"
onClick={props.buttonOnClick}
disabled={props.isPending}
onClick={props.buttonOnClick}
variant="destructive"
>
{props.isPending && <Spinner className="size-3" />}
{props.buttonLabel}
@@ -7,9 +7,10 @@ export function DocLink(props: {
}) {
return (
<Link
href={props.link}
target="_blank"
className="flex items-center gap-2 whitespace-nowrap text-muted-foreground text-sm hover:text-foreground"
href={props.link}
rel="noopener noreferrer"
target="_blank"
>
<props.icon className="size-4" />
{props.label}
@@ -1,9 +1,9 @@
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Button } from "@/components/ui/button";
import { useMutation } from "@tanstack/react-query";
import { DownloadIcon } from "lucide-react";
import Papa from "papaparse";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { cn } from "../../lib/utils";
export function ExportToCSVButton(props: {
@@ -34,12 +34,12 @@ export function ExportToCSVButton(props: {
return (
<Button
variant="outline"
disabled={props.disabled || exportMutation.isPending}
className={cn("flex items-center gap-2 border text-xs", props.className)}
disabled={props.disabled || exportMutation.isPending}
onClick={async () => {
exportMutation.mutate();
}}
variant="outline"
>
{exportMutation.isPending ? (
<>
@@ -58,8 +58,8 @@ export function ExportToCSVButton(props: {
function convertToCSVFormat(data: { header: string[]; rows: string[][] }) {
return Papa.unparse({
fields: data.header,
data: data.rows,
fields: data.header,
});
}
@@ -1,16 +1,15 @@
/* eslint-disable @next/next/no-img-element */
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { useImageFileOrUrl } from "hooks/useImageFileOrUrl";
import { EyeIcon, FilePlusIcon, ImageIcon, UploadIcon } from "lucide-react";
import Link from "next/link";
import { useCallback } from "react";
import { FilePreview } from "@app/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/_common/file-preview";
import { FilePlusIcon, UploadIcon } from "lucide-react";
import { useCallback, useEffect, useRef } from "react";
import {
type Accept,
type DropEvent,
type FileRejection,
useDropzone,
} from "react-dropzone";
import type { ThirdwebClient } from "thirdweb";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
interface IFileInputProps {
accept?: Accept;
@@ -27,7 +26,7 @@ interface IFileInputProps {
children?: React.ReactNode;
className?: string;
disableHelperText?: boolean;
fileUrl?: string;
client: ThirdwebClient;
}
export const FileInput: React.FC<IFileInputProps> = ({
@@ -37,15 +36,14 @@ export const FileInput: React.FC<IFileInputProps> = ({
showUploadButton,
value,
children,
renderPreview,
helperText,
selectOrUpload = "Select",
isDisabledText = "Upload Disabled",
showPreview = true,
className,
previewMaxWidth,
client,
disableHelperText,
fileUrl: fileUrlFallback,
}) => {
const onDrop = useCallback<
<T extends File>(
@@ -61,15 +59,11 @@ export const FileInput: React.FC<IFileInputProps> = ({
},
[setValue],
);
const { getRootProps, getInputProps, open } = useDropzone({
onDrop,
const { getRootProps, getInputProps } = useDropzone({
accept,
onDrop,
});
const file: File | null =
typeof window !== "undefined" && value instanceof File ? value : null;
const fileUrl = useImageFileOrUrl(value) || fileUrlFallback || "";
const helperTextOrFile = helperText
? helperText
: accept &&
@@ -78,27 +72,16 @@ export const FileInput: React.FC<IFileInputProps> = ({
? "image"
: "file";
// Don't display non image file types
const noDisplay = file && !file.type.includes("image");
const createdBlobUrl = useRef<string | null>(null);
const fileType = file
? file.type.includes("image")
? "Image"
: file.type.includes("audio")
? "Audio"
: file.type.includes("video")
? "Video"
: file.type.includes("csv")
? "CSV"
: file.type.includes("text") || file.type.includes("pdf")
? "Text"
: file.type.includes("model") ||
file.name.endsWith(".glb") ||
file.name.endsWith(".usdz") ||
file.name.endsWith(".obj")
? "3D Model"
: "Media"
: null;
// eslint-disable-next-line no-restricted-syntax
useEffect(() => {
return () => {
if (createdBlobUrl.current) {
URL.revokeObjectURL(createdBlobUrl.current);
}
};
}, []);
if (children) {
return (
@@ -115,15 +98,15 @@ export const FileInput: React.FC<IFileInputProps> = ({
{showPreview &&
(isDisabled ? (
<div
style={{
aspectRatio: 1,
maxWidth: previewMaxWidth,
}}
className={cn(
"cursor-not-allowed bg-card opacity-50",
"flex w-full items-center justify-center border border-border hover:bg-accent hover:ring-2 hover:ring-ring",
className,
)}
style={{
aspectRatio: 1,
maxWidth: previewMaxWidth,
}}
>
<div className="flex flex-col items-center gap-2 text-muted-foreground">
<UploadIcon className="size-6" />
@@ -132,32 +115,24 @@ export const FileInput: React.FC<IFileInputProps> = ({
</div>
) : (
<div
className={cn(
"relative cursor-pointer overflow-hidden",
"flex w-full items-center justify-center border border-border hover:bg-accent hover:ring-2 hover:ring-ring",
value ? "bg-transparent" : "bg-card",
className,
)}
style={{
aspectRatio: 1,
maxWidth: previewMaxWidth,
}}
className={cn(
"relative cursor-pointer overflow-hidden",
"flex w-full items-center justify-center border border-border hover:bg-accent hover:ring-2 hover:ring-ring",
fileUrl ? "bg-transparent" : "bg-card",
className,
)}
>
{noDisplay ? (
<div className="flex flex-col items-center gap-2 text-muted-foreground">
<ImageIcon className="size-6" />
<p className="text-sm">{fileType} uploaded</p>
</div>
) : fileUrl ? (
renderPreview ? (
renderPreview(fileUrl)
) : (
<img
alt=""
className="absolute top-0 left-0 h-full w-full object-contain"
src={fileUrl}
/>
)
{value ? (
<FilePreview
className="h-full w-full"
client={client}
imgContainerClassName="h-full w-full"
srcOrFile={value}
/>
) : (
<div className="flex flex-col items-center gap-2.5 text-muted-foreground">
<div className="flex items-center justify-center rounded-full border border-border bg-background p-3">
@@ -173,34 +148,14 @@ export const FileInput: React.FC<IFileInputProps> = ({
</div>
))}
{showUploadButton || noDisplay ? (
{showUploadButton ? (
<div className="flex flex-col gap-2">
{showUploadButton && (
<Button
onClick={open}
variant="outline"
disabled={isDisabled}
className="gap-2"
>
<Button className="gap-2" disabled={isDisabled} variant="outline">
<FilePlusIcon className="size-4" /> {selectOrUpload}{" "}
{helperTextOrFile}
</Button>
)}
{noDisplay && (
<Button
variant="outline"
className="gap-2"
asChild
onClick={(e) => {
e.stopPropagation();
}}
>
<Link href={fileUrl} target="_blank" className="no-underline">
<EyeIcon className="size-4" />
View File
</Link>
</Button>
)}
</div>
) : null}
</div>
@@ -1,7 +1,7 @@
import { Label } from "@/components/ui/label";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { AsteriskIcon, InfoIcon } from "lucide-react";
import type React from "react";
import { Label } from "@/components/ui/label";
import { ToolTipLabel } from "@/components/ui/tooltip";
export function FormFieldSetup(props: {
htmlFor?: string;
@@ -1,3 +1,5 @@
import type { Meta, StoryObj } from "@storybook/nextjs";
import { useState } from "react";
import type { Team } from "@/api/team";
import { Label } from "@/components/ui/label";
import {
@@ -7,14 +9,12 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import type { Meta, StoryObj } from "@storybook/react";
import { useState } from "react";
import { BadgeContainer } from "../../../../stories/utils";
import { BadgeContainer } from "@/storybook/utils";
import { GatedSwitch } from "./GatedSwitch";
const meta = {
title: "Billing/GatedSwitch",
component: Variants,
title: "Billing/GatedSwitch",
} satisfies Meta<typeof Variants>;
export default meta;
@@ -33,7 +33,6 @@ function Variants() {
const plans: Team["billingPlan"][] = [
"free",
"starter_legacy",
"starter",
"growth_legacy",
"growth",
@@ -47,10 +46,10 @@ function Variants() {
<div className="flex items-center gap-2">
<Label>Required Plan</Label>
<Select
value={requiredPlan}
onValueChange={(value) =>
setRequiredPlan(value as typeof requiredPlan)
}
value={requiredPlan}
>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Select plan" />
@@ -67,8 +66,8 @@ function Variants() {
{plans.map((currentPlan) => (
<BadgeContainer key={currentPlan} label={`plan: ${currentPlan}`}>
<GatedSwitch
key={currentPlan}
currentPlan={currentPlan}
key={currentPlan}
requiredPlan={requiredPlan}
teamSlug="foo"
/>

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