Compare commits

...
Author SHA1 Message Date
Jonas DanielsGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
d66568467d Version Packages (#5465)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-21 10:05:59 -08:00
arcoraven 6bd968362a chore: Allow optional includeUsage (default: true) (#5477)
<!-- start pr-codex -->

## PR-Codex overview
This PR introduces an optional `includeUsage` parameter to the `CoreServiceConfig` type and updates API URL construction to utilize this parameter, defaulting to `true`. This allows for more flexible API calls regarding usage data.

### Detailed summary
- Added optional `includeUsage` parameter to `CoreServiceConfig`.
- Updated `fetchKeyMetadataFromApi` to use `includeUsage` in the API URL.
- Updated `fetchAccountFromApi` to use `includeUsage` in the API URL for both wallet and account endpoints.

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

<!-- end pr-codex -->
2024-11-21 04:50:27 +00:00
Joaquim VergesandGitHub 17488b8139 [Documentation] Refactor: React guides (#5375) 2024-11-21 15:37:20 +13:00
MananTank 776f5aaeda [Dashboard] User Onboarding Revamp (#5471)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on improving the onboarding process and account management in the application. It introduces better handling of team retrieval, login redirection, and email confirmation, while also refining the UI components related to these features.

### Detailed summary
- Added optional chaining for `firstTeam` retrieval.
- Introduced `loginRedirect` function for consistent login handling.
- Created `getValidAccount` to enforce login and onboarding checks.
- Improved `Onboarding` components for better user experience.
- Enhanced email confirmation flow with clearer messaging.
- Updated various components to use new account and team management functions.

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

<!-- end pr-codex -->
2024-11-21 01:18:01 +00:00
DustinandGitHub ecaa304a81 Update contact-us form (#5474) 2024-11-20 15:37:31 -08:00
Jonas DanielsandGitHub 377f46c303 cleanup unused (#5476) 2024-11-20 15:22:11 -08:00
MananTank b52049c20a [Dashboard] Improve team overview page load perf + UI adjustments (#5475)
Move the chart data fetching inside suspense boundary with loading fallback to speed up page load

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on improving the layout and structure of several components in the dashboard, enhancing the user interface by adjusting styles and adding new features like loading states. It also refines the `TeamOverviewPage` to better handle data and display.

### Detailed summary
- Updated styles in `range-selector.tsx` for improved layout.
- Added `w-full` class in `EmptyState.tsx` for full-width section.
- Adjusted gap sizes in `AnalyticsHeader.tsx`.
- Modified `TeamOverviewPage` to include a loading state using `Suspense`.
- Introduced `OverviewPageContent` function to manage content rendering.
- Enhanced conditionals to simplify rendering logic in `TeamOverviewPage`.

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

<!-- end pr-codex -->
2024-11-20 22:29:19 +00:00
MananTank a67e980ac3 [Dashboard] Feature: Add account settings page with Name and Email fields (#5461)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the account settings functionality, including email confirmation and account updates, while improving error handling and user interactions.

### Detailed summary
- Added a new route for `/account/settings`.
- Improved onboarding logic for email confirmation.
- Updated waitlist joining logic to handle responses better.
- Enhanced `joinTeamWaitlist` and `updateAccount` actions with better error handling.
- Implemented email confirmation with OTP in `confirmEmailWithOTP`.
- Improved `AccountSettingsPage` to include email update functionalities.
- Added UI components for email verification and account updates.
- Refined error handling in various components for better user feedback.

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

<!-- end pr-codex -->
2024-11-20 21:00:48 +00:00
AmineAfia 4c8333d4a3 [Dashboard] Feature: Add initial insight UI (#5435)
## Problem solved
BLOCK-436

This PR adds an initial UI for insight to give visibility to insight in the platform. The first three blueprints are Data APIs available for all users. The next iteration will add the ability to create a new blueprint.

![CleanShot 2024-11-15 at [email protected]](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/dGj3ureqJk84bcvq1s10/8d0fb6d9-f80b-414a-925d-e6ed68aa6bfb.png)

---

## PR-Codex overview
This PR introduces a new `Insight` page under the project structure, adding components for displaying blueprints, including a header and an explorer. It also implements authentication redirection and a loading state for better user experience.

### Detailed summary
- Added a new route for `Insight` in `layout.tsx`.
- Created `BlueprintsPageHeader` component for the `Insight` page.
- Implemented `Page` component with authentication check and redirection logic.
- Developed `BlueprintsPage` component to display blueprints with a loading state.
- Created `getProjectBlueprints` function to fetch blueprint data.
- Added `BlueprintsExplorer` component to list blueprints with a fallback message.
- Implemented `BlueprintCard` for individual blueprint display.
- Included loading spinner in `Loading` component.

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

<!-- end pr-codex -->

Linear Ticket: https://linear.app/thirdweb/issue/BLOCK-436/implement-initial-insight-ui

<!-- start pr-codex -->

---

## PR-Codex overview
This PR adds a new `Insight` page to the dashboard for a project, featuring a `BlueprintsPage` that displays blueprints and a header. It includes authentication checks and a redirect to the login page if the user is not authenticated.

### Detailed summary
- Added a new route for `Insight` in `layout.tsx`.
- Created `Page` component in `page.tsx` with authentication check.
- Added `BlueprintsPageHeader` component for the `Insight` page.
- Implemented `BlueprintsPage` to display billing alerts and blueprints.
- Created `getProjectBlueprints` function to fetch blueprint data.
- Developed `BlueprintsExplorer` component to list blueprints.
- Added `BlueprintCard` component to display individual blueprints.

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

<!-- end pr-codex -->

Linear Ticket: https://linear.app/thirdweb/issue/BLOCK-436/implement-initial-insight-ui

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new `Insight` feature to the dashboard, which includes a dedicated page for insights with blueprints and a header component. It also implements authentication checks and displays billing alerts.

### Detailed summary
- Added a new route for `Insight`.
- Created `Page` component with authentication check and redirection.
- Introduced `BlueprintsPage` component displaying billing alerts and blueprints.
- Implemented `BlueprintsPageHeader` with a disabled button.
- Added `BlueprintsExplorer` for listing blueprints.
- Defined `BlueprintCard` for individual blueprint display.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`
2024-11-20 14:34:39 +01:00
e95ddcdd3e [Dashboard] Feature: Integrate billing v2 init (#5432)
Co-authored-by: Manan Tank <[email protected]>
2024-11-20 00:14:37 -08:00
kien-ngo 7973d6450c [React] Feature: Headless UI Token components (#5433)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces new components related to token management in a React application, including `TokenProvider`, `TokenName`, `TokenSymbol`, and `TokenIcon`. These components allow users to fetch and display token-related data such as name, symbol, and icon.

### Detailed summary
- Added `Token` section in the sidebar with links to token components.
- Created `TokenProvider` for managing token data context.
- Implemented `TokenName`, `TokenSymbol`, and `TokenIcon` components for fetching and displaying token information.
- Exported new token-related types and components from `react.ts`.

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

<!-- end pr-codex -->
2024-11-20 06:40:01 +00:00
kien-ngo 017b13f444 [SDK] Feature: ConnectedWalletDetails to use headless UI (#5429)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on refactoring the wallet connection components in the `thirdweb` package, specifically enhancing the `ConnectButton` and `ConnectedWalletDetails` components by integrating `AccountProvider` and improving the handling of wallet details.

### Detailed summary
- Updated JSDoc comment in `useConnectedWalletDetails` to indicate its limited usage.
- Wrapped `ConnectedWalletDetails` with `AccountProvider` in `ConnectButtonInner`.
- Removed `useConnectedWalletDetails` from `ConnectedWalletDetails`.
- Enhanced `ConnectedWalletDetails` to use `AccountAvatar`, `AccountName`, and `AccountBalance` components.
- Updated props handling for connected account avatar and name in `ConnectedWalletDetails`.
- Refactored balance display logic in `ConnectedWalletDetails` to use `AccountBalance`.
- Modified `DetailsModal` to utilize `AccountProvider` and improved wallet avatar handling.

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

<!-- end pr-codex -->
2024-11-20 05:37:00 +00:00
kien-ngo 901c3a102b [SDK] Feature: Account components (#5388)
cnct-2137

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new `Account` component suite to the `thirdweb` library, enhancing user account management with features like account name, address, avatar, and balance display. It also marks existing `NFT` components as `@beta`.

### Detailed summary
- Added `Account` components: `AccountName`, `AccountAddress`, `AccountAvatar`, `AccountBalance`, and `AccountBlobbie`.
- Updated existing `NFT` components to `@beta`.
- Introduced tests for new `Account` components.
- Enhanced `AccountProvider` for context management.
- Updated documentation for new components and props.

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

<!-- end pr-codex -->
2024-11-20 05:37:00 +00:00
saminacodes 3253a532f8 added 7 new guides for contracts (#5467)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on restructuring and enhancing the documentation for various modular contract types in the `pre-built-modular` section, including updates to navigation, descriptions, and deployment steps.

### Detailed summary
- Renamed `Pre-built Contracts` to `Pre-built Modular` in the sidebar.
- Added detailed documentation for:
  - `NFT Drop`
  - `Token`
  - `Token Drop`
  - `Edition Drop`
  - `Open Edition`
  - `Edition`
  - `NFT Collection`
- Included images and steps for deploying each contract type.
- Updated links and metadata for better navigation and user experience.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`
https://linear.app/thirdweb/issue/DEVX-1828/7-guides-for-contracts
<!-- end pr-codex -->
2024-11-20 01:36:57 +00:00
joaquim-verges 0e5d120909 [Documentation] Refactor: Update tsdoc references (#5374)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on refactoring the `NFT` component and its related functionalities to improve performance, enhance control over fetching logic, and simplify usage by removing the need for `React.Suspense`. It also introduces new props for better loading and error handling.

### Detailed summary
- Deleted several test files and an unused `NFT.tsx` file.
- Introduced `NFTProvider`, `NFTMedia`, `NFTName`, and `NFTDescription` components.
- Added `loadingComponent` and `fallbackComponent` props for better handling of loading states and errors.
- Updated references in tests to use new component structure.
- Renamed and adjusted documentation comments to reflect new functionality.
- Improved sidebar organization by updating links and names for consistency.
- Enhanced utility functions and hooks for better NFT data fetching and context management.

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

<!-- end pr-codex -->
2024-11-20 00:11:28 +00:00
gregfromstl f368793375 [SDK] Fix: PayEmbed Error State (#5463)
CNCT-2385

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on improving error handling and user experience in the `BuyScreen` and `TransactionModeScreen` components of the `thirdweb` package. It adds error states for loading issues and modifies error messages for better clarity.

### Detailed summary
- Updated error message for missing account in `useBuyTxStates.ts`.
- Introduced an error state in `BuyScreen` for `supportedDestinationsQuery` failures.
- Added loading and error handling in `TransactionModeScreen` for transaction cost and chain data.
- Enhanced user feedback with `ErrorState` and `LoadingScreen` components.

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

<!-- end pr-codex -->
2024-11-19 23:15:21 +00:00
gregfromstl b5227c9a8f [SDK] Fix: Adds overrides to createNewPack (#5464)
CNCT-2230

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces the ability to override transaction values in the `createNewPack` function within the `thirdweb` package.

### Detailed summary
- Added `WithOverrides` type to the `options` parameter in `createNewPack` and `getCreatePackParams` functions.
- Updated the function signatures to accept overrides for transaction options.
- Included `overrides` in the parameters for creating pack details.

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

<!-- end pr-codex -->
2024-11-19 23:02:02 +00:00
ElasticBottle 0998fc737b [Portal] Docs: Winston/cnct 2314 consolidate documentation for in app wallets and ecosystem (#5443)
https://linear.app/thirdweb/issue/CNCT-2314/consolidate-documentation-for-in-app-wallets-and-ecosystem

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on restructuring and updating the documentation and components related to `thirdweb` wallets, including `In-App`, `Ecosystem`, and `External Wallets`. It includes deletions of outdated files and additions of new guides and features.

### Detailed summary
- Deleted multiple outdated `.mdx` files related to `In-App Wallet` and `Ecosystem` documentation.
- Added new images and updated asset paths for wallet-related components.
- Introduced a new `AuthList` component to display various authentication methods.
- Updated wallet security documentation to reflect changes in architecture.
- Enhanced the sidebar for better navigation of wallet-related topics.
- Revised various guides to improve clarity on wallet integration and user management.
- Added detailed sections on wallet pregeneration and linking multiple identities.
- Updated the `sign-in methods` documentation with clearer instructions and links.

> The following files were skipped due to too many changes: `apps/portal/src/app/connect/sidebar.tsx`, `apps/portal/src/app/connect/wallet/faq/page.mdx`, `apps/portal/src/app/connect/wallet/user-management/get-user-profiles/page.mdx`, `apps/portal/redirects.mjs`, `apps/portal/src/app/connect/wallet/security/assets/ecosystem-scoped.svg`

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

<!-- end pr-codex -->
2024-11-19 22:30:55 +00:00
3df0541582 Update webauthn to v2 (#5458)
Co-authored-by: Joaquim Verges <[email protected]>
2024-11-20 11:29:40 +13:00
Jonas DanielsGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
2591c75b56 Version Packages (#5410)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-19 10:53:46 -08:00
GWSzeto 55edd4cb8e [Dashboard] Feature: Updated Mintable Modules (#5404)
https://linear.app/thirdweb/project/[modular-contracts]-modules-ui-c7a2be28299d/overview
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on enhancing the `Mintable` functionality in the dashboard and improving the handling of URIs in the `MintableERC721` and `MintableERC1155` modules. It also refines role management for minting processes.

### Detailed summary
- Changed the condition in `module-instance.tsx` to check for "Mintable" instead of "Mintable-ignore".
- Updated `mintWithRole.ts` in both `MintableERC721` and `MintableERC1155` to conditionally process `nfts` and set `baseURI`.
- Simplified import statements in `Mintable.tsx`.
- Modified `hasMinterRole` and `grantRoleTx` to use a more concise syntax for role checking and granting in `Mintable.tsx`.

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

<!-- end pr-codex -->
2024-11-19 18:31:34 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
e0eea33d06 chore(deps-dev): bump cross-spawn from 7.0.5 to 7.0.6 in the npm_and_yarn group (#5455)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-18 14:50:55 -08:00
MananTank d7e36a8afd Add turbo defaults to inputs in turbo config (#5454)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the `inputs` in various tasks defined in `turbo.json` to include a new variable `$TURBO_DEFAULT$`, enhancing the configuration for better management of dependencies.

### Detailed summary
- Updated `inputs` for `thirdweb#update-version` to include `$TURBO_DEFAULT$`.
- Modified `inputs` for `test`, `storybook`, `test:legacy`, `e2e`, and `e2e:legacy` to include `$TURBO_DEFAULT$`.

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

<!-- end pr-codex -->
2024-11-18 22:17:57 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
bcbfb2065f Bump ox from 0.1.3 to 0.2.1 (#5449)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-19 10:46:14 +13:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
261102b798 Bump react-hook-form from 7.52.0 to 7.53.2 (#5451)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-19 10:45:50 +13:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
42910372fc Bump codecov/codecov-action from 4 to 5 (#5453)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-19 10:45:22 +13:00
1554e377eb [Portal] Fix: link to ThirdwebExtensions full reference (#5446)
Signed-off-by: Alec Ananian <[email protected]>
Signed-off-by: greg <[email protected]>
Co-authored-by: greg <[email protected]>
2024-11-19 10:40:47 +13:00
kien-ngo 510be22cb6 [SDK] Fix: Wallet avatar styles in ConnectButton and Modal (#5422)
i just want my cat back

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the styling of the `img` elements in the `DetailsModal` component to ensure they maintain aspect ratio and are responsive.

### Detailed summary
- Changed the `width` of the first `img` to `"100%"` and added `objectFit: "cover"`.
- Updated the second `img` to have `height` and `width` set to `"100%"`, along with `objectFit: "cover"`.

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

<!-- end pr-codex -->
2024-11-18 20:03:03 +00:00
gregfromstl d4430d4b2f [SDK] Feature: Export prepareMethod (#5439)
CNCT-2381

<!-- start pr-codex -->

---

## PR-Codex overview
This PR adds a new export to the `contract.ts` file, enhancing the available utilities for contract interactions.

### Detailed summary
- Added export for `prepareMethod` from `../utils/abi/prepare-method.js`.

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

<!-- end pr-codex -->
2024-11-18 19:40:23 +00:00
MananTank ed58c4c930 [SDK] Fix useSetActiveWallet code example in JSDoc (#5444)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on renaming a variable for clarity in the `useSetActiveWallet` hook implementation within the `useSetActiveWallet.ts` file.

### Detailed summary
- Renamed the variable `setActiveAccount` to `setActiveWallet` for consistency with the hook name.
- Updated the usage of the variable to `await setActiveWallet(wallet);` to reflect the new name.

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

<!-- end pr-codex -->
2024-11-18 19:22:24 +00:00
gregfromstl 8e22cf8812 [Ecosystem Portal] Fix: WalletConnect Connection (#5438)
CNCT-2258

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the `wallet-ui` application, introducing multi-tenancy support, updating configurations, and improving code quality with better imports and utility functions.

### Detailed summary
- Removed unused files: `erc20.ts`, `Erc20Token.ts`
- Added `isMultiTenant` utility for environment checks
- Updated `README.md` for ecosystem portal
- Enhanced routing with ecosystem ID in redirects
- Introduced new configuration files for `knip` and `biome`
- Updated ESLint rules and configurations
- Refactored image handling to use a new `Image` component
- Streamlined various imports across components and hooks

> The following files were skipped due to too many changes: `pnpm-lock.yaml`

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

<!-- end pr-codex -->
2024-11-18 19:16:57 +00:00
0xFirekeeper 565dbb9ef3 [Docs] .NET & Unity as of 2.9/5.10 (#5445)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on updating the documentation for the `Thirdweb .NET SDK`, adding new methods, modifying existing ones, and improving sidebar navigation. It also removes outdated pages and refines descriptions for better clarity.

### Detailed summary
- Deleted `getemail` and `getphonenumber` action pages.
- Updated the reference link for `ThirdwebExtensions`.
- Added documentation for `EcosystemWallet.GetEcosystemDetails` and `EcosystemWallet.GetUserDetails`.
- Modified sidebar links, replacing `GetEmail` and `GetPhoneNumber` with `GetUserDetails` and `GenerateExternalLoginLink`.
- Added documentation for `EcosystemWallet.GenerateExternalLoginLink`.
- Updated `IThirdwebWallet.Transfer` method description and usage examples.
- Revised wallet connection methods in `ThirdwebManager`, updating method names and descriptions.
- Improved `Child Prefabs` section, renaming and clarifying modal usage.

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

<!-- end pr-codex -->
2024-11-18 19:10:52 +00:00
Joaquim VergesandGitHub f01de732fb feat: Support ERC-6492 signature verification on zksync chains (#5440) 2024-11-18 21:44:23 +13:00
Jonas DanielsandGitHub a00233d1ba Chore: Update dependencies (#5430) 2024-11-15 15:16:27 -08:00
GiselleNessi acc7303c05 [Portal] Docs: Update Pay Docs (#5425)
Updated Pay docs to clarify KYC in FAQs

## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR updates the content in the `page.mdx` file to clarify the KYC requirements for using the 'Buy with Fiat' feature in the `Pay` application.

### Detailed summary
- Revised explanation of KYC requirements for `Buy with Fiat`.
- Changed text to specify that KYC is required only for the first purchase, with no further requirements for subsequent purchases on the same platform.

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

<!-- end pr-codex -->
2024-11-15 23:09:21 +00:00
kien-ngo 304f2e6b8e [Portal] Feature: Contract deploy button (#5434)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces the `ContractDeployCard` component to the application, allowing users to deploy contracts with a specified name and link. It enhances the user interface by providing a structured card layout for contract deployment.

### Detailed summary
- Added `ContractDeployCard` component.
- Accepts `contractName`, optional `description`, and `href` props.
- Displays a title, description, and a "Deploy Now" link.
- Utilizes `Link` from `next/link` for navigation.
- Includes usage documentation in the code comments.

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

<!-- end pr-codex -->
2024-11-15 23:05:56 +00:00
MananTank 5585defbe4 Fix position:relative not supported on <tr> in webkit (safari) (#5431)
We use position:relative on tr to contain the absolutely position anchor link inside - to make the whole row clickable, but that does not work on webkit ( safari )

Relevant article on the topic:
https://mtsknn.fi/blog/relative-tr-in-safari/
2024-11-15 08:21:01 +00:00
gregfromstl 10ca0bcd47 [SDK] Fix: Limit WC chains connected to 10 for max payload size (#5427)
CNCT-2231

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a validation check for the `chains` option in the WalletConnect integration, ensuring that no more than 10 chains can be specified. If more than 10 chains are provided, a warning is logged, and only the first 10 chains are used.

### Detailed summary
- Added a check for `options.chains` to limit the number of chains to a maximum of 10.
- Implemented a warning message if more than 10 chains are specified, informing the user of the truncation.
- Used `slice(0, 10)` to return only the first 10 chains if the limit is exceeded.

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

<!-- end pr-codex -->
2024-11-15 01:01:30 +00:00
gregfromstl 7b21f1b632 [SDK] Feature: Steam Auth (#5415)
CNCT-2198

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces `Steam` as a new authentication option across various components and files, enhancing the authentication capabilities of the application.

### Detailed summary
- Added `Steam` as an authentication option in multiple files.
- Updated `types.ts` and `oauthSignIn.ts` to include `Steam`.
- Added `STEAM_ICON` in `svgs.ts` for UI representation.
- Updated connection options in `ConnectWalletMiniPlayground.tsx`.
- Modified documentation to include `Steam` in social logins.

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

<!-- end pr-codex -->
2024-11-15 00:43:57 +00:00
gregfromstl f7a4a466d5 [SDK] Fix: Connect to correct chain with guest account (#5426)
CNCT-2370

<!-- start pr-codex -->

---

## PR-Codex overview
This PR adds support for the `chain` property in the `connectOptions` object within the `ConnectWalletSocialOptions.tsx` file, enhancing the flexibility of wallet connections based on the provided chain.

### Detailed summary
- Added `chain: props.chain` to the `connectOptions` object.

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

<!-- end pr-codex -->
2024-11-15 00:35:02 +00:00
MananTank 895b4d145f Catch localStorage getItem and setItem unhandled errors (#5397)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing error handling for `localStorage` operations across various components, ensuring that unhandled errors do not disrupt functionality.

### Detailed summary
- Wrapped `localStorage.setItem` and `localStorage.getItem` calls in `try-catch` blocks to handle potential errors.
- Updated `ThemeSwitcher`, `RightSection`, `useLocalStorage`, `Banner`, and `webLocalStorage` to ignore errors gracefully.
- Maintained existing functionality while improving robustness.

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

<!-- end pr-codex -->
2024-11-15 04:08:18 +05:30
ElasticBottle e958e75df5 feat: add custom auth demo in playground (#5418)
https://linear.app/thirdweb/issue/CNCT-2364/custom-auth-demo-in-playground

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new `CustomLoginForm` component for handling user authentication via email in the `in-app-wallet` feature. It allows users to log in with a custom authentication endpoint while integrating with existing wallet functionalities.

### Detailed summary
- Added `CustomLoginForm` component in `custom-login-form.tsx`.
- Utilized `useMutation` for handling login requests with an email.
- Implemented email input and submission handling in the `CustomLoginForm`.
- Updated `page.tsx` to include `CustomLoginForm` with a new section for custom authentication UI.

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

<!-- end pr-codex -->
2024-11-14 20:50:09 +00:00
MananTank b620deecfe Show last 50 changelogs in portal instead of last 100 (#5423)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on modifying the parameters for fetching changelog entries in the `ghost.ts` file, specifically adjusting the limit of results returned.

### Detailed summary
- Changed the `-limit` parameter from `100` to `50` to reduce the number of results fetched.

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

<!-- end pr-codex -->
2024-11-14 19:45:23 +00:00
GWSzeto 5218313b19 fix: transferable module restriction (#5420)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on modifying the `isRestricted` prop in the `Transferable` component to invert its boolean value based on the `isTransferEnabledQuery.data`.

### Detailed summary
- Changed the `isRestricted` prop from `isRestricted={!!isTransferEnabledQuery.data}` to `isRestricted={!isTransferEnabledQuery.data}`.
- Other props remain unchanged, including `isPending`, `adminAddress`, `update`, and `isOwnerAccount`.

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

<!-- end pr-codex -->
2024-11-14 18:31:22 +00:00
MananTank 731089b29d Fix claim condition form loading state (#5421)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR updates the conditional rendering logic in the `claim-conditions-form` component to use `claimConditionsQuery.isPending` instead of `isFetchingData`, improving clarity on the loading state of the data.

### Detailed summary
- Changed the condition from `if (isFetchingData)` to `if (claimConditionsQuery.isPending)` in `index.tsx`.

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

<!-- end pr-codex -->
2024-11-14 18:20:28 +00:00
GWSzeto 92e55c8b3d [Dashboard] Feature: Updated Claimable (#5413)
- tokenId and setToken introduced at the parent level ClaimableModule
- Claim Conditions are fetched at the parent level

https://linear.app/thirdweb/issue/DES-251/claimable-erc721-erc1155-erc20

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the `ClaimableModule` functionality by refining claim conditions, improving token ID handling, and updating UI components for better user experience.

### Detailed summary
- Updated claim condition checks for `ClaimableModule`.
- Added `tokenId` state management in `ClaimableModule`.
- Modified claim condition logic to handle ERC721 and ERC1155 tokens.
- Enhanced UI with a new checkbox for "No Claim Condition Set".
- Improved currency selection logic in `CurrencySelector`.
- Refined `pricePerUnit` handling in `claimable.stories.tsx`.

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

<!-- end pr-codex -->
2024-11-14 18:08:54 +00:00
MananTank 5de5c44985 Only show Nebula tab if already on waitlist, automatically join waitlist on Nebula page visit (#5419)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new `ShareButton` component and refactors the handling of the Nebula waitlist across various pages, enhancing user experience with waitlist functionalities and error handling.

### Detailed summary
- Added `ShareButton` component for sharing waitlist links.
- Refactored `TeamLayout` to conditionally render the `Nebula` path based on waitlist status.
- Updated `NebulaWaitListPage` to handle team fetching and waitlist joining.
- Replaced `JoinNebulaWaitlistPage` with `NebulaWaitListPage`.
- Simplified storybook stories for waitlist pages.
- Improved error handling for waitlist status fetching.

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

<!-- end pr-codex -->
2024-11-14 16:47:46 +00:00
gregfromstl 3488281958 [CI] Chore: Update PR stale time to 7 days (#5417)
CNCT-2336

<!-- start pr-codex -->

---

## PR-Codex overview
This PR updates the configuration for stale issues and pull requests in the `.github/workflows/stale.yml` file, reducing the inactivity duration before marking them as stale and the time before they are closed.

### Detailed summary
- Changed `days-before-stale` from 30 to 7.
- Changed `days-before-close` from 5 to 2.

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

<!-- end pr-codex -->
2024-11-14 05:56:10 +00:00
ianandGitHub c93755e6f6 [Documentation] Feature: Add page for supported chains on insight (#5412) 2024-11-13 18:05:38 -08:00
gregfromstl 55a00c441e [Documentation] Fix: Rename framworks.svg to frameworks.svg (#5403) (#5416)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on modifications to the SVG file located at `apps/portal/public/icons/feature-cards/framworks.svg`, likely to update the design or improve the icon representation.

### Detailed summary
- Updated the SVG file `framworks.svg` to reflect design changes.
- Adjusted the dimensions and paths within the SVG for better rendering.
- Enhanced visual elements to improve clarity and aesthetics.

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

<!-- end pr-codex -->
2024-11-14 01:01:38 +00:00
dirtycajunrice 761417cc1a [Documentation] Feature: Unreal Docs (#5363)
## Problem solved
Updated Docs for Unreal Engine

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily updates the `@dirtycajunrice/klee` dependency, enhances the sidebar structure for Unreal Engine blueprints, and adds new functionalities for building hrefs and contract interactions in the application.

### Detailed summary
- Updated `@dirtycajunrice/klee` from `^1.0.1` to `^1.0.5`.
- Introduced `hrefBuilder` function in `util.ts` to construct href links.
- Added `blueprints` and `cpp` link groups in the sidebar.
- Updated various blueprint pages with new content and structure.
- Enhanced contract interaction nodes for reading and writing contracts.

> The following files were skipped due to too many changes: `apps/portal/src/app/unreal-engine/blueprints/engine/nodes.ts`, `apps/portal/src/app/unreal-engine/blueprints/in-app-wallet/nodes.ts`, `apps/portal/src/app/unreal-engine/blueprints/smart-wallet/nodes.ts`

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

<!-- end pr-codex -->
2024-11-14 00:52:12 +00:00
MananTank f331f01225 Add /nebula framer rewrite in dashboard (#5414)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR updates the routing paths in the `apps/dashboard/framer-rewrites.js` file by adding a new route and modifying existing ones.

### Detailed summary
- Added the route `"/nebula"`.
- Changed the `"/connect/sign-in"` route to `"/connect/sign-in"` (no change).
- Modified the `"/contracts/modular-contracts"` route.
- Modified the `"/contracts/explore"` route.

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

<!-- end pr-codex -->
2024-11-13 22:43:22 +00:00
joaquim-verges 7bbe60f2be [Dashboard] Fix: Only use paymaster for sophon testnet chain (#5392)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the handling of transactions for the `sophon` testnet, specifically for zk chains. It introduces a special case for transactions that ensures the use of an EIP-712 transaction format along with a paymaster.

### Detailed summary
- Removed the general check for zk chains on testnets.
- Added a specific condition for the `sophon` testnet (chain ID `531050104`).
- Ensured that transactions on the `sophon` testnet always use EIP-712 format and include paymaster data.
- Simplified the transaction handling by removing unnecessary error handling logic.

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

<!-- end pr-codex -->
2024-11-13 20:29:31 +00:00
gregfromstl 9f9b67da5f [Dashboard] Feature: Adds github as a login option (#5411)
CNCT-2357

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces wallet integration into the login functionality of the application, allowing users to connect using various wallet options.

### Detailed summary
- Added wallet integration by importing `createWallet` and `inAppWallet` from `thirdweb/wallets`.
- Defined a `wallets` array containing multiple wallet options including `inAppWallet` and various `createWallet` instances.
- Updated the `CustomConnectEmbed` component to utilize the new `wallets` array for authentication.

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

<!-- end pr-codex -->
2024-11-13 20:14:24 +00:00
MananTank 24a53e3b82 [Dashboard] Refactor: Migrate usage/storage page to shadcn+tailwind (#5408)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the `storage` section of the dashboard by introducing new components for guides, SDK integration, and file management, along with refactoring existing code for improved structure and readability.

### Detailed summary
- Deleted `TWQueryTable.tsx` and `RelevantDataSection.tsx`.
- Added `GuideSection` and `SDKSection` components for documentation and SDK integration.
- Introduced a new `GuidesSection` function to display links and videos.
- Refactored `YourFilesSection` to use a table format and improved pagination.
- Replaced `ChakraProviderSetup` with a simpler layout in the main `Page` component.

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

<!-- end pr-codex -->
2024-11-13 19:21:51 +00:00
MananTank 9ea155e4f4 [Dashboard] Refactor: Migrate CodeSegment to shadcn/tailwind + Improved UI (#5407)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on refactoring the code related to `CodeSegment` and `CodeEnvironment`, moving them to a new location and updating their usage across various components. It also introduces a new `CodeSegment` component with improved functionality.

### Detailed summary
- Deleted `types.ts` and `CodeSegment.tsx` from `contract-tabs`.
- Introduced `CodeSegment` and `CodeEnvironment` in `code-segment.client.tsx`.
- Updated imports in multiple files to use the new `CodeSegment` and `CodeEnvironment`.
- Enhanced `CodeSegment` to manage code snippets for different environments.
- Added stories for `CodeSegment` in `code-segment.stories.tsx`.

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

<!-- end pr-codex -->
2024-11-13 19:21:51 +00:00
MananTank fae444da66 [Dashboard] Feature: Add Nebula Waitlist page (#5406)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new feature for managing a "Nebula" waitlist within a team dashboard. It adds new routes, components, and API functions to facilitate joining the waitlist and fetching its status.

### Detailed summary
- Added a new route for `Nebula` under the team and project slugs.
- Implemented `JoinNebulaWaitlistPage` component for handling waitlist interactions.
- Created `joinTeamWaitlist` API function for server-side waitlist management.
- Added `getTeamNebulaWaitList` API function to fetch waitlist status.
- Updated existing pages to integrate the new waitlist functionality.
- Enhanced UI components for displaying waitlist status and actions.

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

<!-- end pr-codex -->
2024-11-13 19:11:05 +00:00
jnsdls b30c4d0484 [Dashboard] Fix: Handle null publisher profile (#5372) 2024-11-13 18:42:44 +00:00
iuwqyir 6b853df3d2 adds documentation to insight and adds it to chainslist (#5376)
## Problem solved

[BLOCK-325
](https://linear.app/thirdweb/issue/BLOCK-325/add-insight-to-thirdweb-chainslist)

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces the `Insight` feature into the application, enhancing blockchain data querying capabilities. It includes new components, routes, and metadata to support the functionality, along with the addition of icons and documentation for better user guidance.

### Detailed summary
- Added `Insight` link in `Header.tsx`.
- Updated `chain.ts` and `metadata.ts` to include `Insight`.
- Implemented `InsightSection` in `page.tsx`.
- Created `sidebar` for `Insight` in `sidebar.tsx`.
- Developed `Layout` for `Insight` in `layout.tsx`.
- Added `InfraInsightIcon` in `index.ts`.
- Created `InsightIcon` in `icons` directory.
- Introduced `Insight` pages in `page.mdx`, `blueprints/page.mdx`, `get-started/page.mdx`, and `use-cases/page.mdx`.
- Added SVG for `Insight` icon in `public/og/icons`.
- Enhanced documentation for `Insight` with use cases and blueprints.

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

<!-- end pr-codex -->
2024-11-13 18:02:11 +00:00
Toomas OosaluandGitHub e841bfe081 remove nebula from service list (#5409) 2024-11-13 09:21:20 -08:00
Jonas DanielsGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
e0c641c9cd Version Packages (#5352)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-14 05:57:00 +13:00
a1fc436a92 [SDK] Feature: Adds EIP-1193 Adapter (#5354)
Co-authored-by: gregfromstl <[email protected]>
2024-11-14 04:36:11 +13:00
kien-ngo b30177c518 [Dashboard] Remove tw-components <Badge /> (#5370)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on refactoring the usage of the `Badge` component across various files, simplifying its props and styling. It also removes the `Badge` component from some files and updates imports accordingly.

### Detailed summary
- Deleted `badge.tsx` file.
- Removed `Badge` imports and usage in several components, replacing them with simpler implementations.
- Updated `Badge` usage to use class names for styling instead of props in multiple components.
- Adjusted the `Badge` component's props to reduce complexity and improve readability.

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

<!-- end pr-codex -->
2024-11-13 02:15:42 +00:00
iuwqyir 040e478ad6 add nebula service definition (#5401)
## Problem solved

https://linear.app/thirdweb/issue/BLOCK-412/add-api-key-scope

Added Nebula service scope to the service definitions and included it in the hidden services list.

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces the `nebula` service scope to the project and updates the related configurations and validations accordingly. It also modifies the pull request body check to include the `BLOCK` keyword.

### Detailed summary
- Added `nebula` service scope in `packages/service-utils/src/core/services.ts` with properties: `name`, `title`, and `description`.
- Updated `HIDDEN_SERVICES` in `apps/dashboard/src/components/settings/ApiKeys/validations.ts` to include `nebula`.
- Modified the pull request body check in `.github/workflows/linear.yml` to include `BLOCK`.

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

<!-- end pr-codex -->
2024-11-12 22:51:51 +00:00
ElasticBottle 6771cfed62 Winston/cnct 2311 pro cannot sign transaction with in app wallet (#5390)
## Problem solved

https://linear.app/thirdweb/issue/CNCT-2311/pro-cannot-sign-transaction-with-in-app-wallet

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on improving the transaction signing process in the `enclave-wallet` by ensuring proper type checks for various transaction properties, specifically handling cases for `bigint` and `number` types.

### Detailed summary
- Updated the handling of `tx.value`, `tx.gas`, `tx.nonce`, `tx.maxPriorityFeePerGas`, and `tx.gasPrice` to check their types before processing.
- Ensured that `tx.value`, `tx.gas`, and `tx.maxPriorityFeePerGas` are only processed if they are of type `bigint`.
- Adjusted the handling of `tx.nonce` to support both `number` and fallback to `eth_getTransactionCount`.
- Maintained a 10% buffer for `tx.gas` calculations.
- Updated the assignment of `transaction.maxFeePerGas` and `transaction.type` based on the presence of `tx.maxFeePerGas`.

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

<!-- end pr-codex -->
2024-11-12 22:15:04 +00:00
MananTank ff188fd49d Replace prism+chakra Code component with shiki+tailwind (#5393)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on refactoring code related to code blocks in the UI by replacing the `CodeBlock` component with a new `CodeClient` component and introducing a `PlainTextCodeBlock` for plain text display. It also updates styles and dependencies.

### Detailed summary
- Removed `CodeBlock` component from various files.
- Introduced `CodeClient` component for enhanced code rendering.
- Added `PlainTextCodeBlock` for displaying plain text.
- Updated styles in `globals.css` for dark mode support.
- Added new utility functions for code formatting.
- Updated dependencies in `package.json` and `pnpm-lock.yaml`.

> The following files were skipped due to too many changes: `pnpm-lock.yaml`

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

<!-- end pr-codex -->
2024-11-12 21:58:05 +00:00
gregfromstl 512eeb5cf0 fix: unstoppable domains test (#5389)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR modifies the test suite for resolving addresses with `Unstoppable Domain`. It updates the test structure to conditionally run based on the presence of `TW_SECRET_KEY` and retains the existing test logic.

### Detailed summary
- Changed `describe` to conditionally run with `describe.runIf(process.env.TW_SECRET_KEY)`.
- Retained the test for resolving a valid address.
- Retained the test for handling a non-existent domain name.

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

<!-- end pr-codex -->
2024-11-12 20:57:25 +00:00
gregfromstl a1707f66f3 [SDK] Fix: Add JSDoc documentation for getSocialIcon function (#5395)
CNCT-2324

<!-- start pr-codex -->

---

## PR-Codex overview
This PR enhances the documentation for the `getSocialIcon` function, providing a detailed JSDoc comment that explains its purpose, parameters, return value, and includes usage examples.

### Detailed summary
- Added a JSDoc comment for the `getSocialIcon` function.
- Described the function's purpose, parameters, return value, and provided usage examples.
- Changed the return type of `getSocialIcon` to explicitly specify it returns a `string`.

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

<!-- end pr-codex -->
2024-11-12 20:13:54 +00:00
MananTank f31801e790 ignore invalid module search param in deploy page (#5396)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the handling of the `module` parameter in the `searchParams` to support both an array and a string. It ensures that the code correctly processes the `module` regardless of its type.

### Detailed summary
- Updated `module` type in `searchParams` to allow both `string[]` and `string`.
- Modified the logic to handle `searchParams.module` as a string or an array.
- Ensured that `modules` is derived correctly from `moduleParam`, defaulting to an empty array if no valid modules are found.

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

<!-- end pr-codex -->
2024-11-12 19:52:35 +00:00
gregfromstl ef56304195 [SDK] Feature: Adds Ox for Hex and Bytes Utilities (#5366)
CNCT-2296

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on refactoring the `thirdweb` package to replace internal utility functions with a new library called `ox`, enhancing code efficiency and maintainability.

### Detailed summary
- Removed several utility files related to encoding and hex operations.
- Integrated `ox` library for hex and byte operations.
- Updated tests to reflect changes in utility functions.
- Increased size limits in `.size-limit.json`.
- Modified hash verification logic in `verify-hash.ts`.

> The following files were skipped due to too many changes: `pnpm-lock.yaml`

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

<!-- end pr-codex -->
2024-11-12 19:40:31 +00:00
MananTank daae1dce85 Update condition for showing verify email button in faucet claim section (#5394)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR updates the email verification logic for claiming from the faucet in the `FaucetButton` component. It changes the condition to check for both unconfirmed email and the absence of a confirmed email.

### Detailed summary
- Modified the condition to check if `emailConfirmedAt` is falsy and if `unconfirmedEmail` is truthy.
- Removed the previous check for `accountQuery.data.status === "noCustomer"`.

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

<!-- end pr-codex -->
2024-11-12 19:29:20 +00:00
Joaquim VergesandGitHub afb6343198 feat: add lens sepolia, zero testnet, and treasure topaz chains (#5391) 2024-11-13 04:32:55 +13:00
kien-ngo 7d34a8f50c Add Drop page for minting NFTs with partners (#5307)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the `Dashboard` layout and functionality for NFT drops, including improved metadata handling, wallet connection, and minting features.

### Detailed summary
- Added `AppFooter` and `ErrorProvider` in `DashboardLayout`.
- Introduced `DropPageData` type in `data.ts`.
- Enhanced `NftMint` component with quantity selection and custom address options.
- Integrated OpenGraph image generation for NFT drops.
- Updated `CustomConnectWallet` to accept `chain` prop.

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

<!-- end pr-codex -->
2024-11-12 14:39:19 +00:00
MananTank 9584885f42 Updated 404 page in dashboard, Add to App router (#5387)
![image.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/yNOf1svJ8o3zjO7zQouZ/cb7fcb59-0d12-4072-a426-3c9863302d1f.png)

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on creating a new `NotFound` page component and refactoring the existing 404 page to utilize this new component, improving code organization and reusability.

### Detailed summary
- Added `NotFoundPage` component in `apps/dashboard/src/components/not-found-page.tsx`.
- Created a structured layout for the 404 error message with links.
- Refactored the `PageNotFound` component in `apps/dashboard/src/pages/404.tsx` to use the new `NotFoundPage`.
- Removed unused imports and components related to the old 404 page design.

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

<!-- end pr-codex -->
2024-11-11 22:46:19 +00:00
MananTank c6310e60d5 Move deploy multiple and publish multiple pages to app router + migrate to shadcn/tailwind (#5381)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on the removal of several components and hooks related to contract deployment and publishing, while introducing new pages and components for handling these processes more effectively, including improved loading states and contract metadata handling.

### Detailed summary
- Deleted several files related to contract hooks and components.
- Introduced `DeployMultipleContractsPage` and `PublishMultipleContractsPage` for contract deployment and publishing.
- Added `SanctionedAddressesChecker` to handle blocked wallet addresses.
- Updated `DeployableContractTable` to fetch and display contract metadata.
- Refactored `ContractIdImage` to utilize fetched metadata directly.
- Changed table structure in `DeployableContractTable` to use new UI components.

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

<!-- end pr-codex -->
2024-11-11 20:42:42 +00:00
MananTank dc0ea8a286 Catch fetchDeployMetadata error and redirect to 404 (#5380)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR enhances error handling in the `page.tsx` files for the `publish` and `deploy` routes. It introduces checks to call `notFound()` when metadata cannot be fetched, improving user experience by handling cases where the requested data is unavailable.

### Detailed summary
- In `apps/dashboard/src/app/(dashboard)/contracts/publish/[publish_uri]/page.tsx`:
  - Added a check for `publishMetadataFromUri` and called `notFound()` if it is not present.

- In `apps/dashboard/src/app/(dashboard)/contracts/deploy/[compiler_uri]/page.tsx`:
  - Added a check for `metadata` and called `notFound()` if it is not present.

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

<!-- end pr-codex -->
2024-11-11 18:28:30 +00:00
MananTank e4b40080f4 Remove invalid modules loading spinner in contract deploy page (#5379)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on simplifying the conditional rendering of the `ModularContractDefaultModulesFieldset` component by removing unnecessary nested ternary operators and improving code readability.

### Detailed summary
- Removed nested ternary operator for rendering `ModularContractDefaultModulesFieldset`.
- Simplified the condition to check if `modules` exists and has length greater than 0.
- Streamlined the rendering logic for better readability.

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

<!-- end pr-codex -->
2024-11-11 16:13:04 +00:00
kien-ngo a1b518c676 [Dashboard] Remove tw-components Button from contract action-buttons (#5369)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on refactoring the button components across various files to standardize the usage of the `Button` component from the `@/components/ui/button` module, updating properties for consistency, and improving the UI elements.

### Detailed summary
- Replaced `Button` imports from `tw-components` with `@/components/ui/button`.
- Changed `colorScheme` to `variant` for button styling.
- Updated `isDisabled` to `disabled` for button state management.
- Added `className="gap-2"` for consistent spacing.
- Modified button content to include icons before text.

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

<!-- end pr-codex -->
2024-11-11 00:09:22 +00:00
kien-ngo 8c1fa1b7b4 [Dashboard] Remove tw-components from claim condition UI (#5368)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on improving the user interface and functionality of the `claim-conditions` components in the dashboard application. It enhances accessibility, updates styling, and refines the input handling for claim conditions.

### Detailed summary
- Updated `MaxClaimablePerWalletInput` to use `className` for styling and added a `target` attribute to the `Link`.
- Simplified `CustomFormControl` by replacing `Heading` with `FormLabel`.
- Changed `Text` components to `p` tags in `PricePreview` for better semantic structure.
- Refactored `QuantityInputWithUnlimited` to use new UI components and improved props handling.
- Enhanced `ClaimConditionsPhase` layout with Tailwind CSS classes, replacing Chakra UI components.
- Updated button styles and functionalities, including the removal button and editing toggle.
- Improved readability and structure of the phase details display.

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

<!-- end pr-codex -->
2024-11-11 00:09:20 +00:00
kien-ngo 453e45bd52 [Dashboard] Remove tw-components from marketplace components (#5367)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on replacing the `tw-components` library's `Text` and `Heading` components with standard HTML elements like `<p>` and `<div>` to simplify the code structure and improve styling consistency across various components in the dashboard.

### Detailed summary
- Replaced `Text` components with `<p>` in `csv-data-table.tsx`, `ContractDirectListingsPage.tsx`, and `ContractEnglishAuctionsPage.tsx`.
- Changed `Heading` components to `<p>` in multiple files for consistency.
- Updated button variants from `solid` to `default` in `marketplace-table.tsx`.
- Removed unnecessary imports of `Text` and `Heading` from `tw-components` in several files.
- Adjusted styling classes for better alignment and consistency.
- Enhanced layout structure in the `ListingDrawer` and `CreateListingsForm` components.

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

<!-- end pr-codex -->
2024-11-11 00:09:18 +00:00
MananTank 07af647067 Migrate profile page to tailwind and Move to app router and use RSCs (#5364)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on updating the profile management features within the application. It introduces new functionalities for resolving addresses and fetching published contracts while enhancing the user interface for profile editing.

### Detailed summary
- Removed unused files from the project.
- Updated `replaceDeployerAddress` to include a specific address replacement.
- Changed `PlaygroundMenu` to use `useDashboardRouter`.
- Enhanced `getSortedDeployedContracts` to handle mainnet filtering.
- Made `mapThirdwebPublisher` an exportable function.
- Added `resolveAddressAndEns` for address resolution.
- Introduced `PublishedContracts` component to display published contracts.
- Modified `DeployedContracts` to simplify contract listing logic.
- Updated `ProfileUI` to fetch and display publisher profiles.
- Enhanced `EditProfile` component for better profile editing experience.
- Improved `PublisherSocials` to streamline social media links.
- Added loading states and error handling in various components.

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

<!-- end pr-codex -->
2024-11-09 01:44:53 +00:00
gregfromstl 535e1df9a3 [Dashboard] Fix: More Analytics Cleanup (#5365)
CNCT-2294

https://github.com/user-attachments/assets/29e5e8e7-ff03-46ff-aa68-4f8e13fe323f

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on refactoring components and improving the organization of analytics-related files. It updates titles in story files, introduces new components, and modifies existing ones to enhance functionality and maintainability.

### Detailed summary
- Updated story titles in `Stat`, `BarChart`, `PieChart`, `EmptyState`, and `CombinedBarChartCard` components.
- Added `InAppWalletStats`, `EcosystemWalletStats`, and `UserOpStats` types for better analytics data handling.
- Refactored `createUserOpStatsStub` to return `UserOpStats[]`.
- Enhanced `BarChart` and `CombinedBarChartCard` to support currency formatting.
- Introduced `EmptyStateCard` component for better UI handling of no data scenarios.
- Removed deprecated `UserOpStatsByChain` type and updated related functions.
- Improved data fetching functions to align with new analytics structure.

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

<!-- end pr-codex -->
2024-11-09 00:54:41 +00:00
jnsdls a9375ce593 update homepage routing and redirects (#5362) 2024-11-08 21:55:41 +00:00
60eec0344d feat: use paymaster for all zk testnet chains (#5358)
Co-authored-by: 0xFirekeeper <[email protected]>
2024-11-09 09:59:39 +13:00
MananTank 2e641f816a Migrate usage page to tailwind, render on server + Improved UI (#5361)
![image.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/yNOf1svJ8o3zjO7zQouZ/e0bad265-504c-4eab-811c-ece0607a6852.png)
2024-11-08 20:21:50 +00:00
MananTank c8ad066cf2 Dashboard storybook minor cleanup + fix crashing stories (#5359)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on updating the titles in various story files to follow a consistent naming convention, improving UI component integration, and enhancing the use of the `getThirdwebClient()` function across multiple components.

### Detailed summary
- Updated titles in story files for consistency.
- Modified `StatBreakdownCard` to use a max width.
- Integrated `getThirdwebClient()` in multiple components.
- Adjusted `AccountTeamsUI` and `TeamRow` to accept `client` as a prop.
- Enhanced theme handling in the Storybook preview.

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

<!-- end pr-codex -->
2024-11-08 18:07:30 +00:00
kien-ngo 0bba41446b [Dashboard] Fix padding issue on NFT tab buttons (#5357)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR updates the environment configuration and modifies a component in the dashboard application to enhance functionality and maintainability.

### Detailed summary
- Added `ANALYTICS_SERVICE_URL` to the `.env.example` file.
- Updated `REDIS_URL` in the `.env.example` file.
- Refactored the `ButtonGroup` component in `token-id.tsx` to streamline the JSX structure by removing unnecessary line breaks and attributes.

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

<!-- end pr-codex -->
2024-11-08 09:39:12 +00:00
kien-ngo 6dda1c242e [Dashboard] Fix overflow on team overview page (#5356)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on modifying the layout of the `PieChartCard` component in the `Analytics` section of the dashboard. It changes a `div` class to improve the responsiveness and wrapping behavior of the child elements.

### Detailed summary
- Changed the class of a `div` from `flex-nowrap` to `flex-wrap` to allow child elements to wrap in the `PieChartCard`.

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

<!-- end pr-codex -->
2024-11-08 09:01:21 +00:00
jnsdls 6b1f4184a4 update deps and fix turbopack build? (#5345)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on updating various package dependencies and adjusting import paths in multiple files, ensuring compatibility and improvements in the application.

### Detailed summary
- Updated `@cloudflare/workers-types` to version `4.20241106.0`.
- Adjusted import paths in `engine-list.tsx` and `publish-submit.tsx`.
- Updated several dependencies in `package.json` files across multiple apps:
  - `@aws-sdk` packages to version `3.687.0`.
  - `@tanstack/react-query` to version `5.59.20`.
  - `next` to version `15.0.3`.
  - `eslint-config-next` to version `15.0.3`.
- Modified scripts in `dashboard/package.json` for build optimizations.
- Updated `@next/mdx` to version `15.0.3`.
- Various other minor dependency updates across packages.

> The following files were skipped due to too many changes: `pnpm-lock.yaml`

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

<!-- end pr-codex -->
2024-11-08 06:03:18 +00:00
gregfromstl 9910a0230a [Dashboard] Feature: Team Analytics Page (#5353)
CNCT-2291

https://github.com/user-attachments/assets/163c5eb5-c4dc-4d0f-a6aa-099a321148e8

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on refactoring components related to analytics in the dashboard. It replaces the `ProjectOverviewHeader` with an `AnalyticsHeader`, updates various components to enhance functionality, and introduces new analytics-related features.

### Detailed summary
- Deleted `Steps.tsx` and `HomeProductCard.tsx`.
- Replaced `ProjectOverviewHeader` with `AnalyticsHeader` in various files.
- Updated `projectStub` function to remove the export.
- Introduced new components for analytics: `UsersChartCard`, `WalletDistributionCard`, `AuthMethodDistributionCard`, `TotalSponsoredCard`, and `UserOpUsageCard`.
- Enhanced `EmptyState` component styling and functionality.
- Adjusted imports to reflect new component paths.
- Added new fetching functions for analytics data: `getWalletConnections`, `getInAppWalletUsage`, `getUserOpUsage`, and `getWalletUsers`.

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

<!-- end pr-codex -->
2024-11-08 05:42:02 +00:00
ElasticBottle 9572db007a [Dashboard]: fix client ID not being passed correctly to in app wallet user content (#5355)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR enhances the `Page` component in the `in-app-wallets/users` directory by adding error handling for project retrieval and updating the `clientId` property passed to `InAppWalletUsersPageContent`.

### Detailed summary
- Imported `notFound` from `next/navigation`.
- Fetched the `project` using `getProject` based on `team_slug` and `project_slug`.
- Added a check to call `notFound()` if the `project` is not found.
- Updated `clientId` prop in `InAppWalletUsersPageContent` to use `project.publishableKey`.

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

<!-- end pr-codex -->
2024-11-08 04:15:45 +00:00
gregfromstl 5de541878c [SDK] Feature: Include chain ID in analytics events (#5343)
CNCT-2138

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on adding the `chainId` to various components and functions within the `thirdweb` package, enhancing analytics tracking and wallet creation processes by including chain-specific information.

### Detailed summary
- Added `chainId` to `TransactionModal.tsx` for analytics.
- Updated `ConfirmationScreen.tsx` to use `fromToken.chainId`.
- Included `chainId` in wallet creation functions across multiple wallet types.
- Modified `trackConnect` to accept `chainId` in its arguments.

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

<!-- end pr-codex -->
2024-11-08 01:44:33 +00:00
Joaquim VergesandGitHub 46d0b4bdc0 fix: use maxFeePerGas for Pay gas estimations (#5349) 2024-11-08 14:26:54 +13:00
Joaquim VergesandGitHub 1e7e32f2cb feat: Support sophon testnet (#5335) 2024-11-08 14:10:52 +13:00
gregfromstl d34357e0a9 [Dashboard] Fix: Cleanup dashboard UI (#5344)
CNCT-2282

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on refactoring components related to analytics and charts in the dashboard, removing unused components, improving data handling, and enhancing UI elements for better responsiveness and clarity.

### Detailed summary
- Deleted `ConnectSDKCard.tsx` and `ConnectAnalyticsDashboard.tsx` files.
- Removed the `Analytics` link from the sidebar.
- Enhanced `PieChartCard` and `BarChart` components with better data mapping and UI adjustments.
- Updated `RangeSelector` to handle local state for range and interval.
- Removed unused API functions related to wallet usage.
- Improved data filtering logic in `WalletDistributionCard` and `AuthMethodDistributionCard`.
- Adjusted image rendering for better layout consistency in charts.

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

<!-- end pr-codex -->
2024-11-08 00:32:04 +00:00
arcoraven 03ce8816ce fix: Engine wallet selector dropdown (#5347)
https://linear.app/thirdweb/issue/INFRA-409/fix-engine-wallet-dropdown-bug-in-dashboard

<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on enhancing the backend wallet management by refining wallet type options and improving the user interface for wallet actions. It introduces restrictions on importing smart backend wallets and updates the icons in the wallet management table.

### Detailed summary
- Added a comment to disallow importing smart backend wallets.
- Filtered `EngineBackendWalletOptions` to exclude keys starting with "smart:".
- Replaced `Trash2Icon` with `PencilIcon` for the edit action in the `BackendWalletsTable`.
- Updated logic for checking wallet configuration to simplify conditions.
- Improved `onValueChange` handler in `CreateBackendWalletButton`.

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

<!-- end pr-codex -->
2024-11-07 23:22:49 +00:00
gregfromstl 2d99708c45 fix: flaky test (#5346)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on modifying the test for switching chains in the `smart-wallet-integration.test.ts` file. It simplifies the test by removing the transaction execution and balance verification, only asserting that the wallet's chain ID matches the expected value.

### Detailed summary
- Removed the creation of the `baseSepoliaEdition` contract instance.
- Eliminated the transaction sending and confirmation logic.
- Removed balance checking for the `baseSepoliaEdition` contract.
- Added an assertion to check if the wallet's current chain ID matches `baseSepolia.id`.

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

<!-- end pr-codex -->
2024-11-07 21:45:29 +00:00
MananTank aa46e27d7c Replace legacy dashboard with Teams and Projects dashboard (#5327)
## Changes Summary

### Huge changes
* Delete all `/dashboard/**` pages and make the `/team` the default
* Rename all the links `/dashboard/**` to the new equivalent + Setup redirects

### New pages Added in team and account layouts
* `/account/contracts`
* `/team/<team-slug>/~/contracts`
* `/account/devices`
* new team overview page
* `/team/<team-slug>/~/usage/storage`

### Pages Moved
* Engine pages moved from project to team
* Ecosystem pages moved from project to team
* Team's projects page moved to /projects ( `/team/<team-slug>/~/projects` )

### Links to these pages removed for now (page not deleted) in new dashboard because they are not yet ready
* `/account/wallets`, `/account/settings`

### Minor changes
* Add the `AutoConnect` component directly where we render `ThirdwebProvider` so it is always triggered and not only when certain components are rendered, and remove AutoConnect at component level
* Move `/chain/validate` to app router inside (dashboard) layout
* Remove unnecessary breadcrumb on /explore base page
* Various UI adjustments / improvements
* Add billing alerts on top of all team, project, account pages

### Fixes
* Fix theme switcher not working sometimes in app router
* Engine pages crashing in team dashboard
* Revert color changes done in a recent a PR, fix colors in project header, app footer done.
* Fix Team avatar not resolving IPFS
* Fix various react warnings
* Fix missing Overview tab on the ecosystem page

### Improvements
* Reduced redirects on in-app-wallets and connect analytics page by making the landing page the default. For example: render analytics on `/connect/in-app-wallets` instead of landing on `/connect/in-app-wallets` and then redirecting to `/connect/in-app-wallets/analytics` and then rendering the page )
2024-11-07 20:26:29 +00:00
gregfromstl 9f9410a77a [Dashboard] Feature: Project Overview (Analytics) Page (#5340)
CNCT-2182
CNCT-2180
CNCT-2177
CNCT-2179
CNCT-2178
CNCT-2176

https://github.com/user-attachments/assets/5c676ec4-ca2f-4796-880a-944636f9f0d5

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the analytics components in the dashboard by introducing new stats interfaces, updating existing components to utilize these stats, and improving the layout and functionality of various charts and cards.

### Detailed summary
- Added `WalletUserStats` interface.
- Updated `ProjectOverviewHeader` to accept `interval` and `range`.
- Enhanced `Stat` component to display trends with badges.
- Introduced `PieChart`, `BarChart`, and `CombinedBarChartCard` components.
- Updated `EmptyState` messaging.
- Improved layout in several components for better responsiveness.
- Added new stories for `PieChartCard`, `StatBreakdownCard`, and others in Storybook.
- Refactored data fetching functions to support new stats.

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

<!-- end pr-codex -->
2024-11-07 18:33:42 +00:00
1118 changed files with 26544 additions and 24273 deletions
+6
View File
@@ -8,6 +8,12 @@ playground:
packages:
- changed-files:
- any-glob-to-any-file: "packages/**/*"
sdk:
- changed-files:
- any-glob-to-any-file: "packages/thirdweb/**/*"
portal:
- changed-files:
- any-glob-to-any-file: "apps/portal/**/*"
"Ecosystem Portal":
- changed-files:
- any-glob-to-any-file: "apps/wallet-ui/**/*"
+1 -1
View File
@@ -88,7 +88,7 @@ jobs:
- run: pnpm test
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v5
with:
directory: packages/
flags: packages
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
echo "Pull Request Body: $pr_body"
if echo "$pr_body" | grep -iE "CNCT|DASH"; then
if echo "$pr_body" | grep -iE "CNCT|DASH|BLOCK"; then
echo "Linked issue found in the pull request body."
else
echo "No linked issue found in the pull request body."
+2 -2
View File
@@ -11,5 +11,5 @@ jobs:
with:
stale-issue-message: 'This issue has been inactive for 30 days. It is now marked as stale and will be closed in 5 days if no further activity occurs.'
stale-pr-message: 'This PR has been inactive for 30 days. It is now marked as stale and will be closed in 5 days if no further activity occurs.'
days-before-stale: 30
days-before-close: 5
days-before-stale: 7
days-before-close: 2
+3
View File
@@ -24,5 +24,8 @@
},
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[css]": {
"editor.defaultFormatter": "biomejs.biome"
}
}
+3 -1
View File
@@ -92,4 +92,6 @@ API_SERVER_SECRET=""
# Used for the Faucet page (/<chain_id>)
NEXT_PUBLIC_TURNSTILE_SITE_KEY=""
TURNSTILE_SECRET_KEY=""
REDIS_URL=""
REDIS_URL=""
ANALYTICS_SERVICE_URL=""
+24 -17
View File
@@ -2,15 +2,13 @@ import type { Preview } from "@storybook/react";
import "@/styles/globals.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { MoonIcon, SunIcon } from "lucide-react";
import { ThemeProvider, useTheme } from "next-themes";
import { Inter as interFont } from "next/font/google";
// biome-ignore lint/style/useImportType: <explanation>
import React, { useState } from "react";
import React from "react";
import { useEffect } from "react";
import { Button } from "../src/@/components/ui/button";
// Note: Wrapping the Stoy with AppRouterProviders makes the storybook server time SUPER SLOW
// so let's just not have it there - all stories should be independent from context anyway and just rely on props
const queryClient = new QueryClient();
const fontSans = interFont({
@@ -31,9 +29,16 @@ const preview: Preview = {
decorators: [
(Story) => {
return (
<StoryLayout>
<Story />
</StoryLayout>
<ThemeProvider
attribute="class"
disableTransitionOnChange
enableSystem={false}
defaultTheme="dark"
>
<StoryLayout>
<Story />
</StoryLayout>
</ThemeProvider>
);
},
],
@@ -44,34 +49,36 @@ export default preview;
function StoryLayout(props: {
children: React.ReactNode;
}) {
const [theme, setTheme] = useState<"light" | "dark">("dark");
const { setTheme, theme } = useTheme();
useEffect(() => {
document.body.className = `font-sans antialiased ${fontSans.variable} ${theme === "dark" ? " dark" : ""}`;
}, [theme]);
document.body.className = `font-sans antialiased ${fontSans.variable}`;
}, []);
return (
<QueryClientProvider client={queryClient}>
<div className="min-h-screen flex flex-col bg-background text-foreground min-w-0">
<div className="flex justify-end p-4 border-b gap-2">
<div className="flex min-h-screen min-w-0 flex-col bg-background text-foreground">
<div className="flex justify-end gap-2 border-b p-4">
<Button
onClick={() => setTheme("dark")}
size="sm"
variant={theme === "dark" ? "default" : "ghost"}
variant={theme === "dark" ? "default" : "outline"}
className="h-auto w-auto rounded-full p-2"
>
<MoonIcon className="size-5" />
<MoonIcon className="size-4" />
</Button>
<Button
onClick={() => setTheme("light")}
size="sm"
variant={theme === "light" ? "default" : "ghost"}
variant={theme === "light" ? "default" : "outline"}
className="h-auto w-auto rounded-full p-2"
>
<SunIcon className="size-5" />
<SunIcon className="size-4" />
</Button>
</div>
<div className="grow flex flex-col min-w-0">{props.children}</div>
<div className="flex min-w-0 grow flex-col">{props.children}</div>
</div>
</QueryClientProvider>
);
+2
View File
@@ -2,9 +2,11 @@
module.exports = [
// -- home
"/",
"/pricing",
// -- product landing pages --
// -- connect
"/connect/sign-in",
"/nebula",
// -- contracts
"/contracts/modular-contracts",
"/contracts/explore",
@@ -1,4 +1,8 @@
/* eslint-disable @typescript-eslint/no-var-requires */
import type { SentryBuildOptions } from "@sentry/nextjs";
import type { NextConfig } from "next";
import type { RemotePattern } from "next/dist/shared/lib/image-config";
import FRAMER_PATHS from "./framer-rewrites";
import getRedirects from "./redirects";
const ContentSecurityPolicy = `
default-src 'self';
@@ -37,15 +41,9 @@ const securityHeaders = [
},
];
const redirects = require("./redirects");
const FRAMER_PATHS = require("./framer-rewrites");
/**
* @returns {import('next').RemotePattern[]}
*/
function determineIpfsGateways() {
// add the clientId ipfs gateways
const remotePatterns = [];
const remotePatterns: RemotePattern[] = [];
if (process.env.API_ROUTES_CLIENT_ID) {
remotePatterns.push({
protocol: "https",
@@ -84,23 +82,40 @@ function determineIpfsGateways() {
return remotePatterns;
}
/** @type {import('next').NextConfig} */
const moduleExports = {
webpack: (config, { dev }) => {
if (config.cache && !dev) {
config.cache = Object.freeze({
type: "memory",
});
config.cache.maxMemoryGenerations = 0;
}
config.externals.push("pino-pretty");
config.module = {
...config.module,
exprContextCritical: false,
};
// Important: return the modified config
return config;
},
const SENTRY_OPTIONS: SentryBuildOptions = {
// For all available options, see:
// https://github.com/getsentry/sentry-webpack-plugin#options
org: "thirdweb-dev",
project: "dashboard",
// An auth token is required for uploading source maps.
authToken: process.env.SENTRY_AUTH_TOKEN,
// Suppresses source map uploading logs during build
silent: true,
// For all available options, see:
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
// Upload a larger set of source maps for prettier stack traces (increases build time)
widenClientFileUpload: true,
// Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers (increases server load)
tunnelRoute: "/err",
// Hides source maps from generated client bundles
hideSourceMaps: true,
// Automatically tree-shake Sentry logger statements to reduce bundle size
disableLogger: true,
// Enables automatic instrumentation of Vercel Cron Monitors.
// See the following for more information:
// https://docs.sentry.io/product/crons/
// https://vercel.com/docs/cron-jobs
automaticVercelMonitors: false,
};
const baseNextConfig: NextConfig = {
serverExternalPackages: ["pino-pretty"],
async headers() {
return [
{
@@ -117,7 +132,7 @@ const moduleExports = {
];
},
async redirects() {
return redirects();
return getRedirects();
},
async rewrites() {
return [
@@ -151,74 +166,57 @@ const moduleExports = {
...determineIpfsGateways(),
],
},
reactStrictMode: true,
experimental: {
scrollRestoration: true,
webpackBuildWorker: true,
serverSourceMaps: false,
},
cacheMaxMemorySize: 0,
compiler: {
emotion: true,
},
productionBrowserSourceMaps: false,
reactStrictMode: true,
};
const { withSentryConfig } = require("@sentry/nextjs");
const { withPlausibleProxy } = require("next-plausible");
function getConfig(): NextConfig {
if (process.env.NODE_ENV === "production") {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const withBundleAnalyzer = require("@next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true",
});
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { withPlausibleProxy } = require("next-plausible");
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { withSentryConfig } = require("@sentry/nextjs");
return withBundleAnalyzer(
withPlausibleProxy({
customDomain: "https://pl.thirdweb.com",
scriptName: "pl",
})(
withSentryConfig(
{
...baseNextConfig,
experimental: {
webpackBuildWorker: true,
},
// @ts-expect-error - this is a valid option
webpack: (config, { dev }) => {
if (config.cache && !dev) {
config.cache = Object.freeze({
type: "filesystem",
maxMemoryGenerations: 0,
});
}
config.externals.push("pino-pretty");
config.module = {
...config.module,
exprContextCritical: false,
};
// Important: return the modified config
return config;
},
},
SENTRY_OPTIONS,
),
),
);
}
// otherwise return the base
return baseNextConfig;
}
// we only want sentry on production environments
const wSentry =
process.env.NODE_ENV === "production" ? withSentryConfig : (x) => x;
const withBundleAnalyzer = require("@next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true",
});
module.exports = withBundleAnalyzer(
withPlausibleProxy({
customDomain: "https://pl.thirdweb.com",
scriptName: "pl",
})(
wSentry(moduleExports, {
// 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,
// Transpiles SDK to be compatible with IE11 (increases bundle size)
transpileClientSDK: false,
// Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers (increases server load)
tunnelRoute: "/err",
// Hides source maps from generated client bundles
hideSourceMaps: true,
// Automatically tree-shake Sentry logger statements to reduce bundle size
disableLogger: true,
// Enables automatic instrumentation of Vercel Cron Monitors.
// See the following for more information:
// https://docs.sentry.io/product/crons/
// https://vercel.com/docs/cron-jobs
automaticVercelMonitors: false,
/**
* Disables the Sentry Webpack plugin on the server.
* See: https://github.com/getsentry/sentry-javascript/issues/10468#issuecomment-2004710692
*/
disableServerWebpackPlugin: true,
}),
),
);
export default getConfig();
+26 -29
View File
@@ -5,7 +5,7 @@
"scripts": {
"preinstall": "npx only-allow pnpm",
"dev": "next dev",
"build": "next build",
"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",
@@ -45,12 +45,10 @@
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.1",
"@radix-ui/react-tooltip": "1.1.3",
"@sentry/nextjs": "8.37.1",
"@radix-ui/react-tooltip": "1.1.4",
"@sentry/nextjs": "8.38.0",
"@shazow/whatsabi": "^0.16.0",
"@stripe/react-stripe-js": "^2.8.1",
"@stripe/stripe-js": "^3.5.0",
"@tanstack/react-query": "5.59.19",
"@tanstack/react-query": "5.60.2",
"@tanstack/react-table": "^8.17.3",
"@thirdweb-dev/service-utils": "workspace:*",
"@vercel/functions": "^1.4.2",
@@ -63,23 +61,21 @@
"compare-versions": "^6.1.0",
"date-fns": "4.1.0",
"flat": "^6.0.1",
"framer-motion": "11.11.11",
"framer-motion": "11.11.17",
"fuse.js": "7.0.0",
"input-otp": "^1.4.1",
"ioredis": "^5.4.1",
"ipaddr.js": "^2.2.0",
"lottie-react": "^2.4.0",
"lucide-react": "0.454.0",
"next": "15.0.2",
"next-plausible": "^3.12.3",
"lucide-react": "0.456.0",
"next": "15.0.3",
"next-plausible": "^3.12.4",
"next-seo": "^6.5.0",
"next-themes": "^0.4.3",
"nextjs-toploader": "^1.6.12",
"papaparse": "^5.4.1",
"pluralize": "^8.0.0",
"posthog-js": "1.67.1",
"prism-react-renderer": "^2.3.1",
"prismjs": "^1.29.0",
"qrcode": "^1.5.3",
"react": "19.0.0-rc-69d4b800-20241021",
"react-children-utilities": "^2.10.0",
@@ -87,13 +83,14 @@
"react-dom": "19.0.0-rc-69d4b800-20241021",
"react-dropzone": "^14.3.5",
"react-error-boundary": "^4.1.2",
"react-hook-form": "7.52.0",
"react-hook-form": "7.53.2",
"react-markdown": "^9.0.1",
"react-responsive-carousel": "^3.2.23",
"react-table": "^7.8.0",
"recharts": "^2.13.3",
"remark-gfm": "^4.0.0",
"server-only": "^0.0.1",
"shiki": "1.22.2",
"sonner": "^1.7.0",
"spdx-correct": "^3.2.0",
"swagger-ui-react": "^5.17.14",
@@ -107,18 +104,18 @@
"devDependencies": {
"@chakra-ui/cli": "^2.4.1",
"@chromatic-com/storybook": "3.2.2",
"@next/bundle-analyzer": "15.0.2",
"@next/eslint-plugin-next": "15.0.2",
"@next/bundle-analyzer": "15.0.3",
"@next/eslint-plugin-next": "15.0.3",
"@playwright/test": "1.48.2",
"@storybook/addon-essentials": "8.4.2",
"@storybook/addon-interactions": "8.4.2",
"@storybook/addon-links": "8.4.2",
"@storybook/addon-onboarding": "8.4.2",
"@storybook/addon-viewport": "8.4.2",
"@storybook/blocks": "8.4.2",
"@storybook/nextjs": "8.4.2",
"@storybook/react": "8.4.2",
"@storybook/test": "8.4.2",
"@storybook/addon-essentials": "8.4.4",
"@storybook/addon-interactions": "8.4.4",
"@storybook/addon-links": "8.4.4",
"@storybook/addon-onboarding": "8.4.4",
"@storybook/addon-viewport": "8.4.4",
"@storybook/blocks": "8.4.4",
"@storybook/nextjs": "8.4.4",
"@storybook/react": "8.4.4",
"@storybook/test": "8.4.4",
"@types/color": "4.2.0",
"@types/node": "20.14.9",
"@types/papaparse": "^5.3.15",
@@ -135,13 +132,13 @@
"checkly": "^4.8.1",
"eslint": "8.57.0",
"eslint-config-biome": "1.9.3",
"eslint-plugin-react-compiler": "19.0.0-beta-8a03594-20241020",
"eslint-plugin-react-compiler": "19.0.0-beta-a7bf2bd-20241110",
"eslint-plugin-storybook": "^0.9.0",
"knip": "5.36.2",
"knip": "5.37.0",
"next-sitemap": "^4.2.3",
"postcss": "8.4.47",
"storybook": "8.4.2",
"tailwindcss": "3.4.14",
"postcss": "8.4.49",
"storybook": "8.4.4",
"tailwindcss": "3.4.15",
"typescript": "5.6.3"
}
}
Binary file not shown.
+69 -124
View File
@@ -1,5 +1,69 @@
const legacyDashboardToTeamRedirects = [
{
source: "/dashboard",
destination: "/team",
permanent: false,
},
{
source: "/dashboard/contracts/:path*",
destination: "/team/~/~/contracts",
permanent: false,
},
{
source: "/dashboard/connect/ecosystem/:path*",
destination: "/team/~/~/ecosystem/:path*",
permanent: false,
},
{
source: "/dashboard/engine/:path*",
destination: "/team/~/~/engine/:path*",
permanent: false,
},
{
source: "/dashboard/settings/api-keys",
destination: "/team/~/~/projects",
permanent: false,
},
{
source: "/dashboard/settings/devices",
destination: "/account/devices",
permanent: false,
},
{
source: "/dashboard/settings/billing",
destination: "/team/~/~/settings/billing",
permanent: false,
},
{
source: "/dashboard/settings/gas-credits",
destination: "/team/~/~/settings/credits",
permanent: false,
},
{
source: "/dashboard/settings/usage",
destination: "/team/~/~/usage",
permanent: false,
},
{
source: "/dashboard/settings/storage",
destination: "/team/~/~/usage/storage",
permanent: false,
},
{
source: "/dashboard/settings/notifications",
destination: "/team/~/~/settings/notifications",
permanent: false,
},
// rest of the /dashboard/* routes
{
source: "/dashboard/:path*",
destination: "/team",
permanent: false,
},
];
/** @type {import('next').NextConfig['redirects']} */
function redirects() {
async function redirects() {
return [
{
source: "/portal/:match*",
@@ -11,16 +75,6 @@ function redirects() {
destination: "/solutions/chains",
permanent: true,
},
{
source: "/dashboard/publish/:path*",
destination: "/contracts/publish/:path*",
permanent: false,
},
{
source: "/dashboard/mumbai/publish/:path*",
destination: "/contracts/publish/:path*",
permanent: false,
},
{
source: "/contracts/release",
destination: "/contracts/publish",
@@ -97,23 +151,8 @@ function redirects() {
destination: "/polygon-zkevm",
permanent: false,
},
{
source: "/settings",
destination: "/dashboard/settings/api-keys",
permanent: false,
},
// backwards compat: page moved to pages/settings/api-key
{
source: "/dashboard/api-keys",
destination: "/dashboard/settings/api-keys",
permanent: false,
},
// backwards compat: page moved to pages/settings/devices
{
source: "/dashboard/settings/account",
destination: "/dashboard/settings/devices",
permanent: false,
},
{
source: "/template/nft-drop",
destination: "/template/erc721",
@@ -121,17 +160,12 @@ function redirects() {
},
{
source: "/create-api-key",
destination: "/dashboard/settings/api-keys",
destination: "/team/~/~/projects",
permanent: false,
},
{
source: "/dashboard/settings",
destination: "/dashboard/settings/api-keys",
permanent: false,
},
{
source: "/dashboard/connect",
destination: "/dashboard/connect/analytics",
destination: "/team/~/~/projects",
permanent: false,
},
{
@@ -139,61 +173,6 @@ function redirects() {
destination: "https://playground.thirdweb.com/connect/sign-in/button",
permanent: false,
},
{
source: "/dashboard/wallet",
destination: "https://playground.thirdweb.com/connect/sign-in/button",
permanent: false,
},
{
source: "/dashboard/rpc",
destination: "/dashboard/infrastructure/rpc-edge",
permanent: false,
},
{
source: "/dashboard/storage",
destination: "/dashboard/infrastructure/storage",
permanent: false,
},
{
source: "/smart-wallet",
destination: "/account-abstraction",
permanent: false,
},
{
source: "/dashboard/published",
destination: "/dashboard/publish",
permanent: false,
},
{
source: "/dashboard/wallets",
destination: "https://playground.thirdweb.com/connect/sign-in/button",
permanent: false,
},
{
source: "/dashboard/wallets/analytics",
destination: "/dashboard/connect/analytics",
permanent: false,
},
{
source: "/dashboard/wallets/embedded",
destination: "/dashboard/connect/embedded-wallets",
permanent: false,
},
{
source: "/dashboard/wallets/smart-wallet",
destination: "/dashboard/connect/account-abstraction",
permanent: false,
},
{
source: "/dashboard/wallets/connect",
destination: "https://playground.thirdweb.com/connect/sign-in/button",
permanent: false,
},
{
source: "/dashboard/infrastructure",
destination: "/dashboard/settings/storage",
permanent: false,
},
{
source: "/dashboard/infrastructure/storage",
destination: "/dashboard/settings/storage",
@@ -204,26 +183,6 @@ function redirects() {
destination: "/chainlist",
permanent: false,
},
{
source: "/dashboard/contracts",
destination: "/dashboard/contracts/deploy",
permanent: false,
},
{
source: "/solidity-sdk",
destination: "/build",
permanent: false,
},
{
source: "/connect-wallet",
destination: "/connect",
permanent: false,
},
{
source: "/wallet-sdk",
destination: "https://portal.thirdweb.com/wallet-sdk/latest",
permanent: false,
},
{
source: "/solutions/commerce",
destination: "/solutions/loyalty",
@@ -244,27 +203,12 @@ function redirects() {
destination: "/optimism",
permanent: false,
},
{
source: "/dashboard/payments/settings",
destination: "/dashboard/connect/pay",
permanent: false,
},
{
source: "/dashboard/payments/contracts",
destination: "/dashboard/connect/pay",
permanent: false,
},
// Redirecting as ambassadors lives in community now
{
source: "/ambassadors",
destination: "/community/ambassadors",
permanent: false,
},
{
source: "/dashboard/connect/embedded-wallets",
destination: "/dashboard/connect/in-app-wallets",
permanent: false,
},
{
source: "/embedded-wallets",
destination: "/in-app-wallets",
@@ -321,6 +265,7 @@ function redirects() {
destination: "/account-abstraction",
permanent: false,
},
...legacyDashboardToTeamRedirects,
];
}
+117
View File
@@ -0,0 +1,117 @@
"use server";
import "server-only";
import { API_SERVER_URL, getAbsoluteUrlFromPath } from "@/constants/env";
import { redirect } from "next/navigation";
import { getAuthToken } from "../../app/api/lib/getAuthToken";
import type { ProductSKU } from "../lib/billing";
export type RedirectCheckoutOptions = {
teamSlug: string;
sku: ProductSKU;
redirectPath?: string;
metadata?: Record<string, string>;
};
export async function redirectToCheckout(
options: RedirectCheckoutOptions,
): Promise<{ status: number }> {
if (!options.teamSlug) {
return {
status: 400,
};
}
const token = await getAuthToken();
if (!token) {
return {
status: 401,
};
}
const res = await fetch(
`${API_SERVER_URL}/v1/teams/${options.teamSlug}/checkout/create-link`,
{
method: "POST",
body: JSON.stringify({
sku: options.sku,
redirectTo: getAbsoluteUrlFromPath(
options.redirectPath ||
`/team/${options.teamSlug}/~/settings/billing`,
).toString(),
metadata: options.metadata || {},
}),
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
},
);
if (!res.ok) {
return {
status: res.status,
};
}
const json = await res.json();
if (!json.result) {
return {
status: 500,
};
}
// redirect to the stripe checkout session
redirect(json.result);
}
export type BillingPortalOptions = {
teamSlug: string | undefined;
redirectPath?: string;
};
export async function redirectToBillingPortal(
options: BillingPortalOptions,
): Promise<{ status: number }> {
if (!options.teamSlug) {
return {
status: 400,
};
}
const token = await getAuthToken();
if (!token) {
return {
status: 401,
};
}
const res = await fetch(
`${API_SERVER_URL}/v1/teams/${options.teamSlug}/checkout/create-session-link`,
{
method: "POST",
body: JSON.stringify({
redirectTo: getAbsoluteUrlFromPath(
options.redirectPath ||
`/team/${options.teamSlug}/~/settings/billing`,
).toString(),
}),
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
},
);
if (!res.ok) {
return {
status: res.status,
};
}
const json = await res.json();
if (!json.result) {
return {
status: 500,
};
}
// redirect to the stripe billing portal
redirect(json.result);
}
@@ -0,0 +1,39 @@
"use server";
import { getAuthToken } from "../../app/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";
export async function confirmEmailWithOTP(otp: string) {
const token = await getAuthToken();
if (!token) {
return {
errorMessage: "You are not authorized to perform this action",
};
}
const res = await fetch(`${API_SERVER_URL}/v1/account/confirmEmail`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
confirmationToken: otp,
}),
});
if (!res.ok) {
const json = await res.json();
if (json.error) {
return {
errorMessage: json.error.message,
};
}
return {
errorMessage: "Failed to confirm email",
};
}
}
@@ -0,0 +1,7 @@
"use server";
import { getRawAccount } from "../../app/account/settings/getAccount";
export async function getRawAccountAction() {
return getRawAccount();
}
@@ -0,0 +1,40 @@
"use server";
import { getAuthToken } from "../../app/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";
export async function joinTeamWaitlist(options: {
teamSlug: string;
// currently only 'nebula' is supported
scope: "nebula";
}) {
const { teamSlug, scope } = options;
const token = await getAuthToken();
if (!token) {
return {
errorMessage: "You are not authorized to perform this action",
};
}
const res = await fetch(`${API_SERVER_URL}/v1/teams/${teamSlug}/waitlist`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
scope,
}),
});
if (!res.ok) {
return {
errorMessage: "Failed to join waitlist",
};
}
return {
success: true,
};
}
@@ -0,0 +1,37 @@
"use server";
import { getAuthToken } from "../../app/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";
export async function updateAccount(values: {
name?: string;
email?: string;
}) {
const token = await getAuthToken();
if (!token) {
throw new Error("No Auth token");
}
const res = await fetch(`${API_SERVER_URL}/v1/account`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(values),
});
if (!res.ok) {
const json = await res.json();
if (json.error) {
return {
errorMessage: json.error.message,
};
}
return {
errorMessage: "Failed To Update Account",
};
}
}
+137
View File
@@ -0,0 +1,137 @@
import { fetchAnalytics } from "data/analytics/fetch-analytics";
export interface WalletStats {
date: string;
uniqueWalletsConnected: number;
totalConnections: number;
walletType: string;
}
export interface WalletUserStats {
date: string;
newUsers: number;
returningUsers: number;
totalUsers: number;
}
export interface InAppWalletStats {
date: string;
authenticationMethod: string;
uniqueWalletsConnected: number;
}
export interface EcosystemWalletStats extends InAppWalletStats {}
export interface UserOpStats {
date: string;
successful: number;
failed: number;
sponsoredUsd: number;
chainId?: string;
}
interface AnalyticsQueryParams {
clientId?: string;
accountId?: string;
from?: Date;
to?: Date;
period?: "day" | "week" | "month" | "year" | "all";
}
function buildSearchParams(params: AnalyticsQueryParams): URLSearchParams {
const searchParams = new URLSearchParams();
if (params.clientId) {
searchParams.append("clientId", params.clientId);
}
if (params.accountId) {
searchParams.append("accountId", params.accountId);
}
if (params.from) {
searchParams.append("from", params.from.toISOString());
}
if (params.to) {
searchParams.append("to", params.to.toISOString());
}
if (params.period) {
searchParams.append("period", params.period);
}
return searchParams;
}
export async function getWalletConnections(
params: AnalyticsQueryParams,
): Promise<WalletStats[]> {
const searchParams = buildSearchParams(params);
const res = await fetchAnalytics(`v1/wallets?${searchParams.toString()}`, {
method: "GET",
headers: { "Content-Type": "application/json" },
});
if (res?.status !== 200) {
console.error("Failed to fetch wallet connections");
return [];
}
const json = await res.json();
return json.data as WalletStats[];
}
export async function getInAppWalletUsage(
params: AnalyticsQueryParams,
): Promise<InAppWalletStats[]> {
const searchParams = buildSearchParams(params);
const res = await fetchAnalytics(
`v1/wallets/in-app?${searchParams.toString()}`,
{
method: "GET",
headers: { "Content-Type": "application/json" },
},
);
if (res?.status !== 200) {
console.error("Failed to fetch in-app wallet usage");
return [];
}
const json = await res.json();
return json.data as InAppWalletStats[];
}
export async function getUserOpUsage(
params: AnalyticsQueryParams,
): Promise<UserOpStats[]> {
const searchParams = buildSearchParams(params);
const res = await fetchAnalytics(`v1/user-ops?${searchParams.toString()}`, {
method: "GET",
headers: { "Content-Type": "application/json" },
});
if (res?.status !== 200) {
console.error("Failed to fetch user ops usage");
return [];
}
const json = await res.json();
return json.data as UserOpStats[];
}
export async function getWalletUsers(
params: AnalyticsQueryParams,
): Promise<WalletUserStats[]> {
const searchParams = buildSearchParams(params);
const res = await fetchAnalytics(
`v1/wallets/users?${searchParams.toString()}`,
{
method: "GET",
headers: { "Content-Type": "application/json" },
},
);
if (res?.status !== 200) {
console.error("Failed to fetch wallet user stats");
return [];
}
const json = await res.json();
return json.data as WalletUserStats[];
}
@@ -0,0 +1,62 @@
import { getAuthToken } from "../../app/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";
import type { ProductSKU } from "../lib/billing";
type InvoiceLine = {
// amount for this line item
amount: number;
// statement descriptor
description: string | null;
// the thirdweb product sku or null if it is not recognized
thirdwebSku: ProductSKU | null;
};
type Invoice = {
// total amount excluding tax
amount: number | null;
// the ISO currency code (e.g. USD)
currency: string;
// the line items on the invoice
lines: InvoiceLine[];
};
export type TeamSubscription = {
id: string;
type: "PLAN" | "USAGE" | "PLAN_ADD_ON" | "PRODUCT";
status:
| "incomplete"
| "incomplete_expired"
| "trialing"
| "active"
| "past_due"
| "canceled"
| "unpaid"
| "paused";
currentPeriodStart: string;
currentPeriodEnd: string;
trialStart: string | null;
trialEnd: string | null;
upcomingInvoice: Invoice;
};
export async function getTeamSubscriptions(slug: string) {
const token = await getAuthToken();
if (!token) {
return null;
}
const teamRes = await fetch(
`${API_SERVER_URL}/v1/teams/${slug}/subscriptions`,
{
headers: {
Authorization: `Bearer ${token}`,
},
},
);
if (teamRes.ok) {
return (await teamRes.json())?.result as TeamSubscription[];
}
return null;
}
+37 -23
View File
@@ -1,7 +1,6 @@
import "server-only";
import { COOKIE_ACTIVE_ACCOUNT, COOKIE_PREFIX_TOKEN } from "@/constants/cookie";
import { API_SERVER_URL } from "@/constants/env";
import { cookies } from "next/headers";
import { getAuthToken } from "../../app/api/lib/getAuthToken";
export type Team = {
id: string;
@@ -12,22 +11,14 @@ export type Team = {
deletedAt?: string;
bannedAt?: string;
image?: string;
billingPlan: "pro" | "growth" | "free";
billingStatus: "validPayment" | (string & {}); // what's the other value?
billingEmail: string;
// billingExternalId: string;
// billingType: "STRIPE" | ??
// billingCustomerPayload: ?? | null
// paymentAttemptCount: number
// trialPeriodEndedAt: string | null;
billingPlan: "pro" | "growth" | "free" | "starter";
billingStatus: "validPayment" | (string & {}) | null;
billingEmail: string | null;
growthTrialEligible: boolean | null;
};
export async function getTeamBySlug(slug: string) {
const cookiesManager = await cookies();
const activeAccount = cookiesManager.get(COOKIE_ACTIVE_ACCOUNT)?.value;
const token = activeAccount
? cookiesManager.get(COOKIE_PREFIX_TOKEN + activeAccount)?.value
: null;
const token = await getAuthToken();
if (!token) {
return null;
@@ -45,14 +36,9 @@ export async function getTeamBySlug(slug: string) {
}
export async function getTeams() {
const cookiesManager = await cookies();
const activeAccount = cookiesManager.get(COOKIE_ACTIVE_ACCOUNT)?.value;
const token = activeAccount
? cookiesManager.get(COOKIE_PREFIX_TOKEN + activeAccount)?.value
: null;
const token = await getAuthToken();
if (!token) {
return [];
return null;
}
const teamsRes = await fetch(`${API_SERVER_URL}/v1/teams`, {
@@ -63,5 +49,33 @@ export async function getTeams() {
if (teamsRes.ok) {
return (await teamsRes.json())?.result as Team[];
}
return [];
return null;
}
type TeamNebulWaitList = {
onWaitlist: boolean;
createdAt: null | string;
};
export async function getTeamNebulaWaitList(teamSlug: string) {
const token = await getAuthToken();
if (!token) {
return null;
}
const res = await fetch(
`${API_SERVER_URL}/v1/teams/${teamSlug}/waitlist?scope=nebula`,
{
headers: {
Authorization: `Bearer ${token}`,
},
},
);
if (res.ok) {
return (await res.json()).result as TeamNebulWaitList;
}
return null;
}
@@ -1,15 +0,0 @@
"use client";
import { useEffect } from "react";
import { setCookie } from "../../stores/SyncStoreToCookies";
export function DashboardTypeCookieSetter(props: {
type: "team" | "old";
}) {
// eslint-disable-next-line no-restricted-syntax
useEffect(() => {
setCookie("x-dashboard-type", props.type);
}, [props.type]);
return null;
}
@@ -0,0 +1,61 @@
"use client";
import {
type BillingPortalOptions,
type RedirectCheckoutOptions,
redirectToBillingPortal,
redirectToCheckout,
} from "../actions/billing";
import { Button, type ButtonProps } from "./ui/button";
type CheckoutButtonProps = RedirectCheckoutOptions & ButtonProps;
export function CheckoutButton({
onClick,
teamSlug,
sku,
metadata,
redirectPath,
children,
...restProps
}: CheckoutButtonProps) {
return (
<Button
{...restProps}
onClick={async (e) => {
onClick?.(e);
await redirectToCheckout({
teamSlug,
sku,
metadata,
redirectPath,
});
}}
>
{children}
</Button>
);
}
type BillingPortalButtonProps = BillingPortalOptions & ButtonProps;
export function BillingPortalButton({
onClick,
teamSlug,
redirectPath,
children,
...restProps
}: BillingPortalButtonProps) {
return (
<Button
{...restProps}
onClick={async (e) => {
onClick?.(e);
await redirectToBillingPortal({
teamSlug,
redirectPath,
});
}}
>
{children}
</Button>
);
}
@@ -1,6 +1,7 @@
import type { Meta, StoryObj } from "@storybook/react";
import { useState } from "react";
import { BadgeContainer } from "../../../../stories/utils";
import { getThirdwebClient } from "../../../constants/thirdweb.server";
import { Button } from "../../ui/button";
import { GradientAvatar } from "./GradientAvatar";
@@ -17,29 +18,74 @@ export const Desktop: Story = {
args: {},
};
const client = getThirdwebClient();
function Story() {
return (
<div className="flex flex-col gap-10 p-10">
<p> All images below are set with size-6 className </p>
<BadgeContainer label="No Src, No id - Skeleton">
<GradientAvatar id={undefined} src={undefined} className="size-6" />
<GradientAvatar
id={undefined}
src={undefined}
className="size-6"
client={client}
/>
</BadgeContainer>
<BadgeContainer label="No Src, id=foo - Skeleton">
<GradientAvatar id={"foo"} src={undefined} className="size-6" />
<GradientAvatar
id={"foo"}
src={undefined}
className="size-6"
client={client}
/>
</BadgeContainer>
<BadgeContainer label="Invalid/Empty Src, id=foo - Gradient">
<GradientAvatar id={"foo"} src={""} className="size-6" />
<BadgeContainer label="Invalid Src, id=foo - Gradient">
<GradientAvatar
id={"foo"}
src={""}
className="size-6"
client={client}
/>
</BadgeContainer>
<BadgeContainer label="Invalid/Empty Src, id=bar - Gradient">
<GradientAvatar id={"bar"} src={""} className="size-6" />
<BadgeContainer label="Invalid Src, id=bar - Gradient">
<GradientAvatar
id={"bar"}
src={""}
className="size-6"
client={client}
/>
</BadgeContainer>
<BadgeContainer label="Empty/Invalid Src, No id - default fallback">
<GradientAvatar src="invalid-src" id={undefined} className="size-6" />
<GradientAvatar
src="invalid-src"
id={undefined}
className="size-6"
client={client}
/>
</BadgeContainer>
<BadgeContainer label="Valid Src, https">
<GradientAvatar
src="https://picsum.photos/200/300"
id={undefined}
className="size-6"
client={client}
/>
</BadgeContainer>
<BadgeContainer label="Valid Src, ipfs">
<GradientAvatar
src="ipfs://QmZbeJYEs7kCJHyQxjxU2SJUtjSAr4m87wzJFJUyWomKdj/Smily.svg"
id={undefined}
className="size-6"
client={client}
/>
</BadgeContainer>
<ToggleTest />
@@ -74,7 +120,12 @@ function ToggleTest() {
<p> Src+ID is: {data ? "set" : "not set"} </p>
<BadgeContainer label="Valid Src">
<GradientAvatar src={data?.src} id={data?.id} className="size-6" />
<GradientAvatar
src={data?.src}
id={data?.id}
className="size-6"
client={client}
/>
</BadgeContainer>
<BadgeContainer label="invalid Src">
@@ -82,6 +133,7 @@ function ToggleTest() {
className="size-6"
src={data ? "invalid-src" : undefined}
id={undefined}
client={client}
/>
</BadgeContainer>
</div>
@@ -1,5 +1,7 @@
import { Img } from "@/components/blocks/Img";
import { useMemo } from "react";
import type { ThirdwebClient } from "thirdweb";
import { resolveSchemeWithErrorHandler } from "../../../lib/resolveSchemeWithErrorHandler";
import { cn } from "../../../lib/utils";
const gradients = [
@@ -32,6 +34,7 @@ export function GradientAvatar(props: {
src: string | undefined;
id: string | undefined;
className: string;
client: ThirdwebClient;
}) {
const gradient = useMemo(() => {
if (!props.id) {
@@ -40,9 +43,16 @@ export function GradientAvatar(props: {
return getGradientForString(props.id);
}, [props.id]);
const resolvedSrc = props.src
? resolveSchemeWithErrorHandler({
client: props.client,
uri: props.src,
})
: props.src;
return (
<Img
src={props.src}
src={resolvedSrc}
className={cn("rounded-full", props.className)}
fallback={
gradient ? (
@@ -3,7 +3,7 @@ import { BadgeContainer, mobileViewport } from "../../../stories/utils";
import { DangerSettingCard } from "./DangerSettingCard";
const meta = {
title: "blocks/DangerSettingCard",
title: "blocks/Cards/DangerSettingCard",
component: Story,
parameters: {
nextjs: {
@@ -3,7 +3,7 @@ import { BadgeContainer, mobileViewport } from "../../../stories/utils";
import { SettingsCard } from "./SettingsCard";
const meta = {
title: "blocks/SettingsCard",
title: "Blocks/Cards/SettingsCard",
component: Story,
parameters: {
nextjs: {
@@ -51,7 +51,8 @@ export function RenderSidebarLinks(props: { links: SidebarLink[] }) {
{props.links.map((link, i) => {
if ("group" in link) {
return (
<div className={cn({ "mt-6": i !== 0 })}>
// biome-ignore lint/suspicious/noArrayIndexKey: items won't be reordered
<div className={cn({ "mt-6": i !== 0 })} key={i}>
<p className={cn("px-3 py-2 font-medium text-foreground")}>
{link.group}
</p>
@@ -63,7 +64,8 @@ export function RenderSidebarLinks(props: { links: SidebarLink[] }) {
const isExternal = link.href.startsWith("http");
return (
<NavLink
key={link.href}
// biome-ignore lint/suspicious/noArrayIndexKey: items won't be reordered
key={i}
href={link.href}
className="flex items-center gap-2 rounded-md px-3 py-2 text-muted-foreground text-sm hover:bg-muted"
activeClassName="text-foreground"
@@ -19,7 +19,7 @@ export function AppFooter(props: AppFooterProps) {
return (
<footer
className={cn(
"w-full border-border border-t bg-white py-6 md:py-8 dark:bg-muted/50",
"w-full border-border border-t bg-muted/50 py-6 md:py-8",
props.className,
)}
>
@@ -0,0 +1,131 @@
"use client";
import { CodeClient } from "@/components/ui/code/code.client";
import type React from "react";
import { type Dispatch, type SetStateAction, useMemo } from "react";
import { TabButtons } from "../ui/tabs";
export type CodeEnvironment =
| "javascript"
| "typescript"
| "react"
| "react-native"
| "unity";
type SupportedEnvironment = {
environment: CodeEnvironment;
title: string;
};
type CodeSnippet = Partial<Record<CodeEnvironment, string>>;
const Environments: SupportedEnvironment[] = [
{
environment: "javascript",
title: "JavaScript",
},
{
environment: "typescript",
title: "TypeScript",
},
{
environment: "react",
title: "React",
},
{
environment: "react-native",
title: "React Native",
},
{
environment: "unity",
title: "Unity",
},
];
interface CodeSegmentProps {
snippet: CodeSnippet;
environment: CodeEnvironment;
setEnvironment:
| Dispatch<SetStateAction<CodeEnvironment>>
| ((language: CodeEnvironment) => void);
isInstallCommand?: boolean;
hideTabs?: boolean;
onlyTabs?: boolean;
}
export const CodeSegment: React.FC<CodeSegmentProps> = ({
snippet,
environment,
setEnvironment,
isInstallCommand,
hideTabs,
onlyTabs,
}) => {
const activeEnvironment: CodeEnvironment = useMemo(() => {
return (
snippet[environment] ? environment : Object.keys(snippet)[0]
) as CodeEnvironment;
}, [environment, snippet]);
const activeSnippet = useMemo(() => {
return snippet[activeEnvironment];
}, [activeEnvironment, snippet]);
const lines = useMemo(
() => (activeSnippet ? activeSnippet.split("\n") : []),
[activeSnippet],
);
const code = lines.join("\n").trim();
const environments = Environments.filter(
(env) =>
Object.keys(snippet).includes(env.environment) &&
snippet[env.environment],
);
return (
<div
className={
"flex flex-col overflow-hidden rounded-lg border border-border"
}
>
{!hideTabs && (
<TabButtons
tabs={environments.map((env) => ({
label: env.title,
onClick: () => setEnvironment(env.environment),
isActive: activeEnvironment === env.environment,
name: env.title,
isEnabled: true,
}))}
tabClassName="text-sm gap-2 !text-sm"
tabIconClassName="size-4"
tabContainerClassName="px-3 pt-1.5 gap-0.5"
hideBottomLine={!!onlyTabs}
/>
)}
{onlyTabs ? null : (
<>
<CodeClient
code={code}
loadingClassName="min-h-[450px] rounded-none border-none"
className="rounded-none border-none"
lang={
isInstallCommand
? activeEnvironment === "react-native"
? "tsx"
: "bash"
: activeEnvironment === "react" ||
activeEnvironment === "react-native"
? "tsx"
: activeEnvironment === "unity"
? "cpp"
: activeEnvironment
}
/>
</>
)}
</div>
);
};
@@ -0,0 +1,174 @@
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import type { Meta, StoryObj } from "@storybook/react";
import { useState } from "react";
import { BadgeContainer, mobileViewport } from "../../../stories/utils";
import { type CodeEnvironment, CodeSegment } from "./code-segment.client";
const meta = {
title: "blocks/CodeSegment",
component: Story,
parameters: {
nextjs: {
appDirectory: true,
},
},
} satisfies Meta<typeof Story>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
args: {},
};
export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};
type Mode = "default" | "no-tabs" | "only-tabs";
function Story() {
const [mode, setMode] = useState<Mode>("default");
const modes: Mode[] = ["default", "no-tabs", "only-tabs"];
return (
<div className="container flex max-w-[700px] flex-col gap-10 py-10">
<Select
value={mode}
onValueChange={(v) => {
setMode(v as Mode);
}}
>
<SelectTrigger className="w-[180px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{modes.map((item) => {
return (
<SelectItem key={item} value={item}>
{item}
</SelectItem>
);
})}
</SelectGroup>
</SelectContent>
</Select>
<Variant envsToShow={["javascript"]} storyLabel="1 env" mode={mode} />
<Variant
envsToShow={["javascript", "typescript"]}
storyLabel="2 envs"
mode={mode}
/>
<Variant
envsToShow={[
"javascript",
"typescript",
"react",
"react-native",
"unity",
]}
storyLabel="5 envs"
mode={mode}
/>
</div>
);
}
function Variant(props: {
envsToShow: CodeEnvironment[];
storyLabel: string;
mode: Mode;
}) {
const [env, setEnv] = useState<CodeEnvironment>(
props.envsToShow[0] || "javascript",
);
return (
<BadgeContainer label={props.storyLabel}>
<CodeSegment
snippet={{
javascript: props.envsToShow.includes("javascript")
? jsCode
: undefined,
react: props.envsToShow.includes("react") ? jsxCode : undefined,
unity: props.envsToShow.includes("unity") ? unityCode : undefined,
"react-native": props.envsToShow.includes("react-native")
? jsxCode
: undefined,
typescript: props.envsToShow.includes("typescript")
? tsCode
: undefined,
}}
environment={env}
setEnvironment={setEnv}
hideTabs={props.mode === "no-tabs"}
onlyTabs={props.mode === "only-tabs"}
/>
</BadgeContainer>
);
}
const jsCode = `\
import { getContract } from "thirdweb";
import { sepolia } from "thirdweb/chains";
import { getOwnedNFTs } from "thirdweb/extensions/erc1155";
const contract = getContract({
client,
address: "0x1234...",
chain: sepolia,
});
`;
const jsxCode = `\
import { ThirdwebProvider } from "thirdweb/react";
function Main() {
return (
<ThirdwebProvider>
<App />
</ThirdwebProvider>
);
}`;
const unityCode = `\
using Thirdweb;
// Reference the SDK
var sdk = ThirdwebManager.Instance.SDK;
// Create data
NFTMetadata meta = new NFTMetadata()
{
name = "Unity NFT",
description = "Minted From Unity",
image = "ipfs://QmbpciV7R5SSPb6aT9kEBAxoYoXBUsStJkMpxzymV4ZcVc",
};
string metaJson = Newtonsoft.Json.JsonConvert.SerializeObject(meta);
// Upload raw text or from a file path
var response = await ThirdwebManager.Instance.SDK.storage.UploadText(metaJson);`;
const tsCode = `\
type User = {
name: string;
age: number;
}
function logUser(user: User) {
console.log(user)
}
`;
@@ -1,46 +0,0 @@
import { Highlight } from "prism-react-renderer";
import { cn } from "../../lib/utils";
import { CopyButton } from "./CopyButton";
import { Card } from "./card";
interface CodeBlockProps {
code: string;
language?: string;
className?: string;
canCopy?: boolean;
}
export const CodeBlock = ({
code,
language = "sh",
canCopy = true,
}: CodeBlockProps) => {
return (
<Highlight code={code} language={language}>
{({ className, tokens, getLineProps, getTokenProps }) => (
<Card className={className}>
{canCopy && (
<CopyButton text={code} className="float-right m-2 p-2" />
)}
<div
className={cn(
"block overflow-auto whitespace-pre-wrap p-4 font-mono text-sm",
className,
)}
>
{tokens.map((line, i) => (
// biome-ignore lint/suspicious/noArrayIndexKey: index IS the key here
<div {...getLineProps({ line, key: i })} key={i}>
{line.map((token, key) => (
// biome-ignore lint/suspicious/noArrayIndexKey: index IS the key here
<span {...getTokenProps({ token, key })} key={key} />
))}
</div>
))}
</div>
</Card>
)}
</Highlight>
);
};
@@ -50,7 +50,7 @@ export function DatePickerWithRange(props: {
id="date"
variant="outline"
className={cn(
"justify-start gap-2 bg-card text-left font-normal",
"justify-start gap-2 text-left font-normal",
props.className,
)}
>
@@ -1,36 +0,0 @@
"use client";
import { useClipboard } from "hooks/useClipboard";
import { CheckIcon, CopyIcon } from "lucide-react";
import { cn } from "../../lib/utils";
import { Button } from "./button";
export function PlainTextCodeBlock(props: {
code: string;
copyButtonClassName?: string;
}) {
const { hasCopied, onCopy } = useClipboard(props.code);
return (
<div className="relative">
<code className="block whitespace-pre rounded-lg border border-border bg-muted/50 p-4">
{props.code}
</code>
<Button
size="sm"
variant="outline"
onClick={onCopy}
className={cn(
"absolute top-3.5 right-3.5 h-auto p-2",
props.copyButtonClassName,
)}
>
{hasCopied ? (
<CheckIcon className="size-3 text-green-500" />
) : (
<CopyIcon className="size-3 text-muted-foreground" />
)}
</Button>
</div>
);
}
@@ -0,0 +1,70 @@
import { ScrollShadow } from "@/components/ui/ScrollShadow/ScrollShadow";
import { Button } from "@/components/ui/button";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { CheckIcon, CopyIcon } from "lucide-react";
import { useState } from "react";
export function RenderCode(props: {
code: string;
html: string;
className?: string;
scrollableClassName?: string;
}) {
return (
<div
className={cn(
"group relative max-w-full overflow-hidden rounded-lg border border-border bg-background",
props.className,
)}
>
<ScrollShadow
scrollableClassName={cn("p-4", props.scrollableClassName)}
className="text-xs md:text-sm [&_*]:leading-relaxed"
shadowColor="hsl(var(--muted))"
>
<div
// biome-ignore lint/security/noDangerouslySetInnerHtml: we know what we're doing here
dangerouslySetInnerHTML={{ __html: props.html }}
/>
</ScrollShadow>
<CopyButton
text={props.code}
iconClassName="size-3"
className="absolute top-4 right-4 z-10 border border-border bg-background p-2 opacity-0 transition-opacity duration-300 group-hover:opacity-100"
/>
</div>
);
}
function CopyButton(props: {
text: string;
className?: string;
iconClassName?: string;
}) {
const [isCopied, setIsCopied] = useState(false);
return (
<ToolTipLabel label="Copy">
<Button
variant="ghost"
aria-label="Copy"
className={cn("h-auto w-auto p-1", props.className)}
onClick={() => {
navigator.clipboard.writeText(props.text);
setIsCopied(true);
setTimeout(() => setIsCopied(false), 1000);
}}
>
{isCopied ? (
<CheckIcon
className={cn("size-4 text-green-500", props.iconClassName)}
/>
) : (
<CopyIcon
className={cn("size-4 text-muted-foreground", props.iconClassName)}
/>
)}
</Button>
</ToolTipLabel>
);
}
@@ -0,0 +1,57 @@
"use client";
import { cn } from "@/lib/utils";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import type { BundledLanguage } from "shiki";
import { Spinner } from "../../ui/Spinner/Spinner";
import { RenderCode } from "./RenderCode";
import { getCodeHtml } from "./getCodeHtml";
export type CodeProps = {
code: string;
lang: BundledLanguage;
className?: string;
scrollableClassName?: string;
loadingClassName?: string;
keepPreviousDataOnCodeChange?: boolean;
};
export const CodeClient: React.FC<CodeProps> = ({
code,
lang,
className,
scrollableClassName,
loadingClassName,
keepPreviousDataOnCodeChange = false,
}) => {
const codeQuery = useQuery({
queryKey: ["html", code],
queryFn: () => getCodeHtml(code, lang),
placeholderData: keepPreviousDataOnCodeChange
? keepPreviousData
: undefined,
retry: false,
});
if (!codeQuery.data) {
return (
<div
className={cn(
"flex min-h-[200px] items-center justify-center rounded-lg border border-border",
loadingClassName,
)}
>
<Spinner className="size-8" />
</div>
);
}
return (
<RenderCode
code={codeQuery.data.formattedCode}
html={codeQuery.data.html}
className={className}
scrollableClassName={scrollableClassName}
/>
);
};
@@ -0,0 +1,18 @@
import type { BundledLanguage } from "shiki";
import { RenderCode } from "./RenderCode";
import { getCodeHtml } from "./getCodeHtml";
export type CodeProps = {
code: string;
lang: BundledLanguage;
className?: string;
};
export const CodeServer: React.FC<CodeProps> = async ({
code,
lang,
className,
}) => {
const { html, formattedCode } = await getCodeHtml(code, lang);
return <RenderCode code={formattedCode} html={html} className={className} />;
};
@@ -0,0 +1,115 @@
import type { Meta, StoryObj } from "@storybook/react";
import { BadgeContainer, mobileViewport } from "stories/utils";
import { CodeClient } from "./code.client";
const meta = {
title: "code/lang",
component: Component,
parameters: {},
} satisfies Meta<typeof Component>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
args: {},
};
export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};
const tsCode = `\
type User = {
name: string;
age: number;
}
function logUser(user: User) {
console.log(user)
}
`;
const jsCode = `\
import { getContract } from "thirdweb";
import { sepolia } from "thirdweb/chains";
import { getOwnedNFTs } from "thirdweb/extensions/erc1155";
const contract = getContract({
client,
address: "0x1234...",
chain: sepolia,
});
`;
const jsxCode = `\
import { ThirdwebProvider } from "thirdweb/react";
function Main() {
return (
<ThirdwebProvider>
<App />
</ThirdwebProvider>
);
}`;
const tsxCode = `\
type User = {
name: string;
age: number;
}
function UserInfo(props: { user: User }) {
return <div> {props.user.name} </div>
}
`;
const tsxCodeWithFormError = `\
// This piece of code has invalid syntax and can't be formatted by prettier
// this should not crash the page - and be rendered as is without formatting
// the format error is logged in console
type User = // missing { here
name: string;
age: number;
}
function UserInfo(props: { user: User }) {
return <div> {props.user.name} </div>
}
`;
const bashCode = "pnpm i thirdweb";
function Component() {
return (
<div className="container flex max-w-[600px] flex-col gap-10 py-10">
<BadgeContainer label="ts">
<CodeClient code={tsCode} lang="ts" />
</BadgeContainer>
<BadgeContainer label="js">
<CodeClient code={jsCode} lang="js" />
</BadgeContainer>
<BadgeContainer label="bash">
<CodeClient code={bashCode} lang="bash" />
</BadgeContainer>
<BadgeContainer label="jsx">
<CodeClient code={jsxCode} lang="jsx" />
</BadgeContainer>
<BadgeContainer label="tsx">
<CodeClient code={tsxCode} lang="tsx" />
</BadgeContainer>
<BadgeContainer label="tsx">
<CodeClient code={tsxCodeWithFormError} lang="tsx" />
</BadgeContainer>
</div>
);
}
@@ -0,0 +1,45 @@
import * as parserBabel from "prettier/plugins/babel";
import * as estree from "prettier/plugins/estree";
import { format } from "prettier/standalone";
import { type BundledLanguage, codeToHtml } from "shiki";
function isPrettierSupportedLang(lang: BundledLanguage) {
return (
lang === "js" ||
lang === "jsx" ||
lang === "ts" ||
lang === "tsx" ||
lang === "javascript" ||
lang === "typescript" ||
lang === "css" ||
lang === "json"
);
}
export async function getCodeHtml(code: string, lang: BundledLanguage) {
const formattedCode = isPrettierSupportedLang(lang)
? await format(code, {
parser: "babel-ts",
plugins: [parserBabel, estree],
printWidth: 60,
}).catch((e) => {
console.error(e);
console.error("Failed to format code");
console.log({
code,
lang,
});
return code;
})
: code;
const html = await codeToHtml(formattedCode, {
lang: lang,
themes: {
light: "github-light",
dark: "github-dark-default",
},
});
return { html, formattedCode };
}
@@ -0,0 +1,52 @@
import type { Meta, StoryObj } from "@storybook/react";
import { BadgeContainer, mobileViewport } from "stories/utils";
import { PlainTextCodeBlock } from "./plaintext-code";
const meta = {
title: "code/plaintext",
component: Component,
parameters: {},
} satisfies Meta<typeof Component>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
args: {},
};
export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};
const jsCode = `\
import { getContract } from "thirdweb";
import { sepolia } from "thirdweb/chains";
import { getOwnedNFTs } from "thirdweb/extensions/erc1155";
const contract = getContract({
client,
address: "0x1234...",
chain: sepolia,
});
`;
const overflowText =
"123456789abcdefghijklmnopqrstuvwxyz123456789abcdefghijklmnopqrstuvwxyz123456789abcdefghijklmnopqrstuvwxyz123456789abcdefghijklmnopqrstuvwxyz123456789abcdefghijklmnopqrstuvwxyz123456789abcdefghijklmnopqrstuvwxyz";
function Component() {
return (
<div className="container flex max-w-[600px] flex-col gap-10 py-10">
<BadgeContainer label="large chunk of code">
<PlainTextCodeBlock code={jsCode} />
</BadgeContainer>
<BadgeContainer label="overflow test">
<PlainTextCodeBlock code={overflowText} />
</BadgeContainer>
</div>
);
}
@@ -0,0 +1,52 @@
"use client";
import { cn } from "@/lib/utils";
import { useClipboard } from "hooks/useClipboard";
import { CheckIcon, CopyIcon } from "lucide-react";
import { ScrollShadow } from "../ScrollShadow/ScrollShadow";
import { Button } from "../button";
export function PlainTextCodeBlock(props: {
code: string;
copyButtonClassName?: string;
className?: string;
scrollableClassName?: string;
codeClassName?: string;
}) {
const { hasCopied, onCopy } = useClipboard(props.code);
return (
<div
className={cn(
"group relative max-w-full overflow-hidden rounded-lg border border-border bg-background",
props.className,
)}
>
<ScrollShadow
scrollableClassName={cn("p-4", props.scrollableClassName)}
className="text-xs md:text-sm [&_*]:leading-relaxed"
shadowColor="hsl(var(--muted))"
>
<code className={cn("block whitespace-pre", props.codeClassName)}>
{props.code}
</code>
</ScrollShadow>
<Button
size="sm"
variant="outline"
onClick={onCopy}
className={cn(
"absolute top-3.5 right-3.5 h-auto bg-background p-2",
props.copyButtonClassName,
)}
>
{hasCopied ? (
<CheckIcon className="size-3 text-green-500" />
) : (
<CopyIcon className="size-3 text-muted-foreground" />
)}
</Button>
</div>
);
}
+34 -31
View File
@@ -35,40 +35,43 @@ const DialogContent = React.forwardRef<
dialogOverlayClassName?: string;
dialogCloseClassName?: string;
}
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay className={props.dialogOverlayClassName} />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed grid w-full gap-4 rounded-t-xl border border-border bg-background p-6 shadow-lg duration-300 md:max-w-lg md:rounded-lg",
// on mobile - put the dialog at the bottom of the screen, animate - slide up and fade in
"right-0 bottom-0 left-0",
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
// on desktop - vertically and horizontally center the dialog, animate - zoom and fade in
"md:top-[50%] md:bottom-auto md:left-[50%] md:translate-x-[-50%] md:translate-y-[-50%] ",
"md:data-[state=closed]:zoom-out-95 md:data-[state=open]:zoom-in-95 ",
"md:data-[state=closed]:slide-out-to-left-1/2 md:data-[state=closed]:slide-out-to-top-[48%] ",
"md:data-[state=open]:slide-in-from-left-1/2 md:data-[state=open]:slide-in-from-top-[48%] ",
"data-[state=closed]:animate-out data-[state=open]:animate-in",
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className,
)}
{...props}
>
{children}
<DialogPrimitive.Close
>(({ className, children, ...props }, ref) => {
const { dialogOverlayClassName, dialogCloseClassName, ...restProps } = props;
return (
<DialogPortal>
<DialogOverlay className={dialogOverlayClassName} />
<DialogPrimitive.Content
ref={ref}
className={cn(
"absolute top-4 right-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground",
props.dialogCloseClassName,
"fixed grid w-full gap-4 rounded-t-xl border border-border bg-background p-6 shadow-lg duration-300 md:max-w-lg md:rounded-lg",
// on mobile - put the dialog at the bottom of the screen, animate - slide up and fade in
"right-0 bottom-0 left-0",
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
// on desktop - vertically and horizontally center the dialog, animate - zoom and fade in
"md:top-[50%] md:bottom-auto md:left-[50%] md:translate-x-[-50%] md:translate-y-[-50%] ",
"md:data-[state=closed]:zoom-out-95 md:data-[state=open]:zoom-in-95 ",
"md:data-[state=closed]:slide-out-to-left-1/2 md:data-[state=closed]:slide-out-to-top-[48%] ",
"md:data-[state=open]:slide-in-from-left-1/2 md:data-[state=open]:slide-in-from-top-[48%] ",
"data-[state=closed]:animate-out data-[state=open]:animate-in",
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className,
)}
{...restProps}
>
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
));
{children}
<DialogPrimitive.Close
className={cn(
"absolute top-4 right-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground",
dialogCloseClassName,
)}
>
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
);
});
DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({
@@ -11,7 +11,7 @@ import type { Meta, StoryObj } from "@storybook/react";
import { BadgeContainer } from "../../../stories/utils";
const meta = {
title: "Shadcn/select",
title: "Shadcn/Select",
component: Component,
parameters: {
layout: "centered",
@@ -9,11 +9,13 @@ import {
TableRow,
} from "@/components/ui/table";
import type { Meta, StoryObj } from "@storybook/react";
import Link from "next/link";
import { BadgeContainer, mobileViewport } from "../../../stories/utils";
import { cn } from "../../lib/utils";
import { Badge } from "./badge";
const meta = {
title: "Shadcn/table",
title: "Shadcn/Table",
component: Component,
parameters: {
layout: "centered",
@@ -41,6 +43,10 @@ function Component() {
<TableDemo />
</BadgeContainer>
<BadgeContainer label="Clickable Row">
<TableDemo linkBox />
</BadgeContainer>
<BadgeContainer label="With Footer">
<TableDemo footer />
</BadgeContainer>
@@ -90,6 +96,7 @@ const invoices: Invoice[] = [
function TableDemo(props: {
footer?: boolean;
linkBox?: boolean;
}) {
return (
<TableContainer>
@@ -104,8 +111,21 @@ function TableDemo(props: {
</TableHeader>
<TableBody>
{invoices.map((invoice) => (
<TableRow key={invoice.invoice}>
<TableCell className="font-medium">{invoice.invoice}</TableCell>
<TableRow
key={invoice.invoice}
linkBox={props.linkBox}
className={cn(props.linkBox && "cursor-pointer hover:bg-muted")}
>
<TableCell className="font-medium">
<Link
href={`/invoices/${invoice.invoice}`}
className={cn(
props.linkBox && "before:absolute before:inset-0",
)}
>
{invoice.invoice}
</Link>
</TableCell>
<TableCell>
<Badge>{invoice.paymentStatus}</Badge>
</TableCell>
+9 -2
View File
@@ -63,12 +63,19 @@ TableFooter.displayName = "TableFooter";
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
React.HTMLAttributes<HTMLTableRowElement> & {
/**
* Contain the absolutely position elements inside the row with position:relative + transform:translate(0)
* transform:translate(0) is required because position:relative on tr element does not work on webkit
*/
linkBox?: boolean;
}
>(({ className, linkBox, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-border border-b last:border-0 data-[state=selected]:bg-muted",
linkBox && "relative translate-x-0 translate-y-0",
className,
)}
{...props}
+9 -2
View File
@@ -87,6 +87,8 @@ export function TabButtons(props: {
tabContainerClassName?: string;
containerClassName?: string;
shadowColor?: string;
tabIconClassName?: string;
hideBottomLine?: boolean;
}) {
const { containerRef, lineRef, activeTabRef } =
useUnderline<HTMLButtonElement>();
@@ -94,7 +96,9 @@ export function TabButtons(props: {
return (
<div className={cn("relative", props.containerClassName)}>
{/* Bottom line */}
<div className="absolute right-0 bottom-0 left-0 h-[1px] bg-border" />
{!props.hideBottomLine && (
<div className="absolute right-0 bottom-0 left-0 h-[1px] bg-border" />
)}
<ScrollShadow
scrollableClassName="pb-[8px] relative"
@@ -120,7 +124,9 @@ export function TabButtons(props: {
)}
onClick={tab.isEnabled ? tab.onClick : undefined}
>
{tab.icon && <tab.icon className="size-6" />}
{tab.icon && (
<tab.icon className={cn("size-6", props.tabIconClassName)} />
)}
{tab.name}
</Button>
);
@@ -197,6 +203,7 @@ export function TabPathLinks(props: {
isActive: l.exactMatch
? pathname === l.path
: pathname.startsWith(l.path),
isDisabled: l.isDisabled,
}))}
/>
);
+2 -2
View File
@@ -2,8 +2,8 @@ export function isLoginRequired(pathname: string) {
// remove '/' in front and then split by '/'
const paths = pathname.slice(1).split("/");
// /dashboard, /team, /cli
if (paths[0] === "dashboard" || paths[0] === "team" || paths[0] === "cli") {
// /team, /cli
if (paths[0] === "team" || paths[0] === "cli") {
return true;
}
+13
View File
@@ -33,3 +33,16 @@ export const THIRDWEB_ENGINE_URL = process.env.THIRDWEB_ENGINE_URL;
export const THIRDWEB_ACCESS_TOKEN = process.env.THIRDWEB_ACCESS_TOKEN;
// Comma-separated list of chain IDs to disable faucet for.
export const DISABLE_FAUCET_CHAIN_IDS = process.env.DISABLE_FAUCET_CHAIN_IDS;
export function getAbsoluteUrlFromPath(path: string) {
const url = new URL(
isProd
? "https://thirdweb.com"
: (process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL
? `https://${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL}`
: "http://localhost:3000") || "https://thirdweb-dev.com",
);
url.pathname = path;
return url;
}
@@ -4,6 +4,7 @@ import {
IPFS_GATEWAY_URL,
} from "@/constants/env";
import {
THIRDWEB_BUNDLER_DOMAIN,
THIRDWEB_INAPP_WALLET_DOMAIN,
THIRDWEB_PAY_DOMAIN,
THIRDWEB_RPC_DOMAIN,
@@ -11,7 +12,13 @@ import {
THIRDWEB_STORAGE_DOMAIN,
} from "constants/urls";
import { createThirdwebClient } from "thirdweb";
import { setThirdwebDomains } from "thirdweb/utils";
import { populateEip712Transaction } from "thirdweb/transaction";
import {
getTransactionDecorator,
setThirdwebDomains,
setTransactionDecorator,
} from "thirdweb/utils";
import { getZkPaymasterData } from "thirdweb/wallets/smart";
import { getVercelEnv } from "../../lib/vercel-utils";
// returns a thirdweb client with optional JWT passed in
@@ -24,8 +31,43 @@ export function getThirdwebClient(jwt?: string) {
pay: THIRDWEB_PAY_DOMAIN,
storage: THIRDWEB_STORAGE_DOMAIN,
social: THIRDWEB_SOCIAL_API_DOMAIN,
bundler: THIRDWEB_BUNDLER_DOMAIN,
});
}
if (!getTransactionDecorator()) {
setTransactionDecorator(async ({ account, transaction }) => {
// special override for sophon testnet (zk chain)
// sophon only allows transactions through their paymaster
// so always use eip712 tx + paymaster
if (transaction.chain.id === 531050104) {
const serializedTx = await populateEip712Transaction({
transaction,
account,
});
const pmData = await getZkPaymasterData({
options: {
client: transaction.client,
chain: transaction.chain,
},
transaction: serializedTx,
});
return {
account,
transaction: {
...transaction,
eip712: {
...transaction.eip712,
paymaster: pmData.paymaster,
paymasterInput: pmData.paymasterInput,
},
},
};
}
return { account, transaction };
});
}
return createThirdwebClient({
secretKey: jwt ? jwt : DASHBOARD_THIRDWEB_SECRET_KEY,
clientId: DASHBOARD_THIRDWEB_CLIENT_ID,
+13
View File
@@ -0,0 +1,13 @@
// keep in line with product SKUs in the backend
export type ProductSKU =
| "plan:starter"
| "plan:growth"
| "plan:custom"
| "product:ecosystem_wallets"
| "product:engine_standard"
| "product:engine_premium"
| "usage:storage"
| "usage:in_app_wallet"
| "usage:aa_sponsorship"
| "usage:aa_sponsorship_op_grant"
| null;
+12 -2
View File
@@ -6,7 +6,7 @@
:root,
[data-theme="light"] {
/* bg - neutral */
--background: 0 0% 98%;
--background: 0 0% 100%;
--popover: 0 0% 100%;
--card: 0 0% 100%;
--secondary: 0 0% 92%;
@@ -46,7 +46,7 @@
[data-theme="dark"] {
/* bg - neutral */
--background: 0 0% 0%;
--card: 0 0% 5%;
--card: 0 0% 0%;
--popover: 0 0% 0%;
--secondary: 0 0% 10%;
--muted: 0 0% 9%;
@@ -156,3 +156,13 @@ input:-webkit-autofill:active {
/* Revert background color */
transition: background-color 5000s ease-in-out 0s;
}
.dark .shiki,
.dark .shiki span {
color: var(--shiki-dark) !important;
background-color: var(--background) !important;
/* Optional, if you also want font styles */
font-style: var(--shiki-dark-font-style) !important;
font-weight: var(--shiki-dark-font-weight) !important;
text-decoration: var(--shiki-dark-text-decoration) !important;
}
@@ -4,6 +4,7 @@ import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Button } from "@/components/ui/button";
import { useThirdwebClient } from "@/constants/thirdweb.client";
import { useStore } from "@/lib/reactive";
import { cn } from "@/lib/utils";
import { getSDKTheme } from "app/components/sdk-component-theme";
import { CustomChainRenderer } from "components/selects/CustomChainRenderer";
import { mapV4ChainToV5Chain } from "contexts/map-chains";
@@ -15,7 +16,6 @@ import { usePathname } from "next/navigation";
import { useCallback, useMemo, useState } from "react";
import type { Chain } from "thirdweb";
import {
AutoConnect,
ConnectButton,
type NetworkSelectorProps,
useConnectModal,
@@ -36,6 +36,8 @@ export const CustomConnectWallet = (props: {
connectButtonClassName?: string;
signInLinkButtonClassName?: string;
detailsButtonClassName?: string;
loadingButtonClassName?: string;
chain?: Chain;
}) => {
const thirdwebClient = useThirdwebClient();
const loginRequired =
@@ -123,11 +125,14 @@ export const CustomConnectWallet = (props: {
if (isPending) {
return (
<>
<div className="flex h-[48px] w-[144px] items-center justify-center rounded-lg border border-border bg-muted">
<div
className={cn(
"flex h-[48px] w-[144px] items-center justify-center rounded-lg border border-border bg-muted",
props.loadingButtonClassName,
)}
>
<Spinner className="size-4" />
</div>
{/* need auto connect here so that we actually connect */}
<AutoConnect client={thirdwebClient} />
</>
);
}
@@ -147,8 +152,6 @@ export const CustomConnectWallet = (props: {
Sign In
</Link>
</Button>
{/* need auto connect here so that we actually connect */}
<AutoConnect client={thirdwebClient} />
</>
);
}
@@ -209,6 +212,7 @@ export const CustomConnectWallet = (props: {
},
},
}}
chain={props.chain}
/>
<LazyConfigureNetworkModal
@@ -1,3 +1,5 @@
import type { UserOpStats } from "@/api/analytics";
import type { Team } from "@/api/team";
import {
type Query,
useMutation,
@@ -28,9 +30,6 @@ export const accountPlan = {
enterprise: "enterprise",
} as const;
export type AccountStatus = (typeof accountStatus)[keyof typeof accountStatus];
export type AccountPlan = (typeof accountPlan)[keyof typeof accountPlan];
export type AuthorizedWallet = {
id: string;
accountId: string;
@@ -46,30 +45,17 @@ export type Account = {
id: string;
isStaff: boolean;
creatorWalletAddress: string;
status: AccountStatus;
plan: AccountPlan;
name?: string;
email?: string;
advancedEnabled: boolean;
currentBillingPeriodStartsAt: string;
currentBillingPeriodEndsAt: string;
emailConfirmedAt?: string;
unconfirmedEmail?: string;
trialPeriodEndedAt?: string;
emailConfirmationWalletAddress?: string;
stripePaymentActionUrl?: string;
onboardSkipped?: boolean;
paymentAttemptCount?: number;
notificationPreferences?: {
billing: "email" | "none";
updates: "email" | "none";
};
recurringPaymentFailures: {
subscriptionId: string;
subscriptionDescription: string;
paymentFailureCode: string;
serviceCutoffDate: string;
}[];
// TODO - add image URL
};
@@ -193,33 +179,16 @@ export interface UpdateKeyInput {
redirectUrls: string[];
}
interface UsageBundler {
chainId: number;
sumTransactionFee: string;
}
interface UsageStorage {
sumFileSizeBytes: number;
}
interface UsageEmbeddedWallets {
countWalletAddresses: number;
}
export interface UsageBillableByService {
usage: {
bundler: UsageBundler[];
storage: UsageStorage;
embeddedWallets: UsageEmbeddedWallets;
};
billableUsd: {
bundler: number;
storage: number;
embeddedWallets: number;
};
limits: {
storage: number;
embeddedWallets: number;
};
rateLimits: {
storage: number;
@@ -231,36 +200,6 @@ export interface UsageBillableByService {
};
}
export interface WalletStats {
date: string;
uniqueWalletsConnected: number;
totalConnections: number;
walletType: string;
}
export interface InAppWalletStats {
date: string;
authenticationMethod: string;
uniqueWalletsConnected: number;
}
export interface EcosystemWalletStats extends InAppWalletStats {}
export interface UserOpStats {
date: string;
successful: number;
failed: number;
sponsoredUsd: number;
}
export interface UserOpStatsByChain {
date: string;
successful: number;
failed: number;
sponsoredUsd: number;
chainId?: string;
}
interface BillingProduct {
name: string;
id: string;
@@ -317,30 +256,6 @@ export function useAccount({ refetchInterval }: UseAccountInput = {}) {
});
}
export function useAccountUsage() {
const { user, isLoggedIn } = useLoggedInUser();
return useQuery({
queryKey: accountKeys.usage(user?.address as string),
queryFn: async () => {
const res = await fetch(`${THIRDWEB_API_HOST}/v1/account/usage`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const json = await res.json();
if (json.error) {
throw new Error(json.error.message);
}
return json.data as UsageBillableByService;
},
enabled: !!user?.address && isLoggedIn,
});
}
export function useAccountCredits() {
const { user, isLoggedIn } = useLoggedInUser();
@@ -373,43 +288,6 @@ export function useAccountCredits() {
});
}
async function getWalletUsage(args: {
clientId: string;
from?: Date;
to?: Date;
period?: "day" | "week" | "month" | "year" | "all";
}) {
const { clientId, from, to, period } = args;
const searchParams = new URLSearchParams();
searchParams.append("clientId", clientId);
if (from) {
searchParams.append("from", from.toISOString());
}
if (to) {
searchParams.append("to", to.toISOString());
}
if (period) {
searchParams.append("period", period);
}
const res = await fetch(
`${THIRDWEB_ANALYTICS_API_HOST}/v1/wallets?${searchParams.toString()}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
},
);
const json = await res.json();
if (res.status !== 200) {
throw new Error(json.message);
}
return json.data;
}
async function getUserOpUsage(args: {
clientId: string;
from?: Date;
@@ -447,43 +325,6 @@ async function getUserOpUsage(args: {
return json.data;
}
export async function getInAppWalletUsage(args: {
clientId: string;
from?: Date;
to?: Date;
period?: "day" | "week" | "month" | "year" | "all";
}) {
const { clientId, from, to, period } = args;
const searchParams = new URLSearchParams();
searchParams.append("clientId", clientId);
if (from) {
searchParams.append("from", from.toISOString());
}
if (to) {
searchParams.append("to", to.toISOString());
}
if (period) {
searchParams.append("period", period);
}
const res = await fetch(
`${THIRDWEB_ANALYTICS_API_HOST}/v1/wallets/in-app?${searchParams.toString()}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
},
);
const json = await res?.json();
if (!res || res.status !== 200) {
throw new Error(json.message);
}
return json.data;
}
export function useUserOpUsageAggregate(args: {
clientId: string;
from?: Date;
@@ -567,63 +408,6 @@ export function useUserOpUsagePeriod(args: {
});
}
export function useWalletUsageAggregate(args: {
clientId: string;
from?: Date;
to?: Date;
}) {
const { clientId, from, to } = args;
const { user, isLoggedIn } = useLoggedInUser();
return useQuery({
queryKey: accountKeys.walletStats(
user?.address as string,
clientId as string,
from?.toISOString() || "",
to?.toISOString() || "",
"all",
),
queryFn: async () => {
return getWalletUsage({
clientId,
from,
to,
period: "all",
});
},
enabled: !!clientId && !!user?.address && isLoggedIn,
});
}
export function useWalletUsagePeriod(args: {
clientId: string;
from?: Date;
to?: Date;
period: "day" | "week" | "month" | "year";
}) {
const { clientId, from, to, period } = args;
const { user, isLoggedIn } = useLoggedInUser();
return useQuery({
queryKey: accountKeys.walletStats(
user?.address as string,
clientId as string,
from?.toISOString() || "",
to?.toISOString() || "",
period,
),
queryFn: async () => {
return getWalletUsage({
clientId,
from,
to,
period,
});
},
enabled: !!clientId && !!user?.address && isLoggedIn,
});
}
export function useUpdateAccount() {
const { user } = useLoggedInUser();
const queryClient = useQueryClient();
@@ -658,51 +442,6 @@ export function useUpdateAccount() {
});
}
export function useUpdateAccountPlan(waitForWebhook?: boolean) {
const { user } = useLoggedInUser();
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (input: { plan: string; feedback?: string }) => {
invariant(user?.address, "walletAddress is required");
const res = await fetch(`${THIRDWEB_API_HOST}/v1/account/plan`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(input),
});
const json = await res.json();
if (json.error) {
throw new Error(json.error.message);
}
// Wait for account plan to update via stripe webhook
// TODO: find a better way to notify the client that the plan has been updated
if (waitForWebhook) {
await new Promise((resolve) => setTimeout(resolve, 1000 * 10));
}
return json.data;
},
onSuccess: async () => {
return Promise.all([
// invalidate usage data as limits are different
queryClient.invalidateQueries({
queryKey: accountKeys.me(user?.address as string),
}),
queryClient.invalidateQueries({
queryKey: accountKeys.usage(user?.address as string),
}),
]);
},
});
}
export function useUpdateNotifications() {
const { user } = useLoggedInUser();
const queryClient = useQueryClient();
@@ -735,36 +474,6 @@ export function useUpdateNotifications() {
});
}
export function useCreateBillingSession(enabled = false) {
const { user } = useLoggedInUser();
return useQuery({
queryKey: accountKeys.billingSession(user?.address as string),
queryFn: async () => {
invariant(user?.address, "walletAddress is required");
const res = await fetch(
`${THIRDWEB_API_HOST}/v1/account/billingSession`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
},
);
const json = await res.json();
if (json.error) {
throw new Error(json.error.message);
}
return json.data;
},
enabled,
});
}
export function useConfirmEmail() {
const { user } = useLoggedInUser();
const queryClient = useQueryClient();
@@ -787,7 +496,7 @@ export function useConfirmEmail() {
throw new Error(json.error.message);
}
return json.data;
return json.data as { team: Team; account: Account };
},
onSuccess: async () => {
// invalidate related cache, since could be relinking account
@@ -841,40 +550,6 @@ export function useResendEmailConfirmation() {
});
}
export function useCreatePaymentMethod() {
const { user } = useLoggedInUser();
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (paymentMethodId: string) => {
invariant(user?.address, "walletAddress is required");
const res = await fetch(`${THIRDWEB_API_HOST}/v1/account/paymentMethod`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
paymentMethodId,
}),
});
const json = await res.json();
if (json.error) {
throw new Error(json.error.message);
}
return json.data;
},
onSuccess: () => {
return queryClient.invalidateQueries({
queryKey: accountKeys.me(user?.address as string),
});
},
});
}
export function useApiKeys() {
const { user, isLoggedIn } = useLoggedInUser();
return useQuery({
@@ -1,7 +1,7 @@
"use client";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import type { ResultItem } from "app/team/[team_slug]/[project_slug]/engine/(instance)/[engineId]/metrics/components/StatusCodes";
import type { ResultItem } from "app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/metrics/components/StatusCodes";
import { THIRDWEB_API_HOST } from "constants/urls";
import type { EngineBackendWalletType } from "lib/engine";
import { useState } from "react";
@@ -1,6 +1,4 @@
"use client";
import { ChakraProviderSetup } from "@/components/ChakraProviderSetup";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem } from "@/components/ui/form";
@@ -15,16 +13,17 @@ import { useLoggedInUser } from "@3rdweb-sdk/react/hooks/useLoggedInUser";
import { Turnstile } from "@marsidev/react-turnstile";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import type { CanClaimResponseType } from "app/api/testnet-faucet/can-claim/CanClaimResponseType";
import { Onboarding } from "components/onboarding";
import { mapV4ChainToV5Chain } from "contexts/map-chains";
import { useTrack } from "hooks/analytics/useTrack";
import { useState } from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { toUnits } from "thirdweb";
import type { ChainMetadata } from "thirdweb/chains";
import { useActiveAccount, useWalletBalance } from "thirdweb/react";
import { z } from "zod";
import { isOnboardingComplete } from "../../../../../../login/isOnboardingRequired";
function formatTime(seconds: number) {
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
@@ -54,6 +53,7 @@ export function FaucetButton({
chain: ChainMetadata;
amount: number;
}) {
const pathname = usePathname();
const client = useThirdwebClient();
const address = useActiveAccount()?.address;
const chainId = chain.chainId;
@@ -120,7 +120,6 @@ export function FaucetButton({
const accountQuery = useAccount();
const userQuery = useLoggedInUser();
const [showOnboarding, setShowOnBoarding] = useState(false);
const canClaimFaucetQuery = useQuery({
queryKey: ["testnet-faucet-can-claim", chainId],
@@ -147,7 +146,8 @@ export function FaucetButton({
return (
<CustomConnectWallet
loginRequired={true}
connectButtonClassName="!w-full !rounded !bg-primary !text-primary-foreground !px-4 !py-2 !text-sm"
loadingButtonClassName="!w-full"
signInLinkButtonClassName="!w-full !h-auto !rounded !bg-primary !text-primary-foreground !px-4 !py-2 !text-sm hover:!bg-primary/80"
/>
);
}
@@ -203,24 +203,17 @@ export function FaucetButton({
);
}
// Email verification is required to claim from the faucet
if (accountQuery.data.status === "noCustomer") {
if (!isOnboardingComplete(accountQuery.data)) {
return (
<>
<Button
variant="outline"
className="!opacity-100 w-full"
onClick={() => setShowOnBoarding(true)}
<Button asChild className="w-full">
<Link
href={
pathname ? `/login?next=${encodeURIComponent(pathname)}` : "/login"
}
>
Verify your Email
</Button>
{/* We will show the modal only if the user click on it, because this is a public page */}
{showOnboarding && (
<ChakraProviderSetup>
<Onboarding onOpenChange={setShowOnBoarding} />
</ChakraProviderSetup>
)}
</>
</Link>
</Button>
);
}
@@ -58,7 +58,7 @@ export function ChainHeader(props: ChainHeaderProps) {
}
/>
<Button variant="default" asChild>
<Link href="https://thirdweb.com/dashboard" target="_blank">
<Link href="/team" target="_blank">
Get started with thirdweb
</Link>
</Button>
@@ -205,7 +205,7 @@ export default async function ChainPageLayout(props: {
}
/>
<Button variant="primary">
<Link href="https://thirdweb.com/dashboard" target="_blank">
<Link href="/team" target="_blank">
Get started
</Link>
</Button>
@@ -22,6 +22,7 @@ import { isMoralisSupported } from "lib/wallet/nfts/moralis";
import { isSimpleHashSupported } from "lib/wallet/nfts/simpleHash";
import type { WalletNFT } from "lib/wallet/nfts/types";
import { CircleAlertIcon, InfoIcon } from "lucide-react";
import Link from "next/link";
import { type Dispatch, type SetStateAction, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -47,14 +48,7 @@ import type {
CreateListingParams,
} from "thirdweb/extensions/marketplace";
import { useActiveAccount, useSendAndConfirmTransaction } from "thirdweb/react";
import {
FormErrorMessage,
FormHelperText,
FormLabel,
Heading,
Link,
Text,
} from "tw-components";
import { FormErrorMessage, FormHelperText, FormLabel } from "tw-components";
import { NFTMediaWithEmptyState } from "tw-components/nft-media";
import { shortenIfAddress } from "utils/usedapp-external";
@@ -357,9 +351,7 @@ export const CreateListingsForm: React.FC<CreateListingsFormProps> = ({
})}
>
<FormControl>
<Heading as={FormLabel} size="label.lg">
Select NFT
</Heading>
<FormLabel>Select NFT</FormLabel>
<FormHelperText mb="8px">
Select the NFT you want to list for sale
</FormHelperText>
@@ -367,24 +359,22 @@ export const CreateListingsForm: React.FC<CreateListingsFormProps> = ({
<Flex flexDir="column" gap={4} mb={4}>
<div className="flex flex-row items-center gap-3 rounded-md border border-border border-orange-100 bg-orange-50 p-[10px] dark:border-orange-300 dark:bg-orange-300">
<InfoIcon className="size-6 text-orange-400 dark:text-orange-900" />
<Text color="orange.800" _dark={{ color: "orange.900" }}>
<p className="text-orange-800 dark:text-orange-900">
This chain is not supported by our NFT API yet, please enter the
contract address of the NFT you want to list.
</Text>
</p>
</div>
<FormControl
isInvalid={!!form.formState.errors.selected?.contractAddress}
>
<Heading as={FormLabel} size="label.lg">
Contract address
</Heading>
<FormLabel>Contract address</FormLabel>
<SolidityInput
solidityType="address"
formContext={form}
{...form.register("selected.contractAddress", {
required: "Contract address is required",
})}
placeholder=""
placeholder="0x..."
/>
<FormErrorMessage>
{form.formState.errors.selected?.contractAddress?.message}
@@ -458,21 +448,19 @@ export const CreateListingsForm: React.FC<CreateListingsFormProps> = ({
) : nfts && nfts.length === 0 ? (
<div className="flex flex-row items-center gap-3 rounded-md border border-border border-orange-100 bg-orange-50 p-[10px] dark:border-orange-300 dark:bg-orange-300">
<InfoIcon className="size-6 text-orange-400 dark:text-orange-900" />
<Text color="orange.800" _dark={{ color: "orange.900" }}>
<p className="text-orange-800 dark:text-orange-900">
There are no NFTs owned by this wallet. You need NFTs to create a
listing. You can create NFTs with thirdweb.{" "}
<Link href="/explore/nft" color="blue.600" isExternal>
<Link href="/explore/nft" color="blue.600" target="_blank">
Explore NFT contracts
</Link>
.
</Text>
</p>
</div>
) : null}
</FormControl>
<FormControl isRequired isDisabled={noNfts}>
<Heading as={FormLabel} size="label.lg">
Listing Currency
</Heading>
<FormLabel>Listing Currency</FormLabel>
<CurrencySelector
contractChainId={chainId}
value={form.watch("currencyContractAddress")}
@@ -485,11 +473,11 @@ export const CreateListingsForm: React.FC<CreateListingsFormProps> = ({
</FormHelperText>
</FormControl>
<FormControl isRequired isDisabled={noNfts}>
<Heading as={FormLabel} size="label.lg">
<FormLabel>
{form.watch("listingType") === "auction"
? "Buyout Price Per Token"
: "Listing Price"}
</Heading>
</FormLabel>
<Input {...form.register("pricePerToken")} />
<FormHelperText>
{form.watch("listingType") === "auction"
@@ -500,9 +488,7 @@ export const CreateListingsForm: React.FC<CreateListingsFormProps> = ({
{form.watch("selected")?.type?.toLowerCase() !== "erc721" && (
<FormControl isRequired isDisabled={noNfts}>
<div className="flex flex-row justify-between gap-2">
<Heading as={FormLabel} size="label.lg">
Quantity
</Heading>
<FormLabel>Quantity</FormLabel>
</div>
<Input {...form.register("quantity")} />
<FormHelperText>
@@ -513,18 +499,14 @@ export const CreateListingsForm: React.FC<CreateListingsFormProps> = ({
{form.watch("listingType") === "auction" && (
<>
<FormControl isRequired isDisabled={noNfts}>
<Heading as={FormLabel} size="label.lg">
Reserve Price Per Token
</Heading>
<FormLabel>Reserve Price Per Token</FormLabel>
<Input {...form.register("reservePricePerToken")} />
<FormHelperText>
The minimum price per token necessary to bid on this auction
</FormHelperText>
</FormControl>
<FormControl isRequired>
<Heading as={FormLabel} size="label.lg">
Auction Duration
</Heading>
<FormLabel>Auction Duration</FormLabel>
<Select {...form.register("listingDurationInSeconds")}>
{auctionTimes.map((time) => (
<option key={time.value} value={time.value}>
@@ -1,6 +1,8 @@
import { WalletAddress } from "@/components/blocks/wallet-address";
import { CopyAddressButton } from "@/components/ui/CopyAddressButton";
import { CopyTextButton } from "@/components/ui/CopyTextButton";
import { Badge } from "@/components/ui/badge";
import { Card } from "@/components/ui/card";
import { Sheet, SheetContent } from "@/components/ui/sheet";
import { Flex, GridItem, SimpleGrid, usePrevious } from "@chakra-ui/react";
import type { ThirdwebContract } from "thirdweb";
@@ -9,7 +11,8 @@ import type {
EnglishAuction,
} from "thirdweb/extensions/marketplace";
import { useActiveAccount } from "thirdweb/react";
import { Badge, Card, CodeBlock, Heading, Text } from "tw-components";
import { CodeClient } from "@/components/ui/code/code.client";
import { NFTMediaWithEmptyState } from "tw-components/nft-media";
import { CancelTab } from "./cancel-tab";
import { LISTING_STATUS } from "./types";
@@ -51,18 +54,20 @@ export const ListingDrawer: React.FC<NFTDrawerProps> = ({
height="150px"
/>
<Flex flexDir="column" gap={2} w="70%">
<Heading size="title.lg">{renderData.asset.metadata.name}</Heading>
<Text size="label.md" noOfLines={6}>
<p className="font-bold text-lg">
{renderData.asset.metadata.name}
</Text>
</p>
<p className="line-clamp-6 truncate text-base leading-normal">
{renderData.asset.metadata.name}
</p>
</Flex>
</div>
<Flex flexDir="column" gap={4}>
<Card as={Flex} flexDir="column" gap={3}>
<Card className="flex flex-col gap-3 p-4">
<SimpleGrid rowGap={3} columns={12} placeItems="center left">
<GridItem colSpan={3}>
<Heading size="label.md">Asset contract address</Heading>
<p className="font-bold">Asset contract address</p>
</GridItem>
<GridItem colSpan={9}>
<CopyAddressButton
@@ -71,7 +76,7 @@ export const ListingDrawer: React.FC<NFTDrawerProps> = ({
/>
</GridItem>
<GridItem colSpan={3}>
<Heading size="label.md">Token ID</Heading>
<p className="font-bold">Token ID</p>
</GridItem>
<GridItem colSpan={9}>
<CopyTextButton
@@ -82,13 +87,13 @@ export const ListingDrawer: React.FC<NFTDrawerProps> = ({
/>
</GridItem>
<GridItem colSpan={3}>
<Heading size="label.md">Seller</Heading>
<p className="font-bold">Seller</p>
</GridItem>
<GridItem colSpan={9}>
<WalletAddress address={renderData.creatorAddress} />
</GridItem>
<GridItem colSpan={3}>
<Heading size="label.md">Listing ID</Heading>
<p className="font-bold">Listing ID</p>
</GridItem>
<GridItem colSpan={9}>
<CopyTextButton
@@ -99,49 +104,37 @@ export const ListingDrawer: React.FC<NFTDrawerProps> = ({
/>
</GridItem>
<GridItem colSpan={3}>
<Heading size="label.md">Type</Heading>
</GridItem>
<GridItem colSpan={9}>
<Text fontFamily="mono" size="body.md">
{renderData.asset.type}
</Text>
<p className="font-bold">Type</p>
</GridItem>
<GridItem colSpan={9}>{renderData.asset.type}</GridItem>
<GridItem colSpan={3}>
<Heading size="label.md">Status</Heading>
<p className="font-bold">Status</p>
</GridItem>
<GridItem colSpan={9}>
<Badge
size="label.sm"
variant="subtle"
textTransform="capitalize"
>
<Badge className="uppercase">
{LISTING_STATUS[renderData.status]}
</Badge>
</GridItem>
<GridItem colSpan={3}>
<Heading size="label.md">Quantity</Heading>
<p className="font-bold">Quantity</p>
</GridItem>
<GridItem colSpan={9}>
<Text fontFamily="mono" size="body.md">
{(renderData.quantity || 0n).toString()}{" "}
{/* For listings that are completed, the `quantity` would be `0`
{(renderData.quantity || 0n).toString()}{" "}
{/* For listings that are completed, the `quantity` would be `0`
So we show this text to make it clear */}
{LISTING_STATUS[renderData.status] === "Completed"
? "(Sold out)"
: ""}
</Text>
{LISTING_STATUS[renderData.status] === "Completed"
? "(Sold out)"
: ""}
</GridItem>
{renderData.type === "direct-listing" && (
<>
<GridItem colSpan={3}>
<Heading size="label.md">Price</Heading>
<p className="font-bold">Price</p>
</GridItem>
<GridItem colSpan={9}>
<Text fontFamily="mono" size="body.md">
{renderData.currencyValuePerToken.displayValue}{" "}
{renderData.currencyValuePerToken.symbol}
</Text>
{renderData.currencyValuePerToken.displayValue}{" "}
{renderData.currencyValuePerToken.symbol}
</GridItem>
</>
)}
@@ -153,16 +146,13 @@ export const ListingDrawer: React.FC<NFTDrawerProps> = ({
</SimpleGrid>
</Card>
{data?.asset.metadata.properties ? (
<Card as={Flex} flexDir="column" gap={4}>
<Heading size="label.md">Attributes</Heading>
<CodeBlock
<Card className="flex flex-col gap-3 p-4">
<p className="font-bold">Attributes</p>
<CodeClient
code={
JSON.stringify(data.asset.metadata.properties, null, 2) || ""
}
language="json"
canCopy={false}
wrap={false}
overflow="auto"
lang="json"
/>
</Card>
) : null}
@@ -1,7 +1,6 @@
import { WalletAddress } from "@/components/blocks/wallet-address";
import { Button } from "@/components/ui/button";
import {
ButtonGroup,
Flex,
IconButton,
Select,
Skeleton,
@@ -38,7 +37,6 @@ import type {
EnglishAuction,
} from "thirdweb/extensions/marketplace";
import { min } from "thirdweb/utils";
import { Button, Text } from "tw-components";
import { ListingDrawer } from "./listing-drawer";
import { LISTING_STATUS } from "./types";
@@ -73,9 +71,9 @@ const tableColumns: Column<DirectListing | EnglishAuction>[] = [
// biome-ignore lint/suspicious/noExplicitAny: FIXME
Cell: ({ cell }: { cell: Cell<any, any> }) => {
return (
<Text size="label.md" whiteSpace="nowrap">
<p className="whitespace-nowrap text-muted-foreground">
{cell.value.displayValue} {cell.value.symbol}
</Text>
</p>
);
},
},
@@ -182,21 +180,23 @@ export const MarketplaceTable: React.FC<MarketplaceTableProps> = ({
>(null);
return (
<Flex gap={4} direction="column">
<ButtonGroup size="sm" variant="outline" isAttached>
<div className="flex flex-col gap-4">
<div className="flex flex-row">
<Button
onClick={() => setListingsToShow("all")}
variant={listingsToShow === "all" ? "solid" : "outline"}
variant={listingsToShow === "all" ? "default" : "outline"}
className="w-18 rounded-r-none"
>
All
</Button>
<Button
onClick={() => setListingsToShow("valid")}
variant={listingsToShow === "valid" ? "solid" : "outline"}
variant={listingsToShow === "valid" ? "default" : "outline"}
className="rounded-l-none"
>
Valid
</Button>
</ButtonGroup>
</div>
<TableContainer maxW="100%" className="relative">
{((listingsToShow === "all" && getAllQueryResult.isFetching) ||
(listingsToShow === "valid" && getValidQueryResult.isFetching)) && (
@@ -226,9 +226,9 @@ export const MarketplaceTable: React.FC<MarketplaceTableProps> = ({
// biome-ignore lint/suspicious/noArrayIndexKey: FIXME
key={columnIndex}
>
<Text as="label" size="label.sm" color="faded">
<p className="text-muted-foreground">
{column.render("Header")}
</Text>
</p>
</Th>
))}
{/* // Need to add an empty header for the drawer button */}
@@ -273,7 +273,7 @@ export const MarketplaceTable: React.FC<MarketplaceTableProps> = ({
</Table>
</TableContainer>
<div className="flex w-full items-center justify-center">
<Flex gap={2} direction="row" align="center">
<div className="flex flex-row items-center gap-2">
<IconButton
isDisabled={!canPreviousPage || totalCountQuery.isPending}
aria-label="first page"
@@ -286,7 +286,7 @@ export const MarketplaceTable: React.FC<MarketplaceTableProps> = ({
icon={<ChevronLeftIcon className="size-4" />}
onClick={() => previousPage()}
/>
<Text whiteSpace="nowrap">
<p className="whitespace-nowrap">
Page <strong>{pageIndex + 1}</strong> of{" "}
<Skeleton
as="span"
@@ -295,7 +295,7 @@ export const MarketplaceTable: React.FC<MarketplaceTableProps> = ({
>
<strong>{pageCount}</strong>
</Skeleton>
</Text>
</p>
<IconButton
isDisabled={!canNextPage || totalCountQuery.isPending}
aria-label="next page"
@@ -322,8 +322,8 @@ export const MarketplaceTable: React.FC<MarketplaceTableProps> = ({
<option value="250">250</option>
<option value="500">500</option>
</Select>
</Flex>
</div>
</div>
</Flex>
</div>
);
};
@@ -1,7 +1,6 @@
"use client";
import type { ThirdwebContract } from "thirdweb";
import { Heading } from "tw-components";
import { CreateListingButton } from "../components/list-button";
import { DirectListingsTable } from "./components/table";
@@ -15,7 +14,7 @@ export const ContractDirectListingsPage: React.FC<
return (
<div className="flex flex-col gap-6">
<div className="flex flex-row items-center justify-between">
<Heading size="title.sm">Contract Listings</Heading>
<p className="text-lg">Contract Listings</p>
<div className="flex flex-row gap-4">
<CreateListingButton
contract={contract}
@@ -1,7 +1,6 @@
"use client";
import type { ThirdwebContract } from "thirdweb";
import { Heading } from "tw-components";
import { CreateListingButton } from "../components/list-button";
import { EnglishAuctionsTable } from "./components/table";
@@ -15,7 +14,7 @@ export const ContractEnglishAuctionsPage: React.FC<
return (
<div className="flex flex-col gap-6">
<div className="flex flex-row items-center justify-between">
<Heading size="title.sm">Contract Auctions</Heading>
<p className="text-lg">Contract Auctions</p>
<div className="flex flex-row gap-4">
<CreateListingButton
contract={contract}
@@ -1,4 +1,4 @@
import { Link } from "tw-components";
import Link from "next/link";
import { QuantityInputWithUnlimited } from "../../quantity-input-with-unlimited";
import { CustomFormControl } from "../common";
import { useClaimConditionsFormContext } from "../index";
@@ -41,8 +41,8 @@ export const MaxClaimablePerWalletInput: React.FC = () => {
Limits are set per wallets and not per user, sophisticated actors
could get around wallet restrictions.{" "}
<Link
isExternal
color="blue.500"
className="text-blue-500"
target="_blank"
href="https://portal.thirdweb.com/contracts/design/Drop#sybil-attacks"
>
Learn more
@@ -1,11 +1,6 @@
import { Flex, FormControl } from "@chakra-ui/react";
import type { FieldError } from "react-hook-form";
import {
FormErrorMessage,
FormHelperText,
FormLabel,
Heading,
} from "tw-components";
import { FormErrorMessage, FormHelperText, FormLabel } from "tw-components";
import type { ComponentWithChildren } from "types/component-with-children";
import { useClaimConditionsFormContext } from ".";
@@ -22,9 +17,7 @@ export const CustomFormControl: ComponentWithChildren<
return (
<FormControl isDisabled={props.disabled} isInvalid={!!props.error}>
{/* label */}
<Heading as={FormLabel} size="label.md">
{props.label}
</Heading>
<FormLabel className="font-bold">{props.label}</FormLabel>
{/* input */}
{props.children}
@@ -454,7 +454,7 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
};
}, [claimConditionsQuery.data, controlledFields, isMultiPhase]);
if (isFetchingData) {
if (claimConditionsQuery.isPending) {
return (
<div className="flex h-[400px] w-full items-center justify-center rounded-lg border border-border">
<Spinner className="size-10" />
@@ -1,8 +1,9 @@
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { AdminOnly } from "@3rdweb-sdk/react/components/roles/admin-only";
import { Flex, SimpleGrid } from "@chakra-ui/react";
import { ChevronDownIcon, ChevronUpIcon, XIcon } from "lucide-react";
import type { ThirdwebContract } from "thirdweb";
import { Badge, Button, Card, Heading, Text } from "tw-components";
import { ClaimConditionTypeData, useClaimConditionsFormContext } from ".";
import { PricePreview } from "../price-preview";
import { ClaimPriceInput } from "./Inputs/ClaimPriceInput";
@@ -41,117 +42,114 @@ export const ClaimConditionsPhase: React.FC<ClaimConditionsPhaseProps> = ({
};
return (
<Card position="relative" p={8}>
<Flex direction="column" gap={8}>
<Flex
align="flex-start"
justify="space-between"
position="absolute"
top="10px"
right="10px"
gap={1}
<Card className="relative flex flex-col gap-8 p-8">
<div className="absolute top-3 right-3 flex flex-row items-start justify-between gap-1">
<Button
variant="ghost"
onClick={toggleEditing}
size="sm"
className="gap-2"
>
{field.isEditing ? "Collapse" : isAdmin ? "Edit" : "See Phase"}
{field.isEditing ? (
<ChevronUpIcon className="size-4" />
) : (
<ChevronDownIcon className="size-4" />
)}
</Button>
<AdminOnly contract={contract}>
<Button
variant="ghost"
onClick={toggleEditing}
onClick={onRemove}
disabled={isPending}
size="sm"
rightIcon={
field.isEditing ? (
<ChevronUpIcon className="size-4" />
) : (
<ChevronDownIcon className="size-4" />
)
}
className="gap-2 text-red-300"
>
{field.isEditing ? "Collapse" : isAdmin ? "Edit" : "See Phase"}
Remove <XIcon className="size-4" />
</Button>
<AdminOnly contract={contract}>
<Button
variant="ghost"
onClick={onRemove}
isDisabled={isPending}
colorScheme="red"
size="sm"
rightIcon={<XIcon className="size-4" />}
>
Remove
</Button>
</AdminOnly>
</Flex>
</AdminOnly>
</div>
<Flex flexDir="column" gap={2} mt={{ base: 4, md: 0 }}>
<Flex gap={3} alignItems="center">
<Heading>{ClaimConditionTypeData[claimConditionType].name}</Heading>
{isActive && (
<Badge colorScheme="green" borderRadius="lg" p={1.5}>
Currently active
</Badge>
<div className="mt-4 flex flex-col gap-2 md:mt-0">
<div className="flex flex-row items-center gap-3">
<p className="font-bold text-lg text-muted-foreground">
{ClaimConditionTypeData[claimConditionType].name}
</p>
{isActive && (
<Badge variant="success" className="rounded-lg p-1.5">
Currently active
</Badge>
)}
</div>
<p className="text-muted-foreground">
{ClaimConditionTypeData[claimConditionType].description}
</p>
</div>
{!field.isEditing ? (
<div className="grid grid-cols-2 gap-2 md:grid-cols-4">
<div className="flex flex-col">
<p className="font-bold text-muted-foreground">Phase start</p>
<p className="text-muted-foreground">
{field.startTime?.toLocaleString()}
</p>
</div>
<div className="flex flex-col">
<p className="font-bold text-muted-foreground">
{isErc20 ? "Tokens" : "NFTs"} to drop
</p>
<p className="text-muted-foreground capitalize">
{field.maxClaimableSupply}
</p>
</div>
<PricePreview
price={field.price}
currencyAddress={field.currencyAddress}
contractChainId={contract.chain.id}
/>
<div className="flex flex-col">
<p className="font-bold text-muted-foreground">Limit per wallet</p>
{claimConditionType === "specific" ? (
<p>Set in the snapshot</p>
) : claimConditionType === "creator" ? (
<p>Unlimited</p>
) : (
<p className="text-muted-foreground capitalize">
{field.maxClaimablePerWallet}
</p>
)}
</Flex>
</div>
</div>
) : (
<>
<CustomFormGroup>
{/* Phase Name Input / Form Title */}
{isMultiPhase ? <PhaseNameInput /> : null}
<PhaseStartTimeInput />
</CustomFormGroup>
<Text>{ClaimConditionTypeData[claimConditionType].description}</Text>
</Flex>
<CreatorInput
creatorAddress={
(field.snapshot?.[0] as { address: string })?.address
}
/>
{!field.isEditing ? (
<SimpleGrid columns={{ base: 2, md: 4 }} gap={2}>
<div className="flex flex-col">
<Text fontWeight="bold">Phase start</Text>
<Text>{field.startTime?.toLocaleString()}</Text>
</div>
<div className="flex flex-col">
<Text fontWeight="bold">
{isErc20 ? "Tokens" : "NFTs"} to drop
</Text>
<Text textTransform="capitalize">{field.maxClaimableSupply}</Text>
</div>
<PricePreview
price={field.price}
currencyAddress={field.currencyAddress}
contractChainId={contract.chain.id}
/>
<div className="flex flex-col">
<Text fontWeight="bold">Limit per wallet</Text>
{claimConditionType === "specific" ? (
<Text>Set in the snapshot</Text>
) : claimConditionType === "creator" ? (
<Text>Unlimited</Text>
) : (
<Text textTransform="capitalize">
{field.maxClaimablePerWallet}
</Text>
)}
</div>
</SimpleGrid>
) : (
<>
<CustomFormGroup>
<MaxClaimableSupplyInput />
<ClaimPriceInput contractChainId={contract.chain.id} />
</CustomFormGroup>
{claimConditionType === "specific" ||
claimConditionType === "creator" ? null : (
<CustomFormGroup>
{/* Phase Name Input / Form Title */}
{isMultiPhase ? <PhaseNameInput /> : null}
<PhaseStartTimeInput />
<MaxClaimablePerWalletInput />
</CustomFormGroup>
)}
<CreatorInput
creatorAddress={
(field.snapshot?.[0] as { address: string })?.address
}
/>
<CustomFormGroup>
<MaxClaimableSupplyInput />
<ClaimPriceInput contractChainId={contract.chain.id} />
</CustomFormGroup>
{claimConditionType === "specific" ||
claimConditionType === "creator" ? null : (
<CustomFormGroup>
<MaxClaimablePerWalletInput />
</CustomFormGroup>
)}
<ClaimerSelection />
</>
)}
</Flex>
<ClaimerSelection />
</>
)}
</Card>
);
};
@@ -1,6 +1,5 @@
import { CURRENCIES } from "constants/currencies";
import { useAllChainsData } from "hooks/chains/allChains";
import { Text } from "tw-components";
import { shortenIfAddress } from "utils/usedapp-external";
import { isAddressZero } from "utils/zeroAddress";
@@ -25,19 +24,19 @@ export const PricePreview: React.FC<PricePreviewProps> = ({
);
return (
<div className="flex flex-col">
<Text fontWeight="bold">Default price</Text>
<div className="flex flex-col text-muted-foreground">
<p className="font-bold">Default price</p>
{Number(price) === 0 ? (
<Text>Free</Text>
<p>Free</p>
) : (
<Text>
<p>
{price}{" "}
{foundCurrency
? foundCurrency.symbol
: isAddressZero(currencyAddress || "")
? chain?.nativeCurrency.symbol || "(Native Token)"
: `(${shortenIfAddress(currencyAddress)})`}
</Text>
</p>
)}
</div>
);
@@ -1,18 +1,14 @@
import {
Input,
InputGroup,
type InputProps,
InputRightElement,
} from "@chakra-ui/react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { useEffect, useState } from "react";
import { Button } from "tw-components";
interface QuantityInputWithUnlimitedProps
extends Omit<InputProps, "onChange" | "value" | "onBlur" | "max" | "min"> {
interface QuantityInputWithUnlimitedProps {
value: string;
onChange: (value: string) => void;
hideMaxButton?: true;
decimals?: number;
isDisabled: boolean;
isRequired: boolean;
}
export const QuantityInputWithUnlimited: React.FC<
@@ -24,7 +20,6 @@ export const QuantityInputWithUnlimited: React.FC<
isDisabled,
isRequired,
decimals,
...restInputProps
}) => {
const [stringValue, setStringValue] = useState<string>(
Number.isNaN(Number(value)) ? "0" : value.toString(),
@@ -50,10 +45,10 @@ export const QuantityInputWithUnlimited: React.FC<
};
return (
<InputGroup {...restInputProps}>
<div className="flex flex-row items-center rounded-md border border-border">
<Input
isRequired={isRequired}
isDisabled={isDisabled}
required={isRequired}
disabled={isDisabled}
value={stringValue === "unlimited" ? "Unlimited" : stringValue}
onChange={(e) => updateValue(e.currentTarget.value)}
onBlur={() => {
@@ -65,23 +60,21 @@ export const QuantityInputWithUnlimited: React.FC<
setStringValue("0");
}
}}
className="border-none"
/>
{hideMaxButton ? null : (
<InputRightElement w="auto">
<Button
isDisabled={isDisabled}
colorScheme="primary"
variant="ghost"
size="sm"
mr={1}
onClick={() => {
updateValue("unlimited");
}}
>
Unlimited
</Button>
</InputRightElement>
<Button
disabled={isDisabled}
variant="ghost"
size="sm"
className="mr-1 text-primary"
onClick={() => {
updateValue("unlimited");
}}
>
Unlimited
</Button>
)}
</InputGroup>
</div>
);
};
@@ -17,7 +17,6 @@ import {
ChevronRightIcon,
} from "lucide-react";
import { type Column, usePagination, useTable } from "react-table";
import { Text } from "tw-components";
interface CsvDataTableProps<T extends object> {
data: T[];
@@ -78,9 +77,9 @@ export function CsvDataTable<T extends object>({
// biome-ignore lint/suspicious/noArrayIndexKey: FIXME
key={columnIndex}
>
<Text as="label" size="label.sm" color="faded">
<p className="text-muted-foreground">
{column.render("Header")}
</Text>
</p>
</Th>
))}
</Tr>
@@ -1,9 +1,10 @@
import { WalletAddress } from "@/components/blocks/wallet-address";
import { Badge } from "@/components/ui/badge";
import { Flex, SimpleGrid, useBreakpointValue } from "@chakra-ui/react";
import { formatDistance } from "date-fns/formatDistance";
import { useAllChainsData } from "hooks/chains/allChains";
import { useActiveAccount } from "thirdweb/react";
import { Badge, Card, Heading, Text } from "tw-components";
import { Card, Heading, Text } from "tw-components";
export type AccountSignerType = {
signer: string;
@@ -45,19 +46,9 @@ export const AccountSigner: React.FC<AccountSignerProps> = ({
<WalletAddress shortenAddress={isMobile} address={signer} />
</Heading>
<div className="flex flex-row gap-2">
{isAdmin ? (
<Badge borderRadius="lg" p={1.5}>
Admin Key
</Badge>
) : (
<Badge borderRadius="lg" p={1.5}>
Scoped key
</Badge>
)}
{isAdmin ? <Badge>Admin Key</Badge> : <Badge>Scoped key</Badge>}
{signer === address && (
<Badge colorScheme="green" borderRadius="lg" p={1.5}>
Currently connected
</Badge>
<Badge variant="success">Currently connected</Badge>
)}
</div>
</Flex>
@@ -1,6 +1,7 @@
"use client";
import { Button } from "@/components/ui/button";
import { CodeClient } from "@/components/ui/code/code.client";
import { useApiKeys, useCreateApiKey } from "@3rdweb-sdk/react/hooks/useApi";
import { Flex, FormControl, Input, Select } from "@chakra-ui/react";
import { LazyCreateAPIKeyDialog } from "components/settings/ApiKeys/Create/LazyCreateAPIKeyDialog";
@@ -16,14 +17,7 @@ import type { StoredChain } from "stores/chainStores";
import type { ThirdwebContract } from "thirdweb";
import type { ChainMetadata } from "thirdweb/chains";
import { useActiveAccount } from "thirdweb/react";
import {
Card,
CodeBlock,
FormHelperText,
FormLabel,
Heading,
Text,
} from "tw-components";
import { Card, FormHelperText, FormLabel, Heading, Text } from "tw-components";
interface EmbedSetupProps {
contract: ThirdwebContract;
@@ -304,7 +298,6 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
return (
<Flex gap={8} direction="column">
<LazyCreateAPIKeyDialog
wording="api-key"
prefill={{
name: "Embed API Key",
domains: "embed.ipfscdn.io",
@@ -536,12 +529,10 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
<Card className="flex w-full flex-col gap-2 md:w-1/2">
<Heading size="title.sm">Embed Code</Heading>
<CodeBlock
canCopy={false}
whiteSpace="pre"
overflowX="auto"
<CodeClient
code={embedCode}
language="markup"
lang="html"
loadingClassName="min-h-[190px]"
/>
<Button
className="w-auto gap-2"
@@ -1,5 +1,6 @@
"use client";
import { CodeClient } from "@/components/ui/code/code.client";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import {
type InternalTransaction,
@@ -32,14 +33,7 @@ import { Fragment, useMemo, useState } from "react";
import { toast } from "sonner";
import type { ThirdwebContract } from "thirdweb";
import { stringify } from "thirdweb/utils";
import {
Button,
Card,
CodeBlock,
FormLabel,
Heading,
Text,
} from "tw-components";
import { Button, Card, FormLabel, Heading, Text } from "tw-components";
interface EventsFeedProps {
contract: ThirdwebContract;
@@ -351,12 +345,12 @@ const EventsFeedItem: React.FC<EventsFeedItemProps> = ({
<Box gridColumn="span 3">
<Text fontWeight="bold">{event.eventName}</Text>
</Box>
<CodeBlock
gridColumn="span 9"
code={stringify(event.args)}
language="json"
/>
<Box gridColumn="span 9">
<CodeClient
code={stringify(event.args, null, 2)}
lang="json"
/>
</Box>
</SimpleGrid>
{arr.length - 1 === idx ? null : <Divider />}
@@ -1,4 +1,5 @@
"use client";
import { FormFieldSetup } from "@/components/blocks/FormFieldSetup";
import { DatePickerWithRange } from "@/components/ui/DatePickerWithRange";
import {
@@ -7,7 +8,7 @@ import {
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { Alert, AlertTitle } from "@/components/ui/alert";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import {
Form,
@@ -18,19 +19,26 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { zodResolver } from "@hookform/resolvers/zod";
import { useMutation, useQuery } from "@tanstack/react-query";
import { useMutation } from "@tanstack/react-query";
import { TransactionButton } from "components/buttons/TransactionButton";
import { addDays, fromUnixTime } from "date-fns";
import { useAllChainsData } from "hooks/chains/allChains";
import { useTxNotifications } from "hooks/useTxNotifications";
import { CircleAlertIcon, PlusIcon, Trash2Icon } from "lucide-react";
import { useCallback } from "react";
import {
type Dispatch,
type SetStateAction,
useCallback,
useState,
} from "react";
import { useFieldArray, useForm } from "react-hook-form";
import {
NATIVE_TOKEN_ADDRESS,
type PreparedTransaction,
ZERO_ADDRESS,
getContract,
@@ -57,22 +65,15 @@ export type ClaimConditionValue = {
auxData: string;
};
type ClaimCondition = {
availableSupply: bigint;
allowlistMerkleRoot: `0x${string}`;
pricePerUnit: bigint;
currency: string;
maxMintPerWallet: bigint;
startTimestamp: number;
endTimestamp: number;
auxData: string;
};
const positiveIntegerRegex = /^[0-9]\d*$/;
function ClaimableModule(props: ModuleInstanceProps) {
const { contract, ownerAccount } = props;
const account = useActiveAccount();
const [tokenId, setTokenId] = useState<string>("");
const isErc721 = props.contractInfo.name === "ClaimableERC721";
const isValidTokenId = positiveIntegerRegex.test(tokenId);
const primarySaleRecipientQuery = useReadContract(
isErc721 ? ClaimableERC721.getSaleConfig : ClaimableERC1155.getSaleConfig,
@@ -82,20 +83,27 @@ function ClaimableModule(props: ModuleInstanceProps) {
);
const claimConditionQuery = useReadContract(
ClaimableERC721.getClaimCondition,
isErc721
? ClaimableERC721.getClaimCondition
: ClaimableERC1155.getClaimCondition,
{
tokenId: positiveIntegerRegex.test(tokenId) ? BigInt(tokenId) : 0n,
contract: contract,
queryOptions: {
enabled: isErc721,
enabled: isErc721 || (!!tokenId && isValidTokenId),
},
},
);
const getClaimConditionErc1155 = (tokenId: string) =>
ClaimableERC1155.getClaimCondition({
contract: contract,
tokenId: BigInt(tokenId),
});
const noClaimConditionSet =
claimConditionQuery.data?.availableSupply === 0n &&
claimConditionQuery.data?.allowlistMerkleRoot ===
"0x0000000000000000000000000000000000000000000000000000000000000000" &&
claimConditionQuery.data?.pricePerUnit === 0n &&
claimConditionQuery.data?.currency === ZERO_ADDRESS &&
claimConditionQuery.data?.maxMintPerWallet === 0n &&
claimConditionQuery.data?.startTimestamp === 0 &&
claimConditionQuery.data?.endTimestamp === 0;
const currencyContract = getContract({
address: claimConditionQuery.data?.currency || "",
@@ -104,7 +112,6 @@ function ClaimableModule(props: ModuleInstanceProps) {
});
const shouldFetchTokenDecimals =
isErc721 &&
claimConditionQuery.data &&
claimConditionQuery.data?.currency !== ZERO_ADDRESS;
@@ -217,10 +224,8 @@ function ClaimableModule(props: ModuleInstanceProps) {
}}
claimConditionSection={{
data:
// claim condition is common for all tokens
isErc721 &&
// claim conditions is fetched
claimConditionQuery.isFetched &&
// claim conditions data is present
claimConditionQuery.data &&
// token decimals is fetched if it should be fetched
(shouldFetchTokenDecimals ? tokenDecimalsQuery.isFetched : true)
? {
@@ -229,11 +234,17 @@ function ClaimableModule(props: ModuleInstanceProps) {
}
: undefined,
setClaimCondition,
getClaimConditionErc1155,
tokenId,
isLoading:
claimConditionQuery.isLoading ||
(!!shouldFetchTokenDecimals && tokenDecimalsQuery.isLoading),
}}
isOwnerAccount={!!ownerAccount}
isErc721={isErc721}
contractChainId={props.contract.chain.id}
setTokenId={setTokenId}
isValidTokenId={isValidTokenId}
noClaimConditionSet={noClaimConditionSet}
mintSection={{
mint,
}}
@@ -246,6 +257,9 @@ export function ClaimableModuleUI(
isOwnerAccount: boolean;
isErc721: boolean;
contractChainId: number;
setTokenId: Dispatch<SetStateAction<string>>;
isValidTokenId: boolean;
noClaimConditionSet: boolean;
primarySaleRecipientSection: {
setPrimarySaleRecipient: (
values: PrimarySaleRecipientFormValues,
@@ -260,14 +274,15 @@ export function ClaimableModuleUI(
mint: (values: MintFormValues) => Promise<void>;
};
claimConditionSection: {
tokenId: string;
setClaimCondition: (values: ClaimConditionFormValues) => Promise<void>;
getClaimConditionErc1155: (tokenId: string) => Promise<ClaimCondition>;
data:
| {
claimCondition: ClaimConditionValue | undefined;
claimCondition: ClaimConditionValue;
tokenDecimals: number | undefined;
}
| undefined;
isLoading: boolean;
};
},
) {
@@ -296,25 +311,44 @@ export function ClaimableModuleUI(
Claim Conditions
</AccordionTrigger>
<AccordionContent className="px-1">
{!props.isErc721 || props.claimConditionSection.data ? (
<ClaimConditionSection
isOwnerAccount={props.isOwnerAccount}
claimCondition={
props.claimConditionSection.data?.claimCondition
}
update={props.claimConditionSection.setClaimCondition}
isErc721={props.isErc721}
chainId={props.contractChainId}
tokenDecimals={
props.claimConditionSection.data?.tokenDecimals
}
getClaimConditionErc1155={
props.claimConditionSection.getClaimConditionErc1155
}
/>
) : (
<Skeleton className="h-[350px]" />
{!props.isErc721 && (
<div className="flex flex-col gap-6">
<div className="flex-1 space-y-1">
<Label>Token ID</Label>
<p className="text-muted-foreground text-sm">
{props.isOwnerAccount
? "View and Update claim conditions for given token ID"
: "View claim conditions for given token ID"}
</p>
<Input onChange={(e) => props.setTokenId(e.target.value)} />
</div>
</div>
)}
<div className="h-6" />
{props.isValidTokenId &&
props.claimConditionSection.data &&
!props.claimConditionSection.isLoading && (
<ClaimConditionSection
isOwnerAccount={props.isOwnerAccount}
claimCondition={
props.claimConditionSection.data.claimCondition
}
update={props.claimConditionSection.setClaimCondition}
isErc721={props.isErc721}
chainId={props.contractChainId}
noClaimConditionSet={props.noClaimConditionSet}
tokenDecimals={
props.claimConditionSection.data?.tokenDecimals
}
tokenId={props.claimConditionSection.tokenId}
/>
)}
{props.isValidTokenId &&
props.claimConditionSection.isLoading && (
<Skeleton className="h-[350px]" />
)}
</AccordionContent>
</AccordionItem>
@@ -383,63 +417,48 @@ const defaultStartDate = addDays(new Date(), 7);
const defaultEndDate = addDays(new Date(), 14);
function ClaimConditionSection(props: {
claimCondition?: ClaimConditionValue;
claimCondition: ClaimConditionValue;
update: (values: ClaimConditionFormValues) => Promise<void>;
isOwnerAccount: boolean;
isErc721: boolean;
chainId: number;
tokenDecimals?: number;
getClaimConditionErc1155: (tokenId: string) => Promise<ClaimCondition>;
tokenId: string;
noClaimConditionSet: boolean;
}) {
const { idToChain } = useAllChainsData();
const chain = idToChain.get(props.chainId);
const { claimCondition } = props;
const tokenIdForm = useForm<{ tokenId: string }>({
defaultValues: {
tokenId: "",
},
});
const tokenId = tokenIdForm.watch("tokenId");
const claimConditionErc1155Query = useQuery({
queryKey: ["claimConditionErc1155", props.chainId, tokenId],
queryFn: () => props.getClaimConditionErc1155(tokenId),
enabled: !props.isErc721 && BigInt(tokenId) >= 0n,
});
const conditions = props.isErc721
? claimCondition
: claimConditionErc1155Query.data;
const { tokenId, claimCondition } = props;
const [addClaimConditionButtonClicked, setAddClaimConditionButtonClicked] =
useState(false);
const form = useForm<ClaimConditionFormValues>({
resolver: zodResolver(claimConditionFormSchema),
values: {
tokenId,
currencyAddress:
conditions?.currency === ZERO_ADDRESS ? "" : conditions?.currency,
pricePerToken:
conditions?.pricePerUnit &&
conditions?.currency !== ZERO_ADDRESS &&
props.tokenDecimals
? Number(toTokens(conditions?.pricePerUnit, props.tokenDecimals))
: 0,
claimCondition?.currency === ZERO_ADDRESS
? NATIVE_TOKEN_ADDRESS // default to the native token address
: claimCondition?.currency,
// default case is zero state, so 0 // 10 ** 18 still results in 0
pricePerToken: Number(
toTokens(claimCondition?.pricePerUnit, props.tokenDecimals || 18),
),
maxClaimableSupply:
conditions?.availableSupply.toString() === "0" ||
conditions?.availableSupply.toString() === MAX_UINT_256
claimCondition?.availableSupply.toString() === "0" ||
claimCondition?.availableSupply.toString() === MAX_UINT_256
? ""
: conditions?.availableSupply.toString() || "",
: claimCondition?.availableSupply.toString() || "",
maxClaimablePerWallet:
conditions?.maxMintPerWallet.toString() === "0" ||
conditions?.maxMintPerWallet.toString() === MAX_UINT_256
claimCondition?.maxMintPerWallet.toString() === "0" ||
claimCondition?.maxMintPerWallet.toString() === MAX_UINT_256
? ""
: conditions?.maxMintPerWallet.toString() || "",
startTime: conditions?.startTimestamp
? fromUnixTime(conditions?.startTimestamp)
: claimCondition?.maxMintPerWallet.toString() || "",
startTime: claimCondition?.startTimestamp
? fromUnixTime(claimCondition?.startTimestamp)
: defaultStartDate,
endTime: conditions?.endTimestamp
? fromUnixTime(conditions?.endTimestamp)
endTime: claimCondition?.endTimestamp
? fromUnixTime(claimCondition?.endTimestamp)
: defaultEndDate,
allowList: [],
},
@@ -474,39 +493,30 @@ function ClaimConditionSection(props: {
return (
<div className="flex flex-col gap-6">
{!props.isErc721 && (
<Form {...tokenIdForm}>
<form>
<FormField
control={tokenIdForm.control}
name="tokenId"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>Token ID</FormLabel>
<FormControl>
<Input {...field} disabled={!props.isOwnerAccount} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
{props.noClaimConditionSet && !addClaimConditionButtonClicked && (
<>
<Alert variant="warning">
<CircleAlertIcon className="size-5 max-sm:hidden" />
<AlertTitle>No Claim Condition Set</AlertTitle>
<AlertDescription>
You have not set a claim condition for this token. You can set a
claim condition by clicking the "Set Claim Condition" button.
</AlertDescription>
</Alert>
<Button
onClick={() => setAddClaimConditionButtonClicked(true)}
variant="outline"
className="w-full"
>
Add Claim Condition
</Button>
</>
)}
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
{!props.isErc721 &&
tokenId !== "" &&
BigInt(tokenId) >= 0n &&
claimConditionErc1155Query.isPending && (
<Skeleton className="h-[350px]" />
)}
{(props.isErc721 ||
(tokenId !== "" &&
BigInt(tokenId) >= 0n &&
!claimConditionErc1155Query.isPending)) && (
{(!props.noClaimConditionSet || addClaimConditionButtonClicked) && (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className="flex flex-col gap-6">
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<FormField
@@ -668,9 +678,9 @@ function ClaimConditionSection(props: {
</TransactionButton>
</div>
</div>
)}
</form>{" "}
</Form>
</form>{" "}
</Form>
)}
</div>
);
}
@@ -166,7 +166,8 @@ export function CurrencySelector<
))}
{isCustomCurrency &&
!isPaymentsSelector &&
initialValue !== NATIVE_TOKEN_ADDRESS.toLowerCase() && (
initialValue.toLowerCase() !==
NATIVE_TOKEN_ADDRESS.toLowerCase() && (
<SelectItem key={initialValue} value={initialValue}>
{initialValue}
</SelectItem>
@@ -27,12 +27,9 @@ import { useTxNotifications } from "hooks/useTxNotifications";
import { CircleAlertIcon } from "lucide-react";
import { useCallback } from "react";
import { useForm } from "react-hook-form";
import {
type PreparedTransaction,
prepareContractCall,
sendAndConfirmTransaction,
} from "thirdweb";
import { type PreparedTransaction, sendAndConfirmTransaction } from "thirdweb";
import { MintableERC721, MintableERC1155 } from "thirdweb/modules";
import { grantRoles, hasAllRoles } from "thirdweb/modules";
import { useReadContract } from "thirdweb/react";
import type { NFTMetadataInputLimited } from "types/modified-types";
import { parseAttributes } from "utils/parseAttributes";
@@ -79,10 +76,10 @@ function MintableModule(props: ModuleInstanceProps) {
contract: contract,
},
);
const hasMinterRole = useReadContract({
const hasMinterRole = useReadContract(hasAllRoles, {
contract: contract,
method: "function hasAllRoles(address user, uint256 roles) returns (bool)",
params: [ownerAccount?.address || "", MINTER_ROLE],
user: ownerAccount?.address || "",
roles: MINTER_ROLE,
});
const isBatchMetadataInstalled = !!props.allModuleContractInfo.find(
@@ -97,10 +94,10 @@ function MintableModule(props: ModuleInstanceProps) {
}
if (!hasMinterRole.data) {
const grantRoleTx = prepareContractCall({
const grantRoleTx = grantRoles({
contract,
method: "function grantRole(address user, uint256 role) public",
params: [ownerAccount.address || "", MINTER_ROLE],
user: ownerAccount.address,
roles: MINTER_ROLE,
});
await sendAndConfirmTransaction({
@@ -103,7 +103,7 @@ function TransferableModule(props: ModuleInstanceProps) {
<TransferableModuleUI
{...props}
isPending={isTransferEnabledQuery.isPending}
isRestricted={!!isTransferEnabledQuery.data}
isRestricted={!isTransferEnabledQuery.data}
adminAddress={props.ownerAccount?.address || ""}
update={update}
isOwnerAccount={!!props.ownerAccount}
@@ -48,7 +48,7 @@ const testAddress1 = "0x1F846F6DAE38E1C88D71EAA191760B15f38B7A37";
const claimCondition = {
availableSupply: BigInt(100),
maxMintPerWallet: BigInt(10),
pricePerUnit: 10n,
pricePerUnit: 10000000000000000000n,
// we get checksummed NATIVE_TOKEN_ADDRESS from claim condition query for native token
currency: checksumAddress(NATIVE_TOKEN_ADDRESS),
// last week
@@ -64,6 +64,8 @@ function Component() {
const [isClaimConditionLoading, setIsClaimConditionLoading] = useState(false);
const [isPrimarySaleRecipientLoading, setIsPrimarySaleRecipientLoading] =
useState(false);
const [tokenId, setTokenId] = useState("");
const [noClaimConditionSet, setNoClaimConditionSet] = useState(false);
async function updatePrimarySaleRecipientStub(
values: PrimarySaleRecipientFormValues,
@@ -82,12 +84,6 @@ function Component() {
await new Promise((resolve) => setTimeout(resolve, 1000));
}
async function getClaimConditionErc1155Stub() {
console.log("claim condition in stub: ", claimCondition);
await new Promise((resolve) => setTimeout(resolve, 1500));
return claimCondition;
}
const removeMutation = useMutation({
mutationFn: async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -142,6 +138,13 @@ function Component() {
id="isPrimarySaleRecipientLoading"
label="Primary Sale Recipient Section Loading"
/>
<CheckboxWithLabel
value={noClaimConditionSet}
onChange={setNoClaimConditionSet}
id="noClaimConditionSet"
label="No Claim Condition Set"
/>
</div>
<ClaimableModuleUI
@@ -156,14 +159,16 @@ function Component() {
setPrimarySaleRecipient: updatePrimarySaleRecipientStub,
}}
claimConditionSection={{
data: isClaimConditionLoading
? undefined
: {
claimCondition,
tokenDecimals: 18,
},
data:
isClaimConditionLoading || (!isErc721 && !tokenId)
? undefined
: {
claimCondition,
tokenDecimals: 18,
},
isLoading: false,
setClaimCondition: updateClaimConditionStub,
getClaimConditionErc1155: getClaimConditionErc1155Stub,
tokenId,
}}
mintSection={{
mint: mintStub,
@@ -175,6 +180,9 @@ function Component() {
isOwnerAccount={isOwner}
isErc721={isErc721}
contractChainId={1}
setTokenId={setTokenId}
isValidTokenId={true}
noClaimConditionSet={noClaimConditionSet}
/>
<Toaster richColors />
@@ -32,7 +32,7 @@ import type { Account } from "thirdweb/wallets";
import { ModuleInstance } from "./module-instance";
import { useModuleContractInfo } from "./moduleContractInfo";
export type ModuleCardProps = {
type ModuleCardProps = {
moduleAddress: string;
contract: ContractOptions;
onRemoveModule: () => void;
@@ -31,7 +31,7 @@ export function ModuleInstance(props: ModuleInstanceProps) {
return <TransferableModule {...props} />;
}
if (props.contractInfo.name.includes("Mintable-ignore")) {
if (props.contractInfo.name.includes("Mintable")) {
return <MintableModule {...props} />;
}
@@ -39,7 +39,7 @@ export function ModuleInstance(props: ModuleInstanceProps) {
return <RoyaltyModule {...props} />;
}
if (props.contractInfo.name.includes("Claimable-ignore")) {
if (props.contractInfo.name.includes("Claimable")) {
return <ClaimableModule {...props} />;
}
@@ -4,6 +4,7 @@ import { UnexpectedValueErrorMessage } from "@/components/blocks/error-fallbacks
import { WalletAddress } from "@/components/blocks/wallet-address";
import { CopyTextButton } from "@/components/ui/CopyTextButton";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { CodeClient } from "@/components/ui/code/code.client";
import { useThirdwebClient } from "@/constants/thirdweb.client";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
@@ -26,7 +27,8 @@ import type { ThirdwebContract } from "thirdweb";
import { getNFT as getErc721NFT } from "thirdweb/extensions/erc721";
import { getNFT as getErc1155NFT } from "thirdweb/extensions/erc1155";
import { useReadContract } from "thirdweb/react";
import { Badge, Button, Card, CodeBlock, Heading, Text } from "tw-components";
import {} from "tw-components";
import { Button, Card, Heading, Text } from "tw-components";
import { NFTMediaWithEmptyState } from "tw-components/nft-media";
import { shortenString } from "utils/usedapp-external";
import { NftProperty } from "../components/nft-property";
@@ -158,12 +160,7 @@ export const TokenIdPage: React.FC<TokenIdPageProps> = ({
overflow={{ base: "auto", md: "hidden" }}
pb={{ base: 4, md: 0 }}
>
<ButtonGroup
size="sm"
variant="ghost"
spacing={2}
w={(tabs.length + 1) * 95}
>
<ButtonGroup size="sm" variant="ghost" spacing={2}>
<Button
type="button"
isActive={tab === "Details"}
@@ -241,11 +238,7 @@ export const TokenIdPage: React.FC<TokenIdPageProps> = ({
<GridItem colSpan={4}>
<Heading size="label.md">Token Standard</Heading>
</GridItem>
<GridItem colSpan={8}>
<Badge size="label.sm" variant="subtle">
{nft.type}
</Badge>
</GridItem>
<GridItem colSpan={8}>{nft.type}</GridItem>
{nft.type !== "ERC721" && (
<>
<GridItem colSpan={4}>
@@ -319,12 +312,9 @@ export const TokenIdPage: React.FC<TokenIdPageProps> = ({
))}
</SimpleGrid>
) : (
<CodeBlock
<CodeClient
code={JSON.stringify(properties, null, 2) || ""}
language="json"
canCopy={false}
wrap={false}
overflow="auto"
lang="json"
/>
)}
</Card>
@@ -1,5 +1,6 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Sheet,
SheetContent,
@@ -17,7 +18,6 @@ import type { ThirdwebContract } from "thirdweb";
import * as ERC721Ext from "thirdweb/extensions/erc721";
import * as ERC1155Ext from "thirdweb/extensions/erc1155";
import { useReadContract, useSendAndConfirmTransaction } from "thirdweb/react";
import { Button } from "tw-components";
interface BatchLazyMintButtonProps {
canCreateDelayedRevealBatch: boolean;
@@ -55,10 +55,8 @@ export const BatchLazyMintButton: React.FC<BatchLazyMintButtonProps> = ({
<MinterOnly contract={contract}>
<Sheet onOpenChange={setOpen} open={open}>
<SheetTrigger asChild>
<Button
colorScheme="primary"
leftIcon={<FileStackIcon className="size-4" />}
>
<Button variant="primary" className="gap-2">
<FileStackIcon className="size-4" />
Batch Upload
</Button>
</SheetTrigger>
@@ -1,5 +1,6 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Sheet,
SheetContent,
@@ -18,7 +19,6 @@ import { type ThirdwebContract, ZERO_ADDRESS } from "thirdweb";
import { getApprovalForTransaction } from "thirdweb/extensions/erc20";
import { claimTo } from "thirdweb/extensions/erc721";
import { useActiveAccount, useSendAndConfirmTransaction } from "thirdweb/react";
import { Button } from "tw-components";
import { FormErrorMessage, FormHelperText, FormLabel } from "tw-components";
const CLAIM_FORM_ID = "nft-claim-form";
@@ -45,8 +45,8 @@ export const NFTClaimButton: React.FC<NFTClaimButtonProps> = ({ contract }) => {
return (
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
<Button colorScheme="primary" leftIcon={<GemIcon className="size-4" />}>
Claim
<Button variant="primary" className="gap-2">
<GemIcon className="size-4" /> Claim
</Button>
</SheetTrigger>
<SheetContent className="overflow-y-auto sm:w-[540px] sm:max-w-[90%] lg:w-[700px]">
@@ -1,4 +1,5 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Sheet,
SheetContent,
@@ -10,7 +11,6 @@ import { MinterOnly } from "@3rdweb-sdk/react/components/roles/minter-only";
import { PlusIcon } from "lucide-react";
import { useState } from "react";
import type { ThirdwebContract } from "thirdweb";
import { Button } from "tw-components";
import { LazyMintNftForm } from "./lazy-mint-form";
interface NFTLazyMintButtonProps {
@@ -28,11 +28,8 @@ export const NFTLazyMintButton: React.FC<NFTLazyMintButtonProps> = ({
<MinterOnly contract={contract}>
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
<Button
colorScheme="primary"
leftIcon={<PlusIcon className="size-5" />}
{...restButtonProps}
>
<Button variant="primary" className="gap-2" {...restButtonProps}>
<PlusIcon className="size-5" />
Single Upload
</Button>
</SheetTrigger>
@@ -1,5 +1,6 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Accordion,
AccordionButton,
@@ -25,7 +26,6 @@ import { lazyMint as lazyMint721 } from "thirdweb/extensions/erc721";
import { lazyMint as lazyMint1155 } from "thirdweb/extensions/erc1155";
import { useActiveAccount, useSendAndConfirmTransaction } from "thirdweb/react";
import {
Button,
FormErrorMessage,
FormHelperText,
FormLabel,
@@ -313,9 +313,9 @@ export const LazyMintNftForm: React.FC<LazyMintNftFormParams> = ({
</form>
<div className="mt-8 flex flex-row justify-end gap-3">
<Button
isDisabled={sendAndConfirmTx.isPending}
disabled={sendAndConfirmTx.isPending}
variant="outline"
mr={3}
className="mr-3"
onClick={() => setOpen(false)}
>
Cancel
@@ -1,5 +1,6 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Sheet,
SheetContent,
@@ -11,7 +12,6 @@ import { MinterOnly } from "@3rdweb-sdk/react/components/roles/minter-only";
import { PlusIcon } from "lucide-react";
import { useState } from "react";
import type { ThirdwebContract } from "thirdweb";
import { Button } from "tw-components";
import { NFTMintForm } from "./mint-form";
interface NFTMintButtonProps {
@@ -30,11 +30,8 @@ export const NFTMintButton: React.FC<NFTMintButtonProps> = ({
<MinterOnly contract={contract}>
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
<Button
colorScheme="primary"
leftIcon={<PlusIcon className="size-5" />}
{...restButtonProps}
>
<Button variant="primary" className="gap-2" {...restButtonProps}>
<PlusIcon className="size-5" />
Mint
</Button>
</SheetTrigger>
@@ -1,5 +1,6 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Sheet,
SheetContent,
@@ -18,7 +19,7 @@ import { toast } from "sonner";
import type { ThirdwebContract } from "thirdweb";
import { getBatchesToReveal, reveal } from "thirdweb/extensions/erc721";
import { useReadContract, useSendAndConfirmTransaction } from "thirdweb/react";
import { Button, FormErrorMessage, FormLabel } from "tw-components";
import { FormErrorMessage, FormLabel } from "tw-components";
interface NFTRevealButtonProps {
contract: ThirdwebContract;
@@ -48,11 +49,8 @@ export const NFTRevealButton: React.FC<NFTRevealButtonProps> = ({
<MinterOnly contract={contract}>
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
<Button
colorScheme="primary"
leftIcon={<EyeIcon className="size-4" />}
>
Reveal NFTs
<Button variant="primary" className="gap-2">
<EyeIcon className="size-4" /> Reveal NFTs
</Button>
</SheetTrigger>
<SheetContent className="w-full overflow-y-auto sm:min-w-[540px] lg:min-w-[700px]">
@@ -1,4 +1,5 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Sheet,
SheetContent,
@@ -10,7 +11,6 @@ import { MinterOnly } from "@3rdweb-sdk/react/components/roles/minter-only";
import { PlusIcon } from "lucide-react";
import { useState } from "react";
import type { ThirdwebContract } from "thirdweb";
import { Button } from "tw-components";
import { SharedMetadataForm } from "./shared-metadata-form";
interface NFTSharedMetadataButtonProps {
@@ -25,12 +25,8 @@ export const NFTSharedMetadataButton: React.FC<
<MinterOnly contract={contract}>
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
<Button
colorScheme="primary"
leftIcon={<PlusIcon className="size-5" />}
{...restButtonProps}
>
Set NFT Metadata
<Button variant="primary" className="gap-2" {...restButtonProps}>
<PlusIcon className="size-5" /> Set NFT Metadata
</Button>
</SheetTrigger>
<SheetContent className="w-full overflow-y-auto sm:min-w-[540px] lg:min-w-[700px]">
@@ -1,5 +1,8 @@
"use client";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { useResolveContractAbi } from "@3rdweb-sdk/react/hooks/useResolveContractAbi";
import {
@@ -21,7 +24,7 @@ import { CircleCheckIcon, CircleXIcon } from "lucide-react";
import { useMemo, useState } from "react";
import { toast } from "sonner";
import type { ThirdwebContract } from "thirdweb";
import { Badge, Button, Card, Heading } from "tw-components";
import { Heading } from "tw-components";
interface ContractSourcesPageProps {
contract: ThirdwebContract;
@@ -91,9 +94,7 @@ const VerifyContractModal: React.FC<
<ModalHeader>
<Flex gap={2} align="center">
<Heading size="subtitle.md">Contract Verification</Heading>
<Badge variant="outline" colorScheme="purple" rounded="md" px={2}>
beta
</Badge>
<Badge>beta</Badge>
</Flex>
</ModalHeader>
<ModalCloseButton mt={2} />
@@ -213,11 +214,11 @@ export const ContractSourcesPage: React.FC<ContractSourcesPageProps> = ({
chainId={contract.chain.id}
contractAddress={contract.address}
/>
<Button variant="solid" colorScheme="purple" onClick={onOpen}>
<Button variant="primary" onClick={onOpen}>
Verify contract
</Button>
</Flex>
<Card p={0}>
<Card>
<SourcesPanel sources={sources} abi={abiQuery.data} />
</Card>
</Flex>
@@ -264,7 +265,7 @@ function RefreshContractMetadataButton(props: {
return (
<Button
isLoading={contractCacheMutation.isPending}
disabled={contractCacheMutation.isPending}
variant="outline"
onClick={() => {
toast.promise(contractCacheMutation.mutateAsync(), {
@@ -274,8 +275,9 @@ function RefreshContractMetadataButton(props: {
error: (e) => e?.message || "Failed to refresh contract data.",
});
}}
className="w-[182px]"
>
Refresh Contract Data
{contractCacheMutation.isPending ? <Spinner /> : "Refresh Contract Data"}
</Button>
);
}
@@ -1,5 +1,6 @@
"use client";
import { Button } from "@/components/ui/button";
import { useSplitDistributeFunds } from "@3rdweb-sdk/react/hooks/useSplit";
import { MismatchButton } from "components/buttons/MismatchButton";
import { TransactionButton } from "components/buttons/TransactionButton";
@@ -7,7 +8,6 @@ import { useTrack } from "hooks/analytics/useTrack";
import { useTxNotifications } from "hooks/useTxNotifications";
import { useMemo } from "react";
import type { ThirdwebContract } from "thirdweb";
import { Button } from "tw-components";
import type { Balance } from "../ContractSplitPage";
interface DistributeButtonProps {
@@ -90,7 +90,7 @@ export const DistributeButton: React.FC<DistributeButtonProps> = ({
if (numTransactions === 0) {
return (
<Button isDisabled colorScheme="primary" {...restButtonProps}>
<Button disabled variant="primary" {...restButtonProps}>
Nothing to distribute
</Button>
);
@@ -1,5 +1,6 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Sheet,
SheetContent,
@@ -7,12 +8,11 @@ import {
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet";
import { Droplet } from "lucide-react";
import { DropletIcon } from "lucide-react";
import { useState } from "react";
import type { ThirdwebContract } from "thirdweb";
import { balanceOf } from "thirdweb/extensions/erc20";
import { useActiveAccount, useReadContract } from "thirdweb/react";
import { Button } from "tw-components";
import { TokenAirdropForm } from "./airdrop-form";
interface TokenAirdropButtonProps {
contract: ThirdwebContract;
@@ -33,12 +33,12 @@ export const TokenAirdropButton: React.FC<TokenAirdropButtonProps> = ({
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
<Button
colorScheme="primary"
leftIcon={<Droplet size={16} />}
variant="primary"
{...restButtonProps}
isDisabled={!hasBalance}
disabled={!hasBalance}
className="gap-2"
>
Airdrop
<DropletIcon size={16} /> Airdrop
</Button>
</SheetTrigger>
<SheetContent className="w-full overflow-y-auto sm:min-w-[540px] lg:min-w-[700px]">
@@ -1,5 +1,6 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Sheet,
SheetContent,
@@ -23,7 +24,6 @@ import {
useSendAndConfirmTransaction,
} from "thirdweb/react";
import {
Button,
FormErrorMessage,
FormHelperText,
FormLabel,
@@ -59,12 +59,12 @@ export const TokenBurnButton: React.FC<TokenBurnButtonProps> = ({
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
<Button
colorScheme="primary"
leftIcon={<Flame size={16} />}
variant="primary"
{...restButtonProps}
isDisabled={!hasBalance}
disabled={!hasBalance}
className="gap-2"
>
Burn
<Flame size={16} /> Burn
</Button>
</SheetTrigger>
<SheetContent className="z-[10000]">
@@ -1,5 +1,6 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Sheet,
SheetContent,
@@ -11,7 +12,7 @@ import {
import { FormControl, Input } from "@chakra-ui/react";
import { TransactionButton } from "components/buttons/TransactionButton";
import { useTrack } from "hooks/analytics/useTrack";
import { Gem } from "lucide-react";
import { GemIcon } from "lucide-react";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -22,12 +23,7 @@ import {
useReadContract,
useSendAndConfirmTransaction,
} from "thirdweb/react";
import {
Button,
FormErrorMessage,
FormHelperText,
FormLabel,
} from "tw-components";
import { FormErrorMessage, FormHelperText, FormLabel } from "tw-components";
interface TokenClaimButtonProps {
contract: ThirdwebContract;
@@ -52,12 +48,8 @@ export const TokenClaimButton: React.FC<TokenClaimButtonProps> = ({
return (
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
<Button
colorScheme="primary"
leftIcon={<Gem size={16} />}
{...restButtonProps}
>
Claim
<Button variant="primary" className="gap-2" {...restButtonProps}>
<GemIcon size={16} /> Claim
</Button>
</SheetTrigger>
<SheetContent className="z-[10000]">
@@ -1,5 +1,6 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Sheet,
SheetContent,
@@ -12,7 +13,7 @@ import { MinterOnly } from "@3rdweb-sdk/react/components/roles/minter-only";
import { FormControl, Input } from "@chakra-ui/react";
import { TransactionButton } from "components/buttons/TransactionButton";
import { useTrack } from "hooks/analytics/useTrack";
import { Plus } from "lucide-react";
import { PlusIcon } from "lucide-react";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -23,7 +24,7 @@ import {
useReadContract,
useSendAndConfirmTransaction,
} from "thirdweb/react";
import { Button, FormErrorMessage, FormLabel } from "tw-components";
import { FormErrorMessage, FormLabel } from "tw-components";
interface TokenMintButtonProps {
contract: ThirdwebContract;
@@ -50,12 +51,8 @@ export const TokenMintButton: React.FC<TokenMintButtonProps> = ({
<MinterOnly contract={contract}>
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
<Button
colorScheme="primary"
leftIcon={<Plus size={16} />}
{...restButtonProps}
>
Mint
<Button variant="primary" {...restButtonProps} className="gap-2">
<PlusIcon size={16} /> Mint
</Button>
</SheetTrigger>
<SheetContent className="z-[10000]">
@@ -1,5 +1,6 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Sheet,
SheetContent,
@@ -12,7 +13,7 @@ import { FormControl, Input } from "@chakra-ui/react";
import { TransactionButton } from "components/buttons/TransactionButton";
import { SolidityInput } from "contract-ui/components/solidity-inputs";
import { useTrack } from "hooks/analytics/useTrack";
import { Send } from "lucide-react";
import { SendIcon } from "lucide-react";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -23,12 +24,7 @@ import {
useReadContract,
useSendAndConfirmTransaction,
} from "thirdweb/react";
import {
Button,
FormErrorMessage,
FormHelperText,
FormLabel,
} from "tw-components";
import { FormErrorMessage, FormHelperText, FormLabel } from "tw-components";
interface TokenTransferButtonProps {
contract: ThirdwebContract;
@@ -57,12 +53,12 @@ export const TokenTransferButton: React.FC<TokenTransferButtonProps> = ({
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
<Button
colorScheme="primary"
leftIcon={<Send size={16} />}
variant="primary"
{...restButtonProps}
isDisabled={!hasBalance}
disabled={!hasBalance}
className="gap-2"
>
Transfer
<SendIcon size={16} /> Transfer
</Button>
</SheetTrigger>
<SheetContent className="z-[10000]">
@@ -39,7 +39,7 @@ export async function ChainListRow({
}: ChainListRowProps) {
const chainMetadata = await getChainMetadata(chainId);
return (
<TableRow className="relative hover:bg-muted/50">
<TableRow linkBox className="hover:bg-muted/50">
<TableCell>{favoriteButton}</TableCell>
{/* Name */}
<TableCell>
@@ -122,6 +122,9 @@ function pidToHref(pid: (typeof products)[number]["id"]) {
case "rpc-edge": {
return "https://portal.thirdweb.com/infrastructure/rpc-edge/overview";
}
case "insight": {
return "https://portal.thirdweb.com/insight";
}
}
}
@@ -0,0 +1,53 @@
export function InsightIcon(props: { className?: string }) {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={props.className}
>
<title>Insight</title>
<circle
cx="8.11963"
cy="12.9889"
r="4.505"
fill="url(#paint0_linear_1095_479)"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M21.2163 7.97235C21.2163 7.96914 21.2164 7.96592 21.2164 7.96271C21.2164 5.60751 18.4863 3.69824 15.1186 3.69824C11.7509 3.69824 9.02089 5.60751 9.02089 7.96271C9.02089 7.96324 9.02089 7.96378 9.02089 7.96431H9.02087V17.0369C9.02087 17.0372 9.02087 17.0374 9.02087 17.0376C9.02137 19.3925 11.7512 21.3014 15.1186 21.3014C18.486 21.3014 21.2158 19.3925 21.2163 17.0376C21.2163 17.0374 21.2163 17.0372 21.2163 17.0369V7.97235Z"
fill="#06B6D4"
/>
<path
d="M11.1232 10.3516V10.3516C13.5336 11.9944 16.704 11.9944 19.1145 10.3515V10.3515"
stroke="white"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M11.1232 15.0107V15.0107C13.5336 16.6536 16.704 16.6535 19.1145 15.0107V15.0107"
stroke="white"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<defs>
<linearGradient
id="paint0_linear_1095_479"
x1="8.11963"
y1="16.3861"
x2="-1.14166"
y2="3.67577"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#155E75" />
<stop offset="1" stopColor="#27B0DB" />
</linearGradient>
</defs>
</svg>
);
}
@@ -2,6 +2,7 @@ import type { ChainSupportedService } from "../../types/chain";
import { ConnectSDKIcon } from "./icons/ConnectSDKIcon";
import { ContractIcon } from "./icons/ContractIcon";
import { EngineIcon } from "./icons/EngineIcon";
import { InsightIcon } from "./icons/InsightIcon";
import { PayIcon } from "./icons/PayIcon";
import { RPCIcon } from "./icons/RPCIcon";
import { SmartAccountIcon } from "./icons/SmartAccountIcon";
@@ -49,6 +50,13 @@ export const products = [
description: "Point of sale solution for bridging, onramping & swapping",
link: "https://portal.thirdweb.com/connect/pay/overview",
},
{
name: "Insight",
id: "insight",
icon: InsightIcon,
description: "Query, transform and analyze blockchain data",
link: "https://portal.thirdweb.com/insight",
},
] satisfies Array<{
name: string;
id: ChainSupportedService;
@@ -7,7 +7,8 @@ export type ChainSupportedService =
| "account-abstraction"
| "pay"
| "rpc-edge"
| "chainsaw";
| "chainsaw"
| "insight";
export type ChainService = {
service: ChainSupportedService;
@@ -109,7 +109,7 @@ const OP_CTA = {
description:
"Successful applicants receive gas grants for use across all supported Optimism Superchain networks. These grants can sponsor gas fees for any onchain activity using our Account Abstraction tools.",
buttonText: "Apply now",
buttonLink: "/dashboard/settings/gas-credits",
buttonLink: "/team/~/~/settings/credits",
} satisfies ChainCTAProps;
const chainMetaRecord = {
@@ -1,3 +1,5 @@
"use client";
import { InlineCode } from "@/components/ui/inline-code";
import { TableContainer } from "@/components/ui/table";
import {
@@ -15,7 +17,6 @@ import { useTrack } from "hooks/analytics/useTrack";
import { useRpcValidation } from "hooks/chains/useRpcValidation";
import { AlertCircleIcon, CheckCircleIcon } from "lucide-react";
import { type ChangeEvent, useState } from "react";
import { Button, Card, FormLabel, Link } from "tw-components";
const StatusCheck = (props: {
@@ -0,0 +1,41 @@
import { ChakraProviderSetup } from "@/components/ChakraProviderSetup";
import type { Metadata } from "next";
import Link from "next/link";
import ChainValidation from "./ChainValidation";
const title = "Validate Chain";
const description = "Validate a given chain is compatible with thirdweb.";
export const metadata: Metadata = {
title,
description,
robots: "noindex, nofollow",
};
export default function Page() {
return (
<ChakraProviderSetup>
<div className="container py-6">
<div className="flex flex-col gap-8">
<div className="flex flex-col gap-2">
<h1 className="font-semibold text-2xl tracking-tight">
Validate Chain
</h1>
<p>
Validate a given chain is compatible with{" "}
<Link
href="/chainlist"
className="text-link-foreground hover:text-foreground"
>
thirdweb
</Link>
.
</p>
</div>
<ChainValidation />
</div>
</div>
</ChakraProviderSetup>
);
}

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