Compare commits

..
Author SHA1 Message Date
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
bdb4c31a6a Version Packages (#6966)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-06 18:34:04 -07:00
MananTank 171594d66a [NEB-237] Fix Nebula OG Image (#6965)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR removes a block of code that redirects requests for the `nebula-app` path to a new URL format. This change likely simplifies the routing logic in the application.

### Detailed summary
- Removed the conditional block that checked if the first path was `nebula-app`.
- Deleted the logic to construct a new URL and redirect to `nebula.thirdweb.com`.

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

<!-- end pr-codex -->
2025-05-06 23:48:37 +00:00
Joaquim VergesandGitHub 760b250bc3 [SDK] Fix MetaMask deeplink parsing (#6963) 2025-05-06 16:48:19 -07:00
MananTank 669a95f983 Nebula: Context selection updates (#6961)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the wallet management system in the `Nebula` application. It enhances context handling, modifies wallet address management, and improves the integration of active accounts.

### Detailed summary
- Replaced `activeAccountAddress` with `selectedAddress` in multiple components.
- Updated context handling to use `setContext`.
- Enhanced `WalletSelector` to use the new `selectedAddress`.
- Refactored state management for context filters in `ChatPageContent`.
- Added memoization for context filters to optimize performance.

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

<!-- end pr-codex -->
2025-05-06 23:37:15 +00:00
Phillip HoandGitHub 928113bb0b chore: Support Engine Core and Webhook usage events (#6960) 2025-05-07 06:02:11 +07:00
gregandGitHub c8b8b2faad Fix: Adds checkout error boundary (#6962) 2025-05-06 14:41:26 -07:00
MananTank 9d40b13299 DX: Improved auto imports (#6950)
Taken from:

https://x.com/dillionverma/status/1917639091228926311
2025-05-06 10:51:30 +00:00
Joaquim VergesandGitHub bda0f3edb8 [Checkout] Add theme support and improve client configuration (#6958) 2025-05-06 03:31:56 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
dfa3ee0231 Version Packages (#6949)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-06 00:00:14 -07:00
gregandGitHub 3a100c98ef fix: hide common extension name (#6957) 2025-05-05 23:56:31 -07:00
gregandGitHub 08cff4b6c2 Feature: Adds chains function (#6956) 2025-05-05 23:46:56 -07:00
gregfromstl f2b4fe6727 Feature: Checkout page (#6954)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the checkout experience in the `dashboard` app by integrating analytics and improving the layout and structure of the checkout components.

### Detailed summary
- Removed a redirect from `"/checkout"` to `"/connect"` in `apps/dashboard/redirects.js`.
- Added `THIRDWEB_ANALYTICS_DOMAIN` to `urls.ts`.
- Created `Providers` component to wrap children with `ThirdwebProvider`.
- Enhanced `CheckoutLayout` with `ThemeProvider` and layout structure.
- Updated `CheckoutEmbed` to utilize new analytics domain and handle payment success.
- Implemented a new `RoutesPage` for handling checkout logic and parameters.

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

<!-- end pr-codex -->
2025-05-06 06:36:40 +00:00
gregandGitHub 6dd2b09450 [SDK] Feature: Adds sortBy to Bridge.routes (#6955) 2025-05-05 22:15:12 -07:00
gregandGitHub 736c3f8558 [SDK] Feature: Add maxSteps to bridge quote functions (#6953) 2025-05-05 20:25:54 -07:00
gregGitHubgraphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
055e451fd0 [Portal] Clean up UB docs (#6952)
Signed-off-by: greg <[email protected]>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2025-05-05 19:46:38 -07:00
MananTank 2c6bd620b5 Dashboard: Remove unused assets (#6948)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on the deletion of numerous asset files from the `apps/dashboard/public` directory and an update to the `logUnusedAssets.ts` script to adjust how asset file names are checked within the content.

### Detailed summary
- Deleted multiple asset files from `apps/dashboard/public/logos`, `apps/dashboard/public/assets`, and `apps/dashboard/public/assets/community/gallery`.
- Modified the `logUnusedAssets.ts` script:
  - Changed the condition to check for asset file names to include a leading slash: `if (content.includes(`/${assetFileName.name}`))`.

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

<!-- end pr-codex -->
2025-05-05 22:46:39 +00:00
MananTank 98e0cbdc71 [TOOL-4373] Dashboard: replace /team/~/~ with /team/<team-slug>/~ wherever possible (#6946)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the billing components in the dashboard by adding `teamSlug` as a prop to various components, allowing for better integration of team-specific billing information.

### Detailed summary
- Added `teamSlug` prop to `<CreditsInfoCard />` in `Billing/index.tsx`.
- Updated `<SmartWalletsBillingAlert />` to receive `teamSlug` in `AccountAbstractionPage.tsx`.
- Modified `CreditsInfoCard` to use `teamSlug` prop.
- Enhanced `SmartWalletsBillingAlert` to accept `teamSlug` as a prop.
- Passed `teamSlug` to the `href` in `SmartWalletsBillingAlert`.
- Included `teamSlug` in `CreditsItem` component.

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

<!-- end pr-codex -->
2025-05-05 22:46:39 +00:00
MananTank 1bc036ce6f Dashboard: Remove OPCreditsGrantedModal (#6945)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on removing several components related to onboarding and image handling from the project, as well as updating the `layout.tsx` file to eliminate the use of the `OpCreditsGrantedModalWrapperServer` component.

### Detailed summary
- Deleted the following files:
  - `apps/dashboard/src/components/Image/index.tsx`
  - `apps/dashboard/public/assets/dashboard/confetti.gif`
  - `apps/dashboard/src/components/icons/brand-icons/OpenseaIcon.tsx`
  - `apps/dashboard/src/components/onboarding/OpCreditsGrantedModal.tsx`
  - `apps/dashboard/src/components/onboarding/OpCreditsGrantedModalWrapper.tsx`
  - `apps/dashboard/src/components/onboarding/OpCreditsGrantedModalWrapperServer.tsx`
- Removed the `<OpCreditsGrantedModalWrapperServer />` component from the `layout.tsx` file.

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

<!-- end pr-codex -->
2025-05-05 22:46:39 +00:00
MananTank 9f90b87c31 [TOOL-4366] Redirect /team to last visited team (#6938)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR updates the team management functionality in the `TeamRootPage` component by replacing the method for retrieving the team with a new function that checks for the last visited team or defaults to a standard team. This improves user experience by remembering the last team visited.

### Detailed summary
- Replaced `getDefaultTeam` with `getLastVisitedTeamOrDefaultTeam` in `TeamRootPage`.
- Updated the condition to check if a team is available.
- Changed the redirect URL to use the slug of the newly retrieved `team`.
- Added `getLastVisitedTeamOrDefaultTeam` function in `team.ts` to handle team retrieval logic using cookies.

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

<!-- end pr-codex -->
2025-05-05 22:38:17 +00:00
MananTank e9d0b6e3d2 SDK: do not attempt autoconnect again if already successfull (#6900)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new `force` option for the auto-connect functionality in the `thirdweb` wallet, allowing users to force an auto-connect attempt even if a previous attempt was successful.

### Detailed summary
- Added `force` option to `AutoConnectProps` in `autoConnect.ts`.
- Updated `autoConnectCore` to respect the `force` option, allowing multiple connect attempts.
- Modified tests in `autoConnectCore.test.ts` to include the `force` option in various scenarios.

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

<!-- end pr-codex -->
2025-05-05 22:20:15 +00:00
GiselleNessi ee065b8d10 removed wrong link for connectButton theme (#6937)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR updates the link for the `ConnectButton` component's theme documentation from Notion to the official Thirdweb portal, ensuring users have access to the most accurate and current information.

### Detailed summary
- Changed the link for the `ConnectButton` component's theme documentation from Notion to the official Thirdweb portal.

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

<!-- end pr-codex -->
2025-05-05 19:18:07 +00:00
GiselleNessi b17078b025 [Portal] removed nebula waitlist CTA (#6936)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR updates the `page.mdx` file for the Nebula project, transitioning its status from Alpha to Beta and adding a new feature to encourage users to try the live version.

### Detailed summary
- Added `ArticleIconCard` component import from `@doc`.
- Changed status from Alpha to Beta.
- Introduced a new section with a `Stack` containing an `ArticleIconCard` to promote trying Nebula live.
- Updated the link in the `ArticleIconCard` to point to the live Nebula page.

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

<!-- end pr-codex -->
2025-05-05 19:13:45 +00:00
MananTank a4c9b28149 [TOOL-4374] Dashboard: Update Team name and slug validation (#6935)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces validation schemas for team slugs and names using `zod`, refactors the `TeamInfoForm` and `TeamGeneralSettingsPageUI` components to utilize these schemas, and enhances form handling and error messaging.

### Detailed summary
- Added `teamSlugSchema` and `teamNameSchema` for validation.
- Refactored `TeamInfoForm` to use `teamNameSchema` and `teamSlugSchema`.
- Updated form submission handling in `TeamInfoForm` and `TeamSlugFormControl`.
- Improved error handling and messaging for team name and slug inputs.
- Removed redundant validations and constants from `TeamInfoForm`.

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

<!-- end pr-codex -->
2025-05-05 18:23:28 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
96155016d1 Version Packages (#6920)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-04 15:51:41 -07:00
MananTank b08efa1778 Dashboard: Fix Nebula floating chat authToken query cache key (#6932)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR enhances the `NebulaChatButton` component by adding an `isLoggedIn` prop across various instances, affecting its behavior based on user authentication. It also updates the `useNebulaAuthToken` hook to accept an `isLoggedIn` parameter, improving its functionality.

### Detailed summary
- Added `isLoggedIn` prop to `NebulaChatButton` in:
  - `layout.tsx` for chain page
  - `page.tsx` for support page
  - `layout.tsx` for contract page
- Updated `NebulaChatButton` to pass `isLoggedIn` to `NebulaChatUIContainer`.
- Modified `useNebulaAuthToken` to accept `isLoggedIn` and updated query key accordingly.
- Changed loading state check from `isPending` to `isFetching`.

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

<!-- end pr-codex -->
2025-05-03 09:39:12 +00:00
FirekeeperandGitHub 6745ba092c [SDK] force type 1 ronin+saigon (#6927) 2025-05-02 20:47:39 -07:00
MananTank 80bf795760 [TOOL-4292] Dashboard: Show Team selector page for '~' team slug (#6930)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR removes a redirect feature for team paths in the `middleware.ts` file and introduces a new page component in `page.tsx` that allows users to select a team when they belong to multiple teams, enhancing the team selection experience.

### Detailed summary
- Removed redirect logic for `/team/~/...` paths in `middleware.ts`.
- Added a new page component in `page.tsx` for team selection.
- Implemented fetching of teams and authentication token using `Promise.all`.
- Included UI elements for displaying teams and their details.
- Introduced `createTeamLink` function to generate team-specific links.
- Utilized components like `GradientAvatar`, `TeamHeader`, and `AppFooter` for layout.

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

<!-- end pr-codex -->
2025-05-02 22:00:21 +00:00
MananTank 7ae174fed5 Dashboard: Update banner text (#6931)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR updates the `label` prop of the `AnnouncementBannerUI` component to enhance the messaging about the Nebula App.

### Detailed summary
- Changed the `label` prop from "Nebula AI Beta is out and open to all" to "Announcing Nebula App — the most powerful AI for blockchains! Try it now!" in the `AnnouncementBannerUI` component.

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

<!-- end pr-codex -->
2025-05-02 20:33:25 +00:00
MananTank c803893a17 [TOOL-4347] Dashboard: Use Nebula auth token for nebula floating chat (#6928)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on enhancing the `NebulaChatButton` component by modifying authentication handling, improving the login process, and introducing a new method for managing the Nebula authentication token.

### Detailed summary
- Removed `authToken` from several components.
- Updated `doLogin` function to accept `loginPayload`.
- Introduced `getNebulaAuthToken` to manage token retrieval.
- Replaced `Suspense` with a new `ChatContent` component for better token handling.
- Added `useNebulaAuthToken` hook to fetch authentication token using React Query.
- Enhanced error handling and login flow in `doNebulaLogin`.

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

<!-- end pr-codex -->
2025-05-02 19:51:17 +00:00
MananTank 4839173d00 [TOOL-4354] Nebula: Configure erc20Value for swap prepared transaction (#6926)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces logic to handle transactions involving native tokens in the `SwapTransactionCardLayout` component. It determines if the transaction involves selling a native token and adjusts the transaction data accordingly.

### Detailed summary
- Added a constant `isSellingNativeToken` to check if the transaction involves selling the native token.
- Updated the transaction object to conditionally set `erc20Value` based on whether the native token is being sold.

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

<!-- end pr-codex -->
2025-05-02 15:04:48 +00:00
MananTank 1e82083a3c [NEB-229] Nebula: follow-up prompt improvements, preserve reasoning UI on error (#6925)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the `ChatPageContent` and `Chats` components by managing message handling and improving the logic for sending prompts based on the presence of subsequent messages.

### Detailed summary
- Removed logic to delete the last message if it is of type `presence` in `ChatPageContent.tsx`.
- Added `nextMessage` prop to `RenderMessage` in `Chats.tsx`.
- Updated `onTxSettled` to prevent sending prompts if the `nextMessage` is of type `action`.

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

<!-- end pr-codex -->
2025-05-02 14:56:24 +00:00
Joaquim VergesandGitHub 20d182b228 [Changeset] Optimize token query in PayEmbed with Insight (#6924) 2025-05-01 19:50:49 -07:00
Joaquim VergesandGitHub a96fa915cf [SDK] Optimize token balance fetching with Insight API (#6923) 2025-05-01 19:46:52 -07:00
Joaquim VergesandGitHub 4b58eca7c8 Update dependencies in pnpm-lock.yaml (#6921) 2025-05-01 16:53:05 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
6e87370687 chore(deps-dev): bump vite from 6.3.2 to 6.3.4 in the npm_and_yarn group (#6909)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-01 16:41:31 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Joaquim Verges
465cf8c990 chore(deps): bump viem from 2.27.2 to 2.28.0 (#6882)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Joaquim Verges <[email protected]>
2025-05-01 16:33:20 -07:00
Joaquim VergesandGitHub 98cce77cf1 [SDK] Add ZERO_ADDRESS check to isNativeToken function (#6918) 2025-05-01 16:19:41 -07:00
Joaquim VergesandGitHub 5f0049f35f [Dashboard] Add teamId to fee management API calls (#6919) 2025-05-01 16:19:06 -07:00
MananTank 9a63cd4b57 [TOOL-4346] Dashboard: Show Project ID in project settings page (#6917)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new component, `ProjectIdCard`, to display the project ID in the `ProjectGeneralSettingsPage`. This component allows users to copy the project ID easily.

### Detailed summary
- Added `ProjectIdCard` component to display project ID.
- Integrated `ProjectIdCard` into `ProjectGeneralSettingsPage`.
- `ProjectIdCard` includes a `SettingsCard` with a title, description, and a `CopyTextButton` for copying the project ID.

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

<!-- end pr-codex -->
2025-05-01 22:55:35 +00:00
MananTank 004f3776cf [TOOL-4336] Dashboard: Fix contracts on custom chains not shown in contracts table (#6916)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on refactoring the chain fetching functionality within the application. It renames and updates the `getChains` function to `getChainsForNebula`, introduces a new utility `fetchChainWithLocalOverrides`, and modifies various components to utilize these changes.

### Detailed summary
- Renamed `getChains` to `getChainsForNebula` in `getChainIds.ts`.
- Updated references to `getChains` in `page.tsx` and `login/page.tsx` to use `getChainsForNebula`.
- Introduced `fetchChainWithLocalOverrides` in `fetchChainWithLocalOverrides.ts`.
- Replaced `fetchChain` with `fetchChainWithLocalOverrides` in `getSortedDeployedContracts.tsx` and `getContractFromParams.ts`.
- Removed redundant local chain fetching logic from `getContractFromParams.ts`.

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

<!-- end pr-codex -->
2025-05-01 20:59:33 +00:00
221 changed files with 2272 additions and 1366 deletions
+6
View File
@@ -9,6 +9,12 @@
"typescript.preferences.autoImportFileExcludePatterns": [
"./packages/thirdweb/src/exports"
],
"typescript.preferences.autoImportSpecifierExcludeRegexes": [
"@radix-ui",
"next/router",
"next/dist",
"^lucide-react/dist/lucide-react.suffixed$"
],
"typescript.tsdk": "node_modules/typescript/lib",
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
+3
View File
@@ -0,0 +1,3 @@
declare module "lucide-react" {
export * from "lucide-react/dist/lucide-react.suffixed";
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

@@ -1,6 +0,0 @@
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Check Circle Icon">
<path id="Vector" d="M8.5 16.5C12.9184 16.5 16.5 12.9184 16.5 8.5C16.5 4.0816 12.9184 0.5 8.5 0.5C4.0816 0.5 0.5 4.0816 0.5 8.5C0.5 12.9184 4.0816 16.5 8.5 16.5Z" fill="white"/>
<path id="Check" d="M4.87939 9.01716L6.9482 11.086L12.1202 5.91394" stroke="#000001" stroke-width="1.23077" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 395 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 676 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 779 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

-5
View File
@@ -100,11 +100,6 @@ async function redirects() {
destination: "/auth",
permanent: false,
},
{
source: "/checkout",
destination: "/connect",
permanent: false,
},
{
source: "/extensions",
destination: "/build",
+1 -1
View File
@@ -88,7 +88,7 @@ function main(params: { assetsFolder: string; srcFolder: string }) {
// remove the files for which its name can be found in the content of the file
folderVisitor(params.srcFolder, (content) => {
for (const assetFileName of assetFileNames) {
if (content.includes(assetFileName.name)) {
if (content.includes(`/${assetFileName.name}`)) {
unusedFileNames.delete(assetFileName);
}
}
+21
View File
@@ -1,7 +1,9 @@
import "server-only";
import { API_SERVER_URL, THIRDWEB_API_SECRET } from "@/constants/env";
import type { TeamResponse } from "@thirdweb-dev/service-utils";
import { cookies } from "next/headers";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { LAST_USED_TEAM_ID } from "../../constants/cookies";
export type Team = TeamResponse & { stripeCustomerId: string | null };
@@ -74,3 +76,22 @@ export async function getDefaultTeam() {
}
return null;
}
export async function getLastVisitedTeamOrDefaultTeam() {
const token = await getAuthToken();
if (!token) {
return null;
}
const cookiesStore = await cookies();
const lastVisitedTeamId = cookiesStore.get(LAST_USED_TEAM_ID)?.value;
if (lastVisitedTeamId) {
const team = await getTeamById(lastVisitedTeamId);
if (team) {
return team;
}
}
return getDefaultTeam();
}
@@ -100,12 +100,14 @@ export type Fee = {
export async function getFees(props: {
clientId: string;
teamId: string;
}) {
const authToken = await getAuthToken();
const res = await fetch(`${UB_BASE_URL}/v1/developer/fees`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"x-team-id": props.teamId,
"x-client-id-override": props.clientId,
Authorization: `Bearer ${authToken}`,
},
@@ -122,6 +124,7 @@ export async function getFees(props: {
export async function updateFee(props: {
clientId: string;
teamId: string;
feeRecipient: string;
feeBps: number;
}) {
@@ -131,6 +134,7 @@ export async function updateFee(props: {
headers: {
"Content-Type": "application/json",
"x-client-id-override": props.clientId,
"x-team-id": props.teamId,
Authorization: `Bearer ${authToken}`,
},
body: JSON.stringify({
@@ -9,7 +9,7 @@ import {
} from "@/components/ui/popover";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
import { CheckIcon, ChevronDown, SearchIcon, XIcon } from "lucide-react";
import { CheckIcon, ChevronDownIcon, SearchIcon, XIcon } from "lucide-react";
import {
forwardRef,
useCallback,
@@ -201,7 +201,7 @@ export const MultiSelect = forwardRef<HTMLButtonElement, MultiSelectProps>(
orientation="vertical"
className="flex h-full min-h-6"
/>
<ChevronDown className="h-4 cursor-pointer text-muted-foreground" />
<ChevronDownIcon className="h-4 cursor-pointer text-muted-foreground" />
</div>
</div>
) : (
@@ -209,7 +209,7 @@ export const MultiSelect = forwardRef<HTMLButtonElement, MultiSelectProps>(
<span className="text-muted-foreground text-sm">
{placeholder}
</span>
<ChevronDown className="h-4 cursor-pointer text-muted-foreground" />
<ChevronDownIcon className="h-4 cursor-pointer text-muted-foreground" />
</div>
)}
</Button>
@@ -8,7 +8,7 @@ import {
PopoverTrigger,
} from "@/components/ui/popover";
import { cn } from "@/lib/utils";
import { CheckIcon, ChevronDown, SearchIcon } from "lucide-react";
import { CheckIcon, ChevronDownIcon, SearchIcon } from "lucide-react";
import React, { useRef, useMemo, useEffect } from "react";
import { useShowMore } from "../../lib/useShowMore";
import { ScrollShadow } from "../ui/ScrollShadow/ScrollShadow";
@@ -131,7 +131,7 @@ export const SelectWithSearch = React.forwardRef<
>
{selectedOption?.label || placeholder}
</span>
<ChevronDown className="size-4 cursor-pointer text-muted-foreground" />
<ChevronDownIcon className="size-4 cursor-pointer text-muted-foreground" />
</div>
</Button>
</PopoverTrigger>
@@ -8,7 +8,7 @@ import {
import { useThirdwebClient } from "@/constants/thirdweb.client";
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
import { useClipboard } from "hooks/useClipboard";
import { Check, Copy, XIcon } from "lucide-react";
import { CheckIcon, CopyIcon, XIcon } from "lucide-react";
import { useMemo } from "react";
import { type ThirdwebClient, isAddress } from "thirdweb";
import { ZERO_ADDRESS } from "thirdweb";
@@ -104,9 +104,9 @@ export function WalletAddress(props: {
className="flex items-center gap-2"
>
{hasCopied ? (
<Check className="h-4 w-4" />
<CheckIcon className="h-4 w-4" />
) : (
<Copy className="h-4 w-4" />
<CopyIcon className="h-4 w-4" />
)}
{hasCopied ? "Copied!" : "Copy"}
</Button>
@@ -9,11 +9,7 @@ import {
} from "@/components/ui/popover";
import { cn } from "@/lib/utils";
import { format, isBefore } from "date-fns";
import {
Calendar as CalendarIcon,
CalendarX2Icon,
ChevronDownIcon,
} from "lucide-react";
import { CalendarIcon, CalendarX2Icon, ChevronDownIcon } from "lucide-react";
import React from "react";
import { DynamicHeight } from "./DynamicHeight";
import { TabButtons } from "./tabs";
@@ -1,7 +1,7 @@
"use client";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDown } from "lucide-react";
import { ChevronDownIcon } from "lucide-react";
import * as React from "react";
import { cn } from "@/lib/utils";
@@ -34,7 +34,7 @@ const AccordionTrigger = React.forwardRef<
{...props}
>
{children}
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
<ChevronDownIcon className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
));
@@ -1,5 +1,5 @@
import { Slot } from "@radix-ui/react-slot";
import { ChevronRight, MoreHorizontal } from "lucide-react";
import { ChevronRightIcon, MoreHorizontalIcon } from "lucide-react";
import * as React from "react";
import { cn } from "@/lib/utils";
@@ -84,7 +84,7 @@ const BreadcrumbSeparator = ({
className={cn("[&>svg]:size-3.5", className)}
{...props}
>
{children ?? <ChevronRight />}
{children ?? <ChevronRightIcon />}
</li>
);
BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
@@ -102,7 +102,7 @@ const BreadcrumbEllipsis = ({
)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
<MoreHorizontalIcon className="h-4 w-4" />
<span className="sr-only">More</span>
</span>
);
@@ -1,6 +1,6 @@
"use client";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import type * as React from "react";
import { DayPicker } from "react-day-picker";
@@ -54,8 +54,8 @@ function Calendar({
...classNames,
}}
components={{
IconLeft: () => <ChevronLeft className="h-4 w-4" />,
IconRight: () => <ChevronRight className="h-4 w-4" />,
IconLeft: () => <ChevronLeftIcon className="size-4" />,
IconRight: () => <ChevronRightIcon className="size-4" />,
}}
{...props}
/>
@@ -1,7 +1,7 @@
"use client";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { Check } from "lucide-react";
import { CheckIcon } from "lucide-react";
import * as React from "react";
import { cn } from "@/lib/utils";
@@ -21,7 +21,7 @@ const Checkbox = React.forwardRef<
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")}
>
<Check className="h-4 w-4" />
<CheckIcon className="size-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
));
@@ -1,7 +1,7 @@
"use client";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { X } from "lucide-react";
import { XIcon } from "lucide-react";
import * as React from "react";
import { cn } from "@/lib/utils";
@@ -65,7 +65,7 @@ const DialogContent = React.forwardRef<
dialogCloseClassName,
)}
>
<X className="h-4 w-4" />
<XIcon className="size-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
@@ -1,7 +1,7 @@
"use client";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { Check, ChevronRight, Circle } from "lucide-react";
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
import * as React from "react";
import { cn } from "@/lib/utils";
@@ -34,7 +34,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
<ChevronRightIcon className="ml-auto size-4" />
</DropdownMenuPrimitive.SubTrigger>
));
DropdownMenuSubTrigger.displayName =
@@ -107,7 +107,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
<CheckIcon className="size-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
@@ -130,7 +130,7 @@ const DropdownMenuRadioItem = React.forwardRef<
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
<CircleIcon className="size-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
@@ -1,7 +1,7 @@
"use client";
import { OTPInput, OTPInputContext } from "input-otp";
import { Dot } from "lucide-react";
import { DotIcon } from "lucide-react";
import * as React from "react";
import { cn } from "@/lib/utils";
@@ -65,7 +65,7 @@ const InputOTPSeparator = React.forwardRef<
>(({ ...props }, ref) => (
// biome-ignore lint/a11y/useFocusableInteractive: pure shadcn component - it works
<div ref={ref} role="separator" {...props}>
<Dot />
<DotIcon />
</div>
));
InputOTPSeparator.displayName = "InputOTPSeparator";
@@ -1,4 +1,8 @@
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
import {
ChevronLeftIcon,
ChevronRightIcon,
MoreHorizontalIcon,
} from "lucide-react";
import * as React from "react";
import { Button, type ButtonProps } from "@/components/ui/button";
@@ -66,7 +70,7 @@ const PaginationPrevious = ({
className={cn("h-10 gap-1 pl-2.5", className)}
{...props}
>
<ChevronLeft className="h-4 w-4" />
<ChevronLeftIcon className="size-4" />
<span className="max-sm:hidden">Previous</span>
</PaginationLink>
);
@@ -83,7 +87,7 @@ const PaginationNext = ({
{...props}
>
<span className="max-sm:hidden">Next</span>
<ChevronRight className="h-4 w-4" />
<ChevronRightIcon className="size-4" />
</PaginationLink>
);
PaginationNext.displayName = "PaginationNext";
@@ -100,7 +104,7 @@ const PaginationEllipsis = ({
)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
<MoreHorizontalIcon className="size-4" />
<span className="sr-only">More pages</span>
</span>
);
@@ -1,7 +1,7 @@
"use client";
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
import { Circle } from "lucide-react";
import { CircleIcon } from "lucide-react";
import * as React from "react";
import { cn } from "@/lib/utils";
@@ -35,7 +35,7 @@ const RadioGroupItem = React.forwardRef<
{...props}
>
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
<Circle className="h-2.5 w-2.5 fill-current text-current" />
<CircleIcon className="size-2.5 fill-current text-current" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
);
@@ -59,11 +59,11 @@ const RadioGroupItemButton = React.forwardRef<
<div className="flex aspect-square h-4 w-4 items-center justify-center rounded-full border text-inverted ring-offset-background transition-all focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 group-hover:border-foreground/25 group-data-[state=checked]:border-inverted">
{/* Show on checked */}
<RadioGroupPrimitive.Indicator className="flex items-center justify-center rounded-full transition-all">
<Circle className="h-2 w-2 fill-current text-current" />
<CircleIcon className="size-2 fill-current text-current" />
</RadioGroupPrimitive.Indicator>
{/* Show on hover */}
<div className="hidden items-center justify-center rounded-full text-foreground/25 transition-all group-hover:flex group-data-[state=checked]:hidden">
<Circle className="h-2 w-2 fill-current text-current" />
<CircleIcon className="size-2 fill-current text-current" />
</div>
</div>
<Label className="cursor-pointer">{props.children}</Label>
@@ -1,7 +1,7 @@
"use client";
import * as SelectPrimitive from "@radix-ui/react-select";
import { Check, ChevronDown } from "lucide-react";
import { CheckIcon, ChevronDownIcon } from "lucide-react";
import * as React from "react";
import { cn } from "@/lib/utils";
@@ -28,7 +28,7 @@ const SelectTrigger = React.forwardRef<
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className={cn("h-4 w-4 opacity-50", chevronClassName)} />
<ChevronDownIcon className={cn("h-4 w-4 opacity-50", chevronClassName)} />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
));
@@ -127,7 +127,7 @@ const SelectItem = React.forwardRef<
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
<CheckIcon className="size-4" />
</SelectPrimitive.ItemIndicator>
</span>
+2 -2
View File
@@ -2,7 +2,7 @@
import * as SheetPrimitive from "@radix-ui/react-dialog";
import { type VariantProps, cva } from "class-variance-authority";
import { X } from "lucide-react";
import { XIcon } from "lucide-react";
import * as React from "react";
import { cn } from "@/lib/utils";
@@ -66,7 +66,7 @@ const SheetContent = React.forwardRef<
>
{children}
<SheetPrimitive.Close className="absolute top-4 right-4 rounded-sm border-border 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-secondary">
<X className="h-4 w-4" />
<XIcon className="size-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
@@ -21,7 +21,7 @@ import { useIsMobile } from "@/hooks/use-mobile";
import { cn } from "@/lib/utils";
import { Slot } from "@radix-ui/react-slot";
import { type VariantProps, cva } from "class-variance-authority";
import { PanelLeft } from "lucide-react";
import { PanelLeftIcon } from "lucide-react";
import * as React from "react";
const SIDEBAR_COOKIE_NAME = "sidebar_state";
@@ -288,7 +288,7 @@ const SidebarTrigger = React.forwardRef<
}}
{...props}
>
<PanelLeft />
<PanelLeftIcon />
<span className="sr-only">Toggle Sidebar</span>
</Button>
);
@@ -1,6 +1,6 @@
"use client";
import { useTrack } from "hooks/analytics/useTrack";
import { FileText } from "lucide-react";
import { FileTextIcon } from "lucide-react";
import Link from "next/link";
import type { ChainMetadata } from "thirdweb/chains";
import { SectionTitle } from "../server/SectionTitle";
@@ -14,7 +14,7 @@ export default function NextSteps(props: { chain: ChainMetadata }) {
<SectionTitle title="Next Steps" />
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3">
<div className="relative flex gap-3 rounded-lg border bg-card p-4 pr-8 transition-colors hover:border-active-border">
<FileText className="mt-0.5 size-5 shrink-0" />
<FileTextIcon className="mt-0.5 size-5 shrink-0" />
<div>
<h3 className="mb-1.5 font-medium">
<Link
@@ -5,7 +5,7 @@ import { Skeleton } from "@/components/ui/skeleton";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { DASHBOARD_THIRDWEB_CLIENT_ID, isProd } from "@/constants/env";
import { useQuery } from "@tanstack/react-query";
import { CircleCheck, XIcon } from "lucide-react";
import { CircleCheckIcon, XIcon } from "lucide-react";
import { hostnameEndsWith } from "utils/url";
import { PrimaryInfoItem } from "../server/primary-info-item";
@@ -67,7 +67,7 @@ export function ChainLiveStats(props: { rpc: string }) {
titleIcon={
stats.isSuccess ? (
<ToolTipLabel label="Working">
<CircleCheck className="size-4 text-success-text" />
<CircleCheckIcon className="size-4 text-success-text" />
</ToolTipLabel>
) : stats.isError ? (
<ToolTipLabel label="Not Working">
@@ -13,11 +13,11 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { getThirdwebClient } from "@/constants/thirdweb.server";
import { ChevronDownIcon, TicketCheckIcon } from "lucide-react";
import type { Metadata } from "next";
import Link from "next/link";
import { redirect } from "next/navigation";
import { getThirdwebClient } from "../../../../../../@/constants/thirdweb.server";
import { mapV4ChainToV5Chain } from "../../../../../../contexts/map-chains";
import { NebulaChatButton } from "../../../../../nebula-app/(app)/components/FloatingChat/FloatingChat";
import {
@@ -100,10 +100,10 @@ The following is the user's message:
return (
<>
<NebulaChatButton
isLoggedIn={!!authToken}
networks={chain.testnet ? "testnet" : "mainnet"}
isFloating={true}
pageType="chain"
authToken={authToken ?? undefined}
label="Ask AI about this chain"
client={client}
nebulaParams={{
@@ -1,11 +1,9 @@
import { getThirdwebClient } from "@/constants/thirdweb.server";
import { mapV4ChainToV5Chain } from "contexts/map-chains";
import { cookies } from "next/headers";
import { TW_LOCAL_CHAIN_STORE } from "stores/storageKeys";
import { getAddress, getContract, isAddress } from "thirdweb";
import type { ChainMetadata } from "thirdweb/chains";
import { fetchChain } from "utils/fetchChain";
import { LAST_USED_TEAM_ID } from "../../../../../../../constants/cookies";
import { fetchChainWithLocalOverrides } from "../../../../../../../utils/fetchChainWithLocalOverrides";
import { getAuthToken } from "../../../../../api/lib/getAuthToken";
export async function getContractPageParamsInfo(params: {
@@ -14,29 +12,7 @@ export async function getContractPageParamsInfo(params: {
}) {
const contractAddress = getCheckSummedAddress(params.contractAddress);
const chainSlugOrId = params.chain_id;
let chainMetadata = await fetchChain(chainSlugOrId).catch(() => null);
const cookieStore = await cookies();
const localChainStoreValue = cookieStore.get(TW_LOCAL_CHAIN_STORE)?.value;
if (localChainStoreValue) {
try {
const chains = JSON.parse(decodeURIComponent(localChainStoreValue));
if (typeof chains === "object" && Array.isArray(chains)) {
const chainOverrides = chains as ChainMetadata[];
const chain = chainOverrides.find(
(c) =>
c.slug === chainSlugOrId ||
c.chainId === Number.parseInt(chainSlugOrId),
);
if (chain) {
chainMetadata = chain;
}
}
} catch {
// noop
}
}
const chainMetadata = await fetchChainWithLocalOverrides(chainSlugOrId);
if (!chainMetadata) {
return undefined;
@@ -10,7 +10,6 @@ import { isAddress, isContractDeployed } from "thirdweb/utils";
import { shortenIfAddress } from "utils/usedapp-external";
import { NebulaChatButton } from "../../../../../nebula-app/(app)/components/FloatingChat/FloatingChat";
import {
getAuthToken,
getAuthTokenWalletAddress,
getUserThirdwebClient,
} from "../../../../api/lib/getAuthToken";
@@ -47,10 +46,7 @@ export default async function Layout(props: {
notFound();
}
const [authToken, accountAddress] = await Promise.all([
getAuthToken(),
getAuthTokenWalletAddress(),
]);
const accountAddress = await getAuthTokenWalletAddress();
const client = await getUserThirdwebClient();
const teamsAndProjects = await getTeamsAndProjectsIfLoggedIn();
@@ -115,10 +111,10 @@ The following is the user's message:`;
client={client}
>
<NebulaChatButton
isLoggedIn={!!accountAddress}
networks={info.chainMetadata.testnet ? "testnet" : "mainnet"}
isFloating={true}
pageType="contract"
authToken={authToken ?? undefined}
label="Ask AI about this contract"
client={client}
nebulaParams={{
@@ -2,7 +2,7 @@
import { ScrollShadow } from "@/components/ui/ScrollShadow/ScrollShadow";
import { Alert, AlertTitle } from "@/components/ui/alert";
import { CircleSlash } from "lucide-react";
import { CircleSlashIcon } from "lucide-react";
import type { ThirdwebContract } from "thirdweb/contract";
import type { Account } from "thirdweb/wallets";
import { ModuleCard } from "./module-card";
@@ -37,7 +37,7 @@ export const InstalledModulesTable = (props: {
{sectionTitle}
<Alert variant="destructive">
<div className="flex items-center gap-3">
<CircleSlash className="size-6 text-red-400" />
<CircleSlashIcon className="size-6 text-red-400" />
<AlertTitle className="mb-0">No modules installed</AlertTitle>
</div>
</Alert>
@@ -3,7 +3,7 @@
import { TransactionButton } from "components/buttons/TransactionButton";
import { useTrack } from "hooks/analytics/useTrack";
import { useTxNotifications } from "hooks/useTxNotifications";
import { CircleCheck, Upload } from "lucide-react";
import { CircleCheckIcon, UploadIcon } from "lucide-react";
import { type Dispatch, type SetStateAction, useState } from "react";
import { useForm } from "react-hook-form";
import type { ThirdwebContract } from "thirdweb";
@@ -108,13 +108,13 @@ export const TokenAirdropForm: React.FC<TokenAirdropFormProps> = ({
colorScheme="primary"
borderRadius="md"
onClick={() => setAirdropFormOpen(true)}
rightIcon={<Upload size={16} />}
rightIcon={<UploadIcon className="size-4" />}
>
Upload addresses
</Button>
{addresses.length > 0 && (
<div className="flex flex-row items-center justify-center gap-2 text-green-500">
<CircleCheck className="text-green-500" size={16} />
<CircleCheckIcon className="text-green-500" size={16} />
<Text size="body.sm" color="inherit">
<strong>{addresses.length} addresses</strong> ready to be
airdropped
@@ -12,7 +12,7 @@ import {
import { FormControl, Input } from "@chakra-ui/react";
import { TransactionButton } from "components/buttons/TransactionButton";
import { useTrack } from "hooks/analytics/useTrack";
import { Flame } from "lucide-react";
import { FlameIcon } from "lucide-react";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -66,7 +66,7 @@ export const TokenBurnButton: React.FC<TokenBurnButtonProps> = ({
disabled={!hasBalance}
className="gap-2"
>
<Flame size={16} /> Burn
<FlameIcon className="size-4" /> Burn
</Button>
</SheetTrigger>
<SheetContent>
@@ -11,7 +11,7 @@ import {
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Separator } from "@/components/ui/separator";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { ChevronDownIcon, Filter, XIcon } from "lucide-react";
import { ChevronDownIcon, FilterIcon, XIcon } from "lucide-react";
import { usePathname, useSearchParams } from "next/navigation";
import type React from "react";
import { type PropsWithChildren, useCallback, useMemo } from "react";
@@ -32,7 +32,7 @@ export function AllFilters(props: { hideChainType?: boolean }) {
variant="outline"
className="h-10 w-auto gap-2 p-2 lg:h-10 lg:border-0 lg:px-4 lg:py-2"
>
<Filter strokeWidth={1} className="lg:size-4" />
<FilterIcon strokeWidth={1} className="lg:size-4" />
<span className="hidden lg:inline">All Filters</span>
</Button>
</PopoverTrigger>
@@ -6,7 +6,7 @@ import { Button, type ButtonProps } from "@/components/ui/button";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Star } from "lucide-react";
import { StarIcon } from "lucide-react";
import { useActiveAccount } from "thirdweb/react";
async function favoriteChains() {
@@ -104,7 +104,7 @@ export function StarButton(props: {
{mutation.isPending ? (
<Spinner className={cn("size-6", props.iconClassName)} />
) : (
<Star
<StarIcon
className={cn(
"size-6 text-foreground transition-all",
props.iconClassName,
@@ -158,10 +158,10 @@ export default async function SupportPage() {
</p>
<div className="mt-6 flex w-full flex-col items-center gap-3">
<NebulaChatButton
isLoggedIn={!!accountAddress}
networks="all"
isFloating={false}
pageType="support"
authToken={authToken ?? undefined}
label="Ask Nebula AI for support"
client={client}
nebulaParams={{
@@ -11,7 +11,7 @@ import { ToolTipLabel } from "@/components/ui/tooltip";
import { useThirdwebClient } from "@/constants/thirdweb.client";
import type { Abi, AbiFunction } from "abitype";
import { useV5DashboardChain } from "lib/v5-adapter";
import { ArrowDown, WalletIcon } from "lucide-react";
import { ArrowDownIcon, WalletIcon } from "lucide-react";
import { useState } from "react";
import { useForm } from "react-hook-form";
import {
@@ -207,7 +207,7 @@ ${Object.keys(populatedTransaction)
</Card>
<div className="flex justify-center">
<ArrowDown />
<ArrowDownIcon className="size-4" />
</div>
<Card className="flex flex-col gap-4 p-4">
@@ -273,7 +273,7 @@ ${Object.keys(populatedTransaction)
{state.message && (
<>
<div className="flex justify-center">
<ArrowDown />
<ArrowDownIcon className="size-4" />
</div>
<Card className="max-w-[800px] p-4">
<p className="overflow-auto whitespace-pre-wrap font-mono text-sm">
@@ -1,4 +1,4 @@
import { fetchChain } from "utils/fetchChain";
import { fetchChainWithLocalOverrides } from "../../../../../utils/fetchChainWithLocalOverrides";
import {
type ProjectContract,
getProjectContracts,
@@ -19,7 +19,9 @@ export async function getSortedDeployedContracts(params: {
const chainIds = Array.from(new Set(contracts.map((c) => c.chainId)));
const chains = (
await Promise.allSettled(
chainIds.map((chainId) => fetchChain(chainId.toString())),
chainIds.map((chainId) =>
fetchChainWithLocalOverrides(chainId.toString()),
),
)
)
.filter((c) => c.status === "fulfilled")
@@ -10,7 +10,7 @@ import {
import { cn } from "@/lib/utils";
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
import { useEns } from "components/contract-components/hooks";
import { LogOutIcon, Moon, Sun } from "lucide-react";
import { LogOutIcon, MoonIcon, SunIcon } from "lucide-react";
import { useTheme } from "next-themes";
import Link from "next/link";
import { useState } from "react";
@@ -107,13 +107,13 @@ export function AccountButton(props: {
<p className="text-sm">Theme</p>
<div className="ml-auto flex items-center gap-2 rounded-lg border border-border px-3 py-1.5 text-foreground">
<Sun
<SunIcon
className={cn(
"size-4",
theme === "light" ? "opacity-100" : "opacity-30",
)}
/>
<Moon
<MoonIcon
className={cn(
"size-4",
theme === "dark" ? "opacity-100" : "opacity-30",
@@ -8,8 +8,8 @@ import { useEns } from "components/contract-components/hooks";
import {
LogOutIcon,
MenuIcon,
Moon,
Sun,
MoonIcon,
SunIcon,
UserRoundIcon,
WalletIcon,
XIcon,
@@ -213,7 +213,7 @@ export function MobileBurgerMenuButton(
>
<span className="text-base text-muted-foreground">Theme</span>
<div className="ml-auto flex items-center gap-2 rounded-lg border px-2 py-1">
<Sun
<SunIcon
className={cn(
"size-4",
theme === "light"
@@ -221,7 +221,7 @@ export function MobileBurgerMenuButton(
: "text-muted-foreground",
)}
/>
<Moon
<MoonIcon
className={cn(
"size-4",
theme === "dark"
@@ -20,7 +20,12 @@ import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { useDebounce } from "use-debounce";
import { z } from "zod";
import { teamSlugRegex } from "../../../team/[team_slug]/(team)/~/settings/general/common";
import {
maxTeamNameLength,
maxTeamSlugLength,
teamNameSchema,
teamSlugSchema,
} from "../../../team/[team_slug]/(team)/~/settings/general/common";
type TeamData = {
name?: string;
@@ -28,27 +33,8 @@ type TeamData = {
image?: File;
};
const teamSlugSchema = z
.string()
.min(3, {
message: "URL must be at least 3 characters",
})
.max(48, {
message: "URL must be at most 48 characters",
})
.refine((slug) => !teamSlugRegex.test(slug), {
message: "URL can only contain lowercase letters, numbers and hyphens",
});
const formSchema = z.object({
name: z
.string()
.min(3, {
message: "Name must be at least 3 characters",
})
.max(32, {
message: "Name must be at most 32 characters",
}),
name: teamNameSchema,
slug: teamSlugSchema,
image: z.instanceof(File).optional(),
});
@@ -155,8 +141,6 @@ export function TeamInfoFormUI(props: {
});
}
const maxTeamNameLength = 32;
return (
<div className="rounded-lg border bg-card ">
<Form {...form}>
@@ -234,6 +218,7 @@ export function TeamInfoFormUI(props: {
}}
className="truncate border-0 font-mono"
placeholder="my-team"
maxLength={maxTeamSlugLength}
/>
{(isCheckingSlug || isCalculatingSlug) && (
<div className="-translate-y-1/2 fade-in-0 absolute top-1/2 right-3 duration-300">
@@ -10,7 +10,7 @@ import {
import { getThirdwebClient } from "@/constants/thirdweb.server";
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
import { PROJECT_SHOWCASE_DATA } from "lib/project-showcase-constants";
import { ExternalLink, FileText } from "lucide-react";
import { ExternalLinkIcon, FileTextIcon } from "lucide-react";
import Link from "next/link";
export default async function DetailPage(props: {
@@ -52,7 +52,7 @@ export default async function DetailPage(props: {
target="_blank"
rel="noopener noreferrer"
>
<ExternalLink className="mr-2 h-4 w-4" />
<ExternalLinkIcon className="mr-2 h-4 w-4" />
Visit Project Website
</Link>
</Button>
@@ -63,7 +63,7 @@ export default async function DetailPage(props: {
target="_blank"
rel="noopener noreferrer"
>
<FileText className="mr-2 h-4 w-4" />
<FileTextIcon className="mr-2 h-4 w-4" />
Link to Case Study
</Link>
</Button>
@@ -23,7 +23,7 @@ import {
PROJECT_SHOWCASE_ITEMS_PER_PAGE,
} from "lib/project-showcase-constants";
import { getAbsoluteUrl } from "lib/vercel-utils";
import { FileText } from "lucide-react";
import { FileTextIcon } from "lucide-react";
import type { Metadata } from "next";
import Image from "next/image";
import Link from "next/link";
@@ -171,7 +171,7 @@ export default async function ProjectShowcasePage(props: {
// onClick={(e) => e.stopPropagation()}
>
<Button variant="outline" size="sm">
<FileText className="mr-2 h-4 w-4" /> Case Study
<FileTextIcon className="mr-2 h-4 w-4" /> Case Study
</Button>
</Link>
)}
@@ -18,7 +18,7 @@ import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandl
import {
AlertTriangleIcon,
CheckIcon,
ChevronsUpDown,
ChevronsUpDownIcon,
ExternalLinkIcon,
PlusCircleIcon,
} from "lucide-react";
@@ -78,7 +78,7 @@ function EcosystemSelect(props: {
className="relative flex w-full justify-start truncate pr-8 pl-3 md:w-48"
>
<div className="truncate">{props.ecosystem?.name}</div>
<ChevronsUpDown className="absolute right-2 h-4 w-4 text-muted-foreground" />
<ChevronsUpDownIcon className="absolute right-2 h-4 w-4 text-muted-foreground" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-full md:w-48">
@@ -14,7 +14,7 @@ import {
import { ToolTipLabel } from "@/components/ui/tooltip";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { cn } from "@/lib/utils";
import { Pencil, Trash2 } from "lucide-react";
import { PencilIcon, Trash2Icon } from "lucide-react";
import { toast } from "sonner";
import { Link } from "tw-components/link";
import type { Ecosystem, Partner } from "../../../../../types";
@@ -146,7 +146,7 @@ function PartnerRow(props: {
);
}}
>
<Pencil className="size-4" />
<PencilIcon className="size-4" />
<span className="sr-only">Edit</span>
</Button>
<ConfirmationDialog
@@ -179,7 +179,7 @@ function PartnerRow(props: {
className="text-destructive hover:bg-destructive hover:text-destructive-foreground"
disabled={isDeleting}
>
<Trash2 className="size-4" />
<Trash2Icon className="size-4" />
<span className="sr-only">Delete</span>
</Button>
</ConfirmationDialog>
@@ -63,7 +63,7 @@ import {
Trash2Icon,
} from "lucide-react";
import { MoreHorizontalIcon } from "lucide-react";
import { ArrowRight } from "lucide-react";
import { ArrowRightIcon } from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { useForm } from "react-hook-form";
@@ -868,7 +868,7 @@ function EmptyEngineState(props: {
>
<Link href={`/team/${props.teamSlug}/~/engine/import`}>
Import self-hosted Engine
<ArrowRight size={16} />
<ArrowRightIcon size={16} />
</Link>
</Button>
</div>
@@ -902,14 +902,14 @@ function EmptyEngineState(props: {
<Button className="w-full gap-2" size="sm" asChild>
<Link href={`/team/${props.teamSlug}/~/settings/billing`}>
Upgrade Plan
<ArrowRight size={16} />
<ArrowRightIcon size={16} />
</Link>
</Button>
) : (
<Button className="w-full gap-2" size="sm" asChild>
<Link href={PRO_CONTACT_US_URL} target="_blank">
Contact Us
<ArrowRight size={16} />
<ArrowRightIcon size={16} />
</Link>
</Button>
)}
@@ -1,7 +1,7 @@
import { Button } from "@/components/ui/button";
import { useEngineCreateWalletCredential } from "@3rdweb-sdk/react/hooks/useEngine";
import { useTrack } from "hooks/analytics/useTrack";
import { Plus } from "lucide-react";
import { PlusIcon } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
import { CredentialForm } from "./credential-form";
@@ -61,7 +61,7 @@ export const CreateWalletCredentialButton: React.FC<
setIsOpen(true);
}}
>
<Plus className="mr-2 h-4 w-4" />
<PlusIcon className="mr-2 h-4 w-4" />
Create Wallet Credential
</Button>
@@ -26,7 +26,7 @@ import { createColumnHelper } from "@tanstack/react-table";
import { TWTable } from "components/shared/TWTable";
import { format, formatDistanceToNowStrict } from "date-fns";
import { useTrack } from "hooks/analytics/useTrack";
import { MailQuestion, TrashIcon } from "lucide-react";
import { MailQuestionIcon, TrashIcon } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
import { Card, FormLabel, Text } from "tw-components";
@@ -137,7 +137,7 @@ export const WebhooksTable: React.FC<WebhooksTableProps> = ({
isFetched={isFetched}
onMenuClick={[
{
icon: <MailQuestion className="size-4" />,
icon: <MailQuestionIcon className="size-4" />,
text: "Test webhook",
onClick: (row) => {
setSelectedWebhook(row);
@@ -12,7 +12,7 @@ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { AlertCircle, CheckCircle } from "lucide-react";
import { AlertCircleIcon, CheckCircleIcon } from "lucide-react";
import { useState } from "react";
interface DomainVerificationFormProps {
@@ -142,7 +142,7 @@ export function TeamDomainVerificationCard({
</div>
<Alert variant="info">
<AlertCircle className="size-4" />
<AlertCircleIcon className="size-4" />
<AlertTitle>
DNS changes can take up to 48 hours to propagate.
</AlertTitle>
@@ -174,7 +174,7 @@ export function TeamDomainVerificationCard({
<div>
<div className="flex items-center justify-between">
<div className="mt-2 flex items-start space-x-3">
<CheckCircle className="mt-0.5 h-5 w-5 text-green-500" />
<CheckCircleIcon className="mt-0.5 h-5 w-5 text-green-500" />
<div>
<p className="font-medium">{domainQuery.data.domain}</p>
<p className="text-muted-foreground text-sm">
@@ -8,13 +8,21 @@ import { CopyTextButton } from "@/components/ui/CopyTextButton";
import { Input } from "@/components/ui/input";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
import { zodResolver } from "@hookform/resolvers/zod";
import { useMutation } from "@tanstack/react-query";
import { FileInput } from "components/shared/FileInput";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import type { ThirdwebClient } from "thirdweb";
import { z } from "zod";
import { TeamDomainVerificationCard } from "../_components/settings-cards/domain-verification";
import { teamSlugRegex } from "./common";
import {
maxTeamNameLength,
maxTeamSlugLength,
teamNameSchema,
teamSlugSchema,
} from "./common";
type UpdateTeamField = (team: Partial<Team>) => Promise<void>;
@@ -59,111 +67,109 @@ export function TeamGeneralSettingsPageUI(props: {
);
}
const teamNameFormSchema = z.object({
name: teamNameSchema,
});
function TeamNameFormControl(props: {
team: Team;
updateTeamField: UpdateTeamField;
}) {
const [teamName, setTeamName] = useState(props.team.name);
const maxTeamNameLength = 32;
const form = useForm<{ name: string }>({
values: { name: props.team.name },
resolver: zodResolver(teamNameFormSchema),
});
const updateTeamMutation = useMutation({
mutationFn: (name: string) => props.updateTeamField({ name }),
});
function handleSave() {
const promises = updateTeamMutation.mutateAsync(teamName);
toast.promise(promises, {
success: "Team name updated successfully",
error: "Failed to update team name",
});
}
return (
<SettingsCard
header={{
title: "Team Name",
description: "This is your team's name on thirdweb",
}}
bottomText={`Please use ${maxTeamNameLength} characters at maximum.`}
saveButton={{
onClick: handleSave,
disabled: teamName.length === 0,
isPending: updateTeamMutation.isPending,
}}
errorText={undefined}
noPermissionText={undefined} // TODO
<form
onSubmit={form.handleSubmit((values) => {
const promise = updateTeamMutation.mutateAsync(values.name);
toast.promise(promise, {
success: "Team name updated successfully",
error: "Failed to update team name",
});
})}
>
<Input
value={teamName}
maxLength={maxTeamNameLength}
onChange={(e) => {
setTeamName(e.target.value);
<SettingsCard
header={{
title: "Team Name",
description: "This is your team's name on thirdweb",
}}
className="md:w-[450px]"
/>
</SettingsCard>
bottomText={`Please use ${maxTeamNameLength} characters at maximum.`}
saveButton={{
type: "submit",
disabled: !form.formState.isDirty,
isPending: updateTeamMutation.isPending,
}}
errorText={form.formState.errors.name?.message}
noPermissionText={undefined}
>
<Input
{...form.register("name")}
maxLength={maxTeamNameLength}
className="md:w-[450px]"
/>
</SettingsCard>
</form>
);
}
const teamSlugFormSchema = z.object({
slug: teamSlugSchema,
});
function TeamSlugFormControl(props: {
team: Team;
updateTeamField: (team: Partial<Team>) => Promise<void>;
}) {
const [teamSlug, setTeamSlug] = useState(props.team.slug);
const maxTeamURLLength = 48;
const [errorMessage, setErrorMessage] = useState<string | undefined>();
const form = useForm<{ slug: string }>({
defaultValues: { slug: props.team.slug },
resolver: zodResolver(teamSlugFormSchema),
});
const updateTeamMutation = useMutation({
mutationFn: (slug: string) => props.updateTeamField({ slug: slug }),
mutationFn: (slug: string) => props.updateTeamField({ slug }),
});
function handleSave() {
const promises = updateTeamMutation.mutateAsync(teamSlug);
toast.promise(promises, {
success: "Team URL updated successfully",
error: "Failed to update team URL",
});
}
return (
<SettingsCard
header={{
title: "Team URL",
description:
"This is your team's URL namespace on thirdweb. All your team's projects and settings can be accessed using this URL",
}}
bottomText={`Please use ${maxTeamURLLength} characters at maximum.`}
errorText={errorMessage}
saveButton={{
onClick: handleSave,
disabled: errorMessage !== undefined,
isPending: updateTeamMutation.isPending,
}}
noPermissionText={undefined} // TODO
<form
onSubmit={form.handleSubmit((values) => {
const promise = updateTeamMutation.mutateAsync(values.slug);
toast.promise(promise, {
success: "Team URL updated successfully",
error: "Failed to update team URL",
});
})}
>
<div className="relative flex rounded-lg border border-border md:w-[450px]">
<div className="flex items-center self-stretch rounded-l-lg border-border border-r bg-card px-3 font-mono text-muted-foreground/80 text-sm">
thirdweb.com/team/
<SettingsCard
header={{
title: "Team URL",
description:
"This is your team's URL namespace on thirdweb. All your team's projects and settings can be accessed using this URL",
}}
bottomText={`Please use ${maxTeamSlugLength} characters at maximum.`}
errorText={form.formState.errors.slug?.message}
saveButton={{
type: "submit",
disabled: !form.formState.isDirty,
isPending: updateTeamMutation.isPending,
}}
noPermissionText={undefined}
>
<div className="relative flex rounded-lg border border-border md:w-[450px]">
<div className="flex items-center self-stretch rounded-l-lg border-border border-r bg-card px-3 font-mono text-muted-foreground/80 text-sm">
thirdweb.com/team/
</div>
<Input
{...form.register("slug")}
maxLength={maxTeamSlugLength}
className="truncate border-0 font-mono"
/>
</div>
<Input
value={teamSlug}
onChange={(e) => {
const value = e.target.value.slice(0, maxTeamURLLength);
setTeamSlug(value);
if (value.trim().length === 0) {
setErrorMessage("Team URL can not be empty");
} else if (teamSlugRegex.test(value)) {
setErrorMessage(
"Invalid Team URL. Only letters, numbers and hyphens are allowed",
);
} else {
setErrorMessage(undefined);
}
}}
className="truncate border-0 font-mono"
/>
</div>
</SettingsCard>
</SettingsCard>
</form>
);
}
@@ -186,8 +192,8 @@ function TeamAvatarFormControl(props: {
});
function handleSave() {
const promises = updateTeamAvatarMutation.mutateAsync(teamAvatar);
toast.promise(promises, {
const promise = updateTeamAvatarMutation.mutateAsync(teamAvatar);
toast.promise(promise, {
success: "Team avatar updated successfully",
error: "Failed to update team avatar",
});
@@ -263,8 +269,8 @@ export function LeaveTeamCard(props: {
});
function handleLeave() {
const promises = leaveTeam.mutateAsync();
toast.promise(promises, {
const promise = leaveTeam.mutateAsync();
toast.promise(promise, {
success: "Left team successfully",
error: "Failed to leave team",
});
@@ -308,8 +314,8 @@ export function DeleteTeamCard(props: {
});
function handleDelete() {
const promises = deleteTeam.mutateAsync();
toast.promise(promises, {
const promise = deleteTeam.mutateAsync();
toast.promise(promise, {
success: "Team deleted successfully",
error: "Failed to delete team",
});
@@ -1 +1,40 @@
export const teamSlugRegex = /[^a-zA-Z0-9-]/;
import { z } from "zod";
const teamSlugRegex = /^[a-z0-9]+(?:[-][a-z0-9]+)*$/;
const teamNameRegex = /^[A-Za-z0-9]+(?:[ -][A-Za-z0-9]+)*$/;
export const maxTeamSlugLength = 48;
const minTeamSlugLength = 3;
export const teamSlugSchema = z
.string()
.min(minTeamSlugLength, {
message: `Team slug must be at least ${minTeamSlugLength} characters`,
})
.max(maxTeamSlugLength, {
message: `Team slug must be at most ${maxTeamSlugLength} characters`,
})
.refine((slug) => !slug.includes(" "), {
message: "Team URL cannot contain spaces",
})
.refine((slug) => teamSlugRegex.test(slug), {
message: "Team URL can only contain lowercase letters, numbers and hyphens",
});
export const maxTeamNameLength = 32;
const minTeamNameLength = 3;
export const teamNameSchema = z
.string()
.min(minTeamNameLength, {
message: `Team name must be at least ${minTeamNameLength} characters`,
})
.max(maxTeamNameLength, {
message: `Team name must be at most ${maxTeamNameLength} characters`,
})
.refine((name) => name.trim() === name, {
message: "Team name cannot contain leading or trailing spaces",
})
.refine((name) => teamNameRegex.test(name), {
message: "Team name can only contain letters, numbers, spaces and hyphens",
});
@@ -13,10 +13,10 @@ import {
} from "@/components/ui/table";
import {
ChevronLeftIcon,
ChevronRight,
CreditCard,
Download,
Receipt,
ChevronRightIcon,
CreditCardIcon,
DownloadIcon,
ReceiptIcon,
} from "lucide-react";
import { useQueryState } from "nuqs";
import { useTransition } from "react";
@@ -78,14 +78,14 @@ export function BillingHistory(props: {
if (props.status === "open") {
return (
<div className="py-6 text-center">
<Receipt className="mx-auto h-12 w-12 text-muted-foreground" />
<ReceiptIcon className="mx-auto h-12 w-12 text-muted-foreground" />
<h3 className="mt-2 font-medium text-lg">No open invoices</h3>
</div>
);
}
return (
<div className="py-6 text-center">
<Receipt className="mx-auto h-12 w-12 text-muted-foreground" />
<ReceiptIcon className="mx-auto h-12 w-12 text-muted-foreground" />
<h3 className="mt-2 font-medium text-lg">No billing history</h3>
<p className="mt-1 text-muted-foreground text-sm">
Your invoice history will appear here once you have made payments.
@@ -130,7 +130,7 @@ export function BillingHistory(props: {
target="_blank"
rel="noopener noreferrer"
>
<CreditCard className="mr-2 h-4 w-4 text-muted-foreground" />
<CreditCardIcon className="mr-2 h-4 w-4 text-muted-foreground" />
Pay Now
</a>
</Button>
@@ -143,7 +143,7 @@ export function BillingHistory(props: {
target="_blank"
rel="noopener noreferrer"
>
<Download className="mr-2 h-4 w-4 text-muted-foreground" />
<DownloadIcon className="mr-2 h-4 w-4 text-muted-foreground" />
PDF
</a>
</Button>
@@ -190,7 +190,7 @@ export function BillingHistory(props: {
{isLoading && props.hasMore ? (
<div className="ml-2 h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
) : (
<ChevronRight className="ml-2 h-4 w-4" />
<ChevronRightIcon className="ml-2 h-4 w-4" />
)}
</Button>
</div>
@@ -30,7 +30,7 @@ import {
PlusIcon,
SearchIcon,
Trash2Icon,
UserPlus,
UserPlusIcon,
} from "lucide-react";
import Link from "next/link";
import { useState } from "react";
@@ -183,7 +183,7 @@ export function InviteSection(props: {
{sendInvites.isPending ? (
<Spinner className="size-4" />
) : (
<UserPlus className="size-4" />
<UserPlusIcon className="size-4" />
)}
{form.watch("invites").length > 1
? "Send Invites"
@@ -47,7 +47,9 @@ export function AccountAbstractionLayout(props: {
</TrackedUnderlineLink>
</p>
</div>
{props.hasSmartWalletsWithoutBilling && <SmartWalletsBillingAlert />}
{props.hasSmartWalletsWithoutBilling && (
<SmartWalletsBillingAlert teamSlug={props.teamSlug} />
)}
<GasCreditAlert
teamSlug={props.teamSlug}
projectId={props.projectId}
@@ -27,6 +27,7 @@ export default async function Page(props: {
let fees = await getFees({
clientId: project.publishableKey,
teamId: team.id,
}).catch(() => {
return {
feeRecipient: "",
@@ -358,6 +358,8 @@ export function ProjectGeneralSettingsPageUI(props: {
rotateSecretKey={props.rotateSecretKey}
/>
<ProjectIdCard project={project} />
<AllowedDomainsSetting
form={form}
isUpdatingProject={updateProject.isPending}
@@ -434,6 +436,31 @@ function ProjectNameSetting(props: {
);
}
function ProjectIdCard(props: {
project: Project;
}) {
return (
<SettingsCard
header={{
title: "Project ID",
description: "This is your project's ID on thirdweb",
}}
bottomText="Used when interacting with the thirdweb API"
noPermissionText={undefined}
errorText={undefined}
>
<CopyTextButton
textToCopy={props.project.id}
textToShow={props.project.id}
variant="outline"
className="w-full justify-between truncate bg-background px-3 py-2 font-mono text-muted-foreground lg:w-[450px]"
tooltip="Copy Project ID"
copyIconPosition="right"
/>
</SettingsCard>
);
}
function ProjectImageSetting(props: {
updateProjectImage: (file: File | undefined) => Promise<void>;
avatar: string | null;
@@ -1,5 +1,4 @@
import { getTeamBySlug } from "@/api/team";
import { OpCreditsGrantedModalWrapperServer } from "components/onboarding/OpCreditsGrantedModalWrapperServer";
import { PosthogIdentifierServer } from "components/wallets/PosthogIdentifierServer";
import { redirect } from "next/navigation";
import { Suspense } from "react";
@@ -42,9 +41,6 @@ export default async function RootTeamLayout(props: {
<SaveLastVisitedTeamPage teamId={team.id} />
<Suspense fallback={null}>
<OpCreditsGrantedModalWrapperServer />
</Suspense>
<Suspense fallback={null}>
<EnsureValidConnectedWalletLoginServer />
</Suspense>
+4 -4
View File
@@ -1,10 +1,10 @@
import { getDefaultTeam } from "@/api/team";
import { getLastVisitedTeamOrDefaultTeam } from "@/api/team";
import { notFound, redirect } from "next/navigation";
export default async function TeamRootPage() {
const firstTeam = await getDefaultTeam();
if (!firstTeam) {
const team = await getLastVisitedTeamOrDefaultTeam();
if (!team) {
notFound();
}
redirect(`/team/${firstTeam.slug}`);
redirect(`/team/${team.slug}`);
}
@@ -0,0 +1,125 @@
import { type Team, getTeams } from "@/api/team";
import { GradientAvatar } from "@/components/blocks/Avatars/GradientAvatar";
import { AppFooter } from "@/components/blocks/app-footer";
import { DotsBackgroundPattern } from "@/components/ui/background-patterns";
import { getThirdwebClient } from "@/constants/thirdweb.server";
import { ChevronRightIcon, UsersIcon } from "lucide-react";
import Link from "next/link";
import { notFound, redirect } from "next/navigation";
import { getAuthToken } from "../../../api/lib/getAuthToken";
import { TeamPlanBadge } from "../../../components/TeamPlanBadge";
import { TeamHeader } from "../../components/TeamHeader/team-header";
export default async function Page(props: {
params: Promise<{
paths?: string[];
}>;
searchParams: Promise<Record<string, string | undefined | string[]>>;
}) {
const [params, searchParams, teams, authToken] = await Promise.all([
props.params,
props.searchParams,
getTeams(),
getAuthToken(),
]);
if (!teams || !authToken) {
notFound();
}
const searchParamsString = Object.entries(searchParams)
.map(([key, value]) => {
if (Array.isArray(value)) {
return value
.map((v) => `${key}=${encodeURIComponent(String(v))}`)
.join("&");
}
return `${key}=${encodeURIComponent(String(value))}`;
})
.join("&");
// if there is a single team, redirect to the team page directly
if (teams.length === 1 && teams[0]) {
redirect(
createTeamLink({
team: teams[0],
paths: params.paths,
searchParams: searchParamsString,
}),
);
}
const client = getThirdwebClient({
jwt: authToken,
teamId: undefined,
});
return (
<div className="relative flex min-h-dvh flex-col ">
<div className="border-b bg-card">
<TeamHeader />
</div>
<div className="relative flex grow flex-col overflow-hidden">
<DotsBackgroundPattern />
<div className="container z-10 flex grow flex-col items-center justify-center py-20">
<div className="w-full max-w-lg rounded-xl border bg-card shadow-2xl">
<div className="flex flex-col border-b p-4 lg:p-6">
<div className="mb-2 self-start rounded-full border p-2">
<UsersIcon className="size-5 text-muted-foreground" />
</div>
<h1 className="mb-0.5 font-semibold text-xl tracking-tight">
Select a team
</h1>
<p className="text-muted-foreground text-sm">
You are currently a member of multiple teams
<br />
Select a team to view this page
</p>
</div>
<div className="flex flex-col [&>*:not(:last-child)]:border-b">
{teams.map((team) => {
return (
<div
key={team.id}
className="group relative flex items-center gap-3 px-4 py-4 hover:bg-accent/50 lg:px-6"
>
<GradientAvatar
src={team.image || ""}
id={team.id}
className="size-8 rounded-full border"
client={client}
/>
<Link
href={createTeamLink({
team,
paths: params.paths,
searchParams: searchParamsString,
})}
className="before:absolute before:inset-0"
>
{team.name}
</Link>
<TeamPlanBadge plan={team.billingPlan} />
<ChevronRightIcon className="ml-auto size-4 text-muted-foreground group-hover:text-foreground" />
</div>
);
})}
</div>
</div>
</div>
</div>
<AppFooter className="relative z-10" />
</div>
);
}
function createTeamLink(params: {
team: Team;
paths: string[] | undefined;
searchParams: string | undefined;
}) {
return `/team/${params.team.slug}/${(params.paths || [])?.join("/") || ""}${params.searchParams ? `?${params.searchParams}` : ""}`;
}

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