Compare commits

..
Author SHA1 Message Date
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
7c458eaa49 Version Packages (#7814)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-08 11:04:39 -07:00
gregandGitHub f7ce6becd6 [SDK] Fix: Adds onExecuteTransaction to TransactionPayment (#7816) 2025-08-07 20:27:27 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
52d1ba32c4 Chore(deps): Bump viem from 2.28.1 to 2.33.2 (#7793)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-07 18:31:12 -07:00
saminacodes 1a7315c815 arb-docs (#7799)
<!--

## 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 documentation for thirdweb's `Stylus` contracts, detailing the deployment and usage of `Stylus` contracts and `Airdrop` contracts on the `Arbitrum` network, including benefits, prerequisites, and step-by-step tutorials.

### Detailed summary
- Added a new section on `Contract security` in `page.mdx`.
- Updated `sidebar.tsx` to include collapsible sections for `Arbitrum Stylus` with links to contract deployment guides.
- Created detailed documentation for `Stylus Contracts` and `Stylus Airdrop Contracts` in their respective `page.mdx` files, including benefits, prerequisites, and step-by-step deployment instructions.

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

<!-- end pr-codex -->

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

* **Documentation**
  * Added a new page detailing Stylus Airdrop Contract deployment and usage on Arbitrum, including multiple airdrop patterns and integration examples.
  * Introduced a guide for deploying Stylus contracts on Arbitrum, covering project setup, deployment, and NFT minting.
  * Added a contract security section highlighting third-party audits and linking to audit reports.
  * Updated the sidebar with a new "Arbitrum Stylus" section for easier navigation to Stylus contract guides.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-08 00:48:15 +00:00
gregandGitHub 50c6c193c6 [SDK] Fix: Bridge.tokens domain (#7815) 2025-08-07 16:55:42 -07:00
gregandGitHub e114b2c4f9 [SDK] Fix: Skip payment method selection when balance is sufficient (#7813) 2025-08-07 16:37:53 -07:00
MananTank d812cc370b [MNY-92] Dashboard: Add tokens section in explore (#7812)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on improving the project selection and token creation flow within the dashboard application. It includes the addition of new components, updates to existing ones, and enhancements to user notifications and alerts.

### Detailed summary
- Updated import paths for `project-selector` and `team-selector`.
- Added `TokenBanner` component for displaying token creation options.
- Enhanced `DismissibleAlert` to support conditional rendering based on visibility state.
- Introduced `GenericProjectSelector` for improved project selection.
- Modified `ExplorePage` layout and styling for better presentation.
- Added `TokensSection` component with links for launching tokens and NFTs.
- Updated `reportTokenUpsellClicked` function for event tracking.
- Improved UI elements for better user experience and responsiveness.

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

<!-- end pr-codex -->

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

* **New Features**
  * Introduced a "Tokens" section on the Explore page, showcasing options for creating ERC-20 tokens (Coins) and NFT Collections.
  * Added a promotional token banner on deploy pages for select published token contracts.
  * Launched a new project selection page within the team context, streamlining project navigation and selection.
  * Added a generic project selector component to enhance project selection flexibility.
  * Added tracking for clicks on token creation cards and token upsell banner interactions to improve analytics.

* **Improvements**
  * Enhanced layout and styling of the Explore page for better readability and organization.
  * Updated project and team selector components for more flexible descriptions and improved navigation.
  * Updated team page alert to preserve dismissal state across sessions.

* **Chores**
  * Refined import paths for improved code organization and maintainability.
  * Refactored dismissible alert component to support optional state persistence.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-07 20:45:31 +00:00
MananTank 7883127d51 Dashboard: Add caching to analytics charts (#7804)
<!--

## 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 analytics functionality across various components by adding an `authToken` parameter to API calls, ensuring secure access to analytics data.

### Detailed summary
- Added `authToken` prop to multiple components for secure API access.
- Updated API functions to accept `authToken` for authentication.
- Modified analytics data fetching methods to include `authToken`.
- Changed default values and parameters in several functions for consistency.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

* **New Features**
  * Analytics data fetching now uses caching for improved performance.
  * Authentication is enforced for all analytics-related pages and components, with users redirected to login if not authenticated.

* **Refactor**
  * All analytics API calls and related components now require an explicit authentication token, improving security and consistency.
  * Parameters for analytics functions and components are more consistently structured, often grouped into objects for clarity.

* **Style**
  * Default chart display updated from "totalVolume" to "activeUsers" in highlights cards for a more relevant initial view.

* **Bug Fixes**
  * Standardized error handling across analytics data requests.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-07 14:33:04 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
53d0d74d9b Version Packages (#7810)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-07 02:59:39 -07:00
Joaquim VergesandGitHub c0948fb9fe [SDK] Handle environments where process is not defined (#7809) 2025-08-07 02:48:44 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
9ae2991ad7 Version Packages (#7806)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-06 19:24:10 -07:00
MananTank bab4ff5e14 Dashboard: Improved chart responsiveness, code cleanup (#7801)
<!--

## 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 analytics components by introducing responsive time filters, updating chart components to support new props, and refactoring existing code for improved clarity and functionality.

### Detailed summary
- Removed `range-selector.tsx` and refactored `AnalyticsHeader.tsx`.
- Added `ResponsiveTimeFilters` for better time range handling.
- Updated `EcosystemWalletsSummary` and `EcosystemAnalyticsPage` to use new props.
- Refactored various chart components to accept `selectedChart` and `selectedChartQueryParam`.
- Enhanced `AsyncTransactionsChartCard` and `AsyncTotalSponsoredCard` for better data handling.
- Replaced `LoadingChartState` with responsive loading states across components.
- Consolidated logic for handling time series data in analytics cards.
- Improved type definitions for better clarity and consistency.

> The following files were skipped due to too many changes: `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/page.tsx`

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

<!-- end pr-codex -->

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

* **New Features**
  * Introduced responsive and modular time filter controls for analytics dashboards.
  * Added new highlights cards for team and project analytics, providing aggregated metrics in a combined bar chart format.
  * Added a new combined bar chart card for gas sponsorship data with dynamic chart selection.
  * Added components that map transaction and sponsorship data with chain metadata for improved analytics detail.

* **Refactor**
  * Replaced multiple analytics and chart components with streamlined, asynchronous, and responsive variants.
  * Standardized date range and interval filtering using a centralized utility and responsive providers.
  * Updated chart selection to use in-app state and callbacks instead of URL-based navigation.
  * Simplified analytics page layouts by replacing Suspense with responsive suspense components.
  * Externalized data processing and state management from several chart card components to improve modularity.
  * Removed deprecated components and replaced them with updated responsive and async implementations.

* **Bug Fixes**
  * Improved handling of date ranges and intervals, ensuring accurate and consistent filtering.

* **Chores**
  * Removed unused or redundant components and consolidated stories for better maintainability.
  * Cleaned up imports and type definitions across analytics and project overview pages.

* **Style**
  * Enhanced button and layout styling for chart selection and analytics cards.

* **Documentation**
  * Updated interactive stories for analytics components to improve clarity and usability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-07 01:24:29 +00:00
621c1de62e [SDK] Add buttonLabel prop to checkout widgets (#7805)
Co-authored-by: Cursor Agent <[email protected]>
2025-08-06 18:23:53 -07:00
Joaquim VergesandGitHub 8bb2b0a227 [Docs] Update pregenerate wallets API endpoint and request format (#7791) 2025-08-06 13:49:16 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
5f82df1652 Version Packages (#7776)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-06 12:17:01 -07:00
MananTank fd4414b451 [BLD-71] SDK, Dashboard: Fix Supplies shown in ERC721 contract (#7803)
<!--

## 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 `getTotalClaimedSupply` and `getTotalUnclaimedSupply` functions in the ERC721 extensions to account for `startTokenId`, improving the accuracy of token supply calculations.

### Detailed summary
- Modified `getTotalClaimedSupply` to subtract `startTokenId` from `nextTokenIdToClaim`.
- Updated `getTotalUnclaimedSupply` to subtract `startTokenId` and `totalClaimedSupply` from `nextTokenIdToMint`.
- Changed imports in `supply-cards.tsx` to use the new supply functions.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

* **Refactor**
  * Improved supply calculations in NFT dashboards by directly using contract data instead of manual computations.
  * Adjusted logic to accurately account for token ID offsets when displaying claimed and unclaimed NFT supply.

* **Bug Fixes**
  * Resolved inconsistencies in claimed and unclaimed NFT supply values when contracts use a custom starting token ID.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-06 18:53:42 +00:00
MananTank d10436d3d2 Dashboard: Add tracking for fund wallet modal (#7800)
<!--

## 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 analytics tracking for the `FundWalletModal` component by adding new reporting functions and integrating them into the wallet funding process.

### Detailed summary
- Added `reportFundWalletOpened` function to track when the fund wallet modal is opened.
- Integrated `reportFundWalletOpened` call in `BackendWalletsTable` when the fund wallet button is clicked.
- Added `reportFundWalletSuccessful` and `reportFundWalletFailed` functions to track successful and failed fund wallet transactions.
- Integrated success and error reporting in the `FundWalletModal` component.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

* **New Features**
  * Added analytics tracking for opening the fund wallet modal, successful wallet funding, and failed wallet funding attempts, including error reporting.
  * Enhanced the fund wallet experience with event reporting on user actions for improved insights.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-06 17:37:19 +00:00
MananTank 56b12c6094 Dashboard: export the date in CSV as ISO string in wallets/users table, UI tweaks (#7802)
<!--

## 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 `AdvancedSearchInput` component by adding a clear button, improving the layout, and integrating a loading spinner. It also updates the `InAppWalletUsersPageContent` to include a tooltip for user identifiers and modifies the display of created dates.

### Detailed summary
- Added `XIcon` to `AdvancedSearchInput` for a clear button.
- Integrated `Spinner` for loading state in `AdvancedSearchInput`.
- Improved layout with responsive design adjustments.
- Added `ToolTipLabel` for user identifier display in `InAppWalletUsersPageContent`.
- Changed date format to ISO in `InAppWalletUsersPageContent`.
- Updated button styles for better UI consistency.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

* **New Features**
  * Added an inline clear button ("X" icon) to the search input, allowing users to clear queries directly.
  * User identifiers now feature a copy-to-clipboard button with truncation and tooltip support.
  * Wallet creation dates now display detailed timestamps in a tooltip on hover.

* **Improvements**
  * Enhanced responsive layout for search input and controls, optimizing usability across devices.
  * Updated button styles and input appearance for a more cohesive look.
  * CSV export now uses ISO date format for wallet creation dates.
  * Improved font size and styling for wallet labels and download button.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-06 17:27:06 +00:00
abbdb0f113 Rename archive to advanced sidebar (#7798)
Co-authored-by: Cursor Agent <[email protected]>
2025-08-05 17:37:25 -07:00
MananTank d05dbcb5ac Dashboard: enforce absolute imports for '@' folder (#7796)
<!--

## 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 adds a new ESLint rule to enforce the use of absolute imports in the `apps/dashboard/.eslintrc.js` configuration file.

### Detailed summary
- Introduced a `patterns` array to the ESLint configuration.
- Added a rule that restricts the use of relative imports, prompting users to use absolute imports instead.
- Provided an example of the correct import format: `'@/foo/bar'`.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

* **Chores**
  * Updated code style rules to enforce the use of absolute imports instead of certain relative import patterns.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-05 23:29:57 +00:00
Jonas DanielsandGitHub 3a52f9d486 Add posthog.com and framer.com to CSP (#7797) 2025-08-05 16:26:26 -07:00
Joaquim VergesandGitHub 9a3ebe1cb1 [SDK] Add transaction hash to max wait time error (#7789) 2025-08-04 17:17:05 -07:00
MananTank ad2d22558b Dashboard: File organization changes (#7788)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

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

* **New Features**
  * Added a hook to fetch favorite chain IDs for user accounts.
  * Introduced contract verification functionality.
  * Added UI for saving deployed contracts to projects.
  * Provided new lightweight types for teams and projects.

* **Bug Fixes**
  * Replaced NFT media cell rendering in marketplace tables for improved display and interaction.

* **Refactor**
  * Standardized and reorganized import paths across the app for improved maintainability.
  * Renamed and updated fieldset components in contract deployment forms for consistency.
  * Centralized and simplified cookie and authentication token handling.
  * Removed or relocated obsolete and redundant components and types.

* **Chores**
  * Cleaned up unused files and consolidated constants.
  * Updated and clarified type imports throughout the codebase.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-04 21:37:15 +00:00
MananTank 0aac9a8593 Dashboard: dedicated engine header UI tweak on mobile, Tab component tweaks (#7787)
<!--

## 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 `TabButtons` component across various files by modifying class names and adjusting properties to improve styling and functionality.

### Detailed summary
- Updated `tabClassName` for `TabButtons` in multiple files to enhance styling.
- Changed `tabContainerClassName` in several instances to adjust layout.
- Added `activeTabClassName` in certain components to manage active tab styles.
- Refined `isActive` logic for better tab state management.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

* **Style**
  * Updated tab button styling across multiple areas of the dashboard for a more consistent appearance, removing explicit small text overrides and adjusting layout and padding.
  * Improved header layout and button appearance in the transactions section for better responsiveness and visual clarity.
  * Enhanced code snippet formatting in API usage examples for readability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-04 20:47:42 +00:00
saminacodes fa7a81260d added wallets seo, fixed faqs, added deployment guides (#7786)
<!--

## 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 documentation and content related to thirdweb's tokens, wallets, and contracts. It enhances clarity, adds new features, and improves user guidance across various sections.

### Detailed summary
- Added new images for token deployment and wallet features.
- Updated titles and descriptions for clarity in multiple `.mdx` files.
- Introduced new sections and links in the sidebar for better navigation.
- Enhanced FAQs with specific details about tokens and wallet security.
- Added guides for deploying ERC-20 and ERC-721 tokens.
- Updated resource links and documentation structure for improved user experience.

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

<!-- end pr-codex -->

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

* **New Features**
  * Added an "Insight" navigation link to the main header.
  * Introduced new documentation pages: Modular Contracts overview, ERC-20, ERC-721, and ERC-1155 token deployment guides, token listing placeholder, and troubleshooting guides for contracts.
  * Added new FAQ pages for contracts and expanded FAQs for payments, tokens, and wallets.

* **Documentation**
  * Improved and expanded metadata (titles, descriptions, images) for multiple documentation pages for better clarity and SEO.
  * Updated and restructured sidebar navigation for Contracts, Tokens, Transactions, and Wallets sections, including new links, reorganized groups, and resource sections.
  * Enhanced FAQ formatting with collapsible sections and improved content clarity across several pages.
  * Streamlined and updated content in various wallet and contract documentation pages.

* **Style**
  * Improved documentation structure and presentation, including use of collapsible details and better grouping of related topics.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-04 17:54:57 +00:00
Alec AnanianandGitHub e5326c43a9 [SDK] Feat: export useSiweAuth for React Native (#7785) 2025-08-03 10:41:21 +12:00
MananTank 57862d0bd0 Playground: Improved sidebar search params suspense fallback, Fix ConnectEmbed width on mobile (#7784)
<!--

## 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 layout responsiveness and improving the rendering of sidebar components by integrating full path management, along with some styling adjustments.

### Detailed summary
- Added `className="!max-w-full"` to various components for responsive design.
- Wrapped `StyledConnectEmbed` in a div with overflow and padding.
- Changed heading and paragraph styles in `APIHeader`.
- Introduced `FullPathSuspense` for managing full path rendering.
- Modified `NavLink` to accept `fullPath` prop.
- Updated sidebar components to use `fullPath` for link matching.
- Removed unnecessary `Suspense` wrappers from sidebar rendering functions.

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

<!-- end pr-codex -->

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

* **New Features**
  * Introduced a component that enables rendering content based on the full current URL path, including query parameters.

* **Refactor**
  * Updated sidebar and navigation components to receive the current path as a prop, simplifying active link determination and layout logic.
  * Consolidated suspense handling for sidebar components to a single top-level boundary.

* **Style**
  * Improved visual layout and spacing for embedded components and page headers.
  * Enhanced responsiveness and maximum width handling for embedded connect components.

* **Documentation**
  * Updated type names and component signatures for improved clarity.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-02 20:16:05 +00:00
MananTank 834bbc0883 [MNY-62] Dashboard: Add CheckoutWidget in server wallets & backend wallets table. server wallets UI improvements (#7782)
<!--

## 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 dashboard application with various improvements, including dependency updates, UI adjustments, and the introduction of new components for better wallet management.

### Detailed summary
- Removed `react-qrcode-logo` from dependencies.
- Changed `tabNames` in `IntegrateAPIKeyCodeTabs.tsx`.
- Updated button structure in `layout.tsx` for better UI.
- Adjusted styles in `wallet-address.tsx` for consistency.
- Modified `PaymentLinksTable.client.tsx` button variants and sizes.
- Removed unused imports in `page.tsx`.
- Enhanced `Switch` component with size prop.
- Added `FundWalletModal` component with form validation.
- Updated `ServerWalletsTableUI` with improved wallet display and functionality.
- Introduced `WalletActionsDropdown` for wallet actions.

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

<!-- end pr-codex -->

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

* **New Features**
  * Introduced a modal for funding wallets, allowing users to select chain, token, and amount, and complete payments via a checkout flow.
  * Added chain selection and a toggle to display ERC4337 Smart Accounts in the server wallets table.
  * Enhanced wallet actions with a dropdown menu for sending test transactions and funding wallets.

* **UI Improvements**
  * Updated wallet and smart account tables with improved layout, balance refresh, and modular components.
  * Refined button and modal styles across various components for a more consistent look and feel.
  * Adjusted the size of wallet avatars and icons for better visual balance.
  * Simplified and clarified descriptive text in payment links and wallet creation dialogs.
  * Improved switch component with a smaller size variant option.
  * Modified tab order in API key integration for better user flow.
  * Updated transaction page button styling and composition.

* **Bug Fixes**
  * Removed the QR code-based receive funds modal, replacing it with the new funding modal.

* **Chores**
  * Removed an unused dependency related to QR code generation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-02 02:24:48 +00:00
MananTank 3a9aa1a72b Dashboard: Fix Payment link buy completion tracking (#7783)
<!--

## 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 simplifying the `reportPaymentLinkBuySuccessful` and `reportPaymentLinkBuyFailed` functions by removing unnecessary parameters and adjusting their calls accordingly.

### Detailed summary
- Removed `linkId` and `clientId` parameters from the `reportPaymentLinkBuySuccessful` function.
- Adjusted the call to `reportPaymentLinkBuySuccessful` in `PayPageWidget.client.tsx` to no longer pass parameters.
- Simplified the call to `reportPaymentLinkBuyFailed` by removing `linkId` and `clientId` parameters and retaining only `errorMessage`.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

* **Refactor**
  * Streamlined payment reporting by simplifying success and failure event tracking. Payment success is now always reported without additional details, and payment failure reports only include the error message. No impact on payment or redirect flows.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-02 02:10:19 +00:00
Joey BrightandGitHub 572368edc3 chore: fix typos in docs (#7777) 2025-08-01 16:52:45 -07:00
MananTank 9b5ca42091 Dashboard: Remove unused envs from .env.example and turbo config (#7779)
<!--

## 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 cleaning up the environment variables in the `turbo.json` and `.env.example` files, removing several unused keys and updating comments for clarity.

### Detailed summary
- Removed multiple environment variables from `turbo.json`.
- Updated comments and reorganized sections in `apps/dashboard/.env.example`.
- Added `NEXT_PUBLIC_THIRDWEB_VAULT_URL` and `NEXT_PUBLIC_ENGINE_CLOUD_URL` as required.
- Clarified purpose for `DASHBOARD_SECRET_KEY` and `API_ROUTES_CLIENT_ID`.

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

<!-- end pr-codex -->

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

* **Chores**
  * Cleaned up and reorganized environment variable examples for improved clarity and usability.
  * Removed deprecated and unused environment variables.
  * Updated placeholder formatting and improved instructions for setting required variables.
  * Simplified and reduced the environment variable list used during dashboard build tasks.
  * Added new environment variables for insight service and Stripe integration.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-01 20:59:25 +00:00
MananTank c6849a8734 [MNY-61] Dashboard: Tokens and Payments tracking updates (#7781)
<!--

## 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 payment link functionality and analytics reporting within the application. It introduces new hooks, modifies existing components, and refines analytics events to improve tracking and user experience.

### Detailed summary
- Added `useEffectOnce` hook for single execution of effects.
- Implemented `AssetPageView` component to report asset page views.
- Updated `CreatePaymentLinkButton` to report link creation.
- Enhanced payment link reporting with success and failure events.
- Removed unused analytics functions related to asset creation.

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

<!-- end pr-codex -->
2025-08-01 20:09:00 +00:00
gregandGitHub 7617cc84fc [SDK] Fix: Respect supported tokens in payment widgets (#7780) 2025-08-01 13:03:24 -07:00
MananTank e1ad7daf54 Dashboard: Remove Moralis, Alchemy API usage, Split UI improvements, Fix thirdweb/insight import (#7774)
<!--

## 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 refactoring the wallet NFT handling and related functionalities, transitioning from using `Moralis` and `Alchemy` to utilizing `thirdweb`'s `insight`. It also includes the removal of deprecated code and updates to the types used for NFTs.

### Detailed summary
- Deleted multiple files related to `Moralis` and `Alchemy`.
- Updated the `package.json` to fix TypeScript errors for `thirdweb/insight`.
- Refactored `NFTDetails.tsx` to map `displayableNFTs` with new properties.
- Removed the `MORALIS_API_KEY` from the environment and codebase.
- Changed `useWalletNFTs` to `useOwnedNFTsInsight` for fetching NFTs.
- Updated `NFTCards.tsx` to accept a new type `OwnedNFT`.
- Modified `useSplit.ts` to fetch owned tokens using `thirdweb` instead of `Moralis`.
- Adjusted `CreateListingsForm` to use `OwnedNFT` type and updated logic accordingly.
- Updated `ContractSplitPage` to utilize `useOwnedTokenBalances` for fetching balances.
- Removed legacy handling for unsupported chains and adjusted UI components accordingly.

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

<!-- end pr-codex -->

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

* **New Features**
  * Unified token balance retrieval now uses Thirdweb's insight API, providing both native and ERC-20 token balances in a single view.
  * Account and contract split pages display balances in a streamlined table format, improving clarity and consistency.

* **Improvements**
  * Enhanced error handling and more informative notifications during balance distribution actions.
  * Updated logic for filtering and displaying balances, ensuring more accurate and reliable information.
  * Simplified chain support checks to rely solely on Insight API, removing Moralis and Alchemy fallbacks.
  * NFT ownership fetching now exclusively uses the Insight API with fallback to RPC data, improving reliability and simplifying support.

* **Bug Fixes**
  * Resolved inconsistencies in balance display and distribution eligibility.

* **Chores**
  * Removed deprecated Moralis and Alchemy NFT and token balance integrations.
  * Updated internal type configurations to support new balance retrieval methods.
  * Cleaned up environment variable usage by removing obsolete API keys.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-01 01:20:30 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
6ad015effb Version Packages (#7773)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-31 17:22:00 -07:00
Joaquim VergesandGitHub b86d520f3c [SDK] Handle mobile deeplinks for WalletConnect transactions (#7775) 2025-07-31 16:01:18 -07:00
Manan TankandGitHub 2df891f380 Playground: Revamp (#7768) 2025-07-31 15:37:13 -07:00
Alec AnanianandGitHub 58787a28f3 [SDK] Fix useContractEvents persisting previous data when filters change (#7766) 2025-07-31 15:34:29 -07:00
Joaquim VergesandGitHub e4ba47bd1e [SDK] Fix decodeFunction and decodeError functions (#7767) 2025-07-31 15:33:02 -07:00
Joaquim VergesandGitHub 710ac32b2f [SDK] Add WalletConnect deep linking support (#7772) 2025-07-31 14:42:22 -07:00
gregandGitHub 6258f7429c Fix: Bridge Token Examples (#7771) 2025-07-31 13:34:14 -07:00
gregandGitHub 943cd1bbcf Chore: Payment links tracking (#7769) 2025-07-31 12:23:59 -07:00
MananTank ed65f4cbe7 Portal: Move Chat UI in floating widget (#7765)
<!--

## 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 chat functionality in the application by introducing a new `ChatButton` component, improving code structure, and refining the chat interface with better loading states and animations.

### Detailed summary
- Removed the `Button` component linking to `/chat` in favor of `ChatButton`.
- Introduced `ChatButton` component with modal functionality for the chat interface.
- Added a `QueryClientProvider` to manage chat state.
- Improved conditional rendering and loading states in chat messages.
- Enhanced CSS animations for chat components.
- Updated key usage in `CodeClient` mapping to ensure unique keys.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

* **New Features**
  * Introduced a new "Ask AI" button that opens a modal chat interface, accessible across the portal.
  * The chat modal includes options to reset the conversation and close the modal, with a loading spinner for improved user experience.

* **Improvements**
  * Enhanced chat interface with animated message appearance and a more precise scroll-to-latest-message effect.
  * Updated code snippet rendering in authentication tabs to ensure unique keys for better performance and reliability.

* **Bug Fixes**
  * Resolved potential issues with rendering multiple code snippets for the same platform by ensuring unique keys.

* **Refactor**
  * Replaced previous "Ask AI" button implementations with the new unified ChatButton component for consistency.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-30 22:22:35 +00:00
MananTank 0483776825 [BLD-26] Portal: AI Chat UI improvements (#7764)
<!--

## 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 UI components and improving the styling, particularly around the `ScrollShadow` component. It also introduces new animations and adjusts various layouts for better responsiveness.

### Detailed summary
- Removed `ScrollShadow.tsx` and its CSS module.
- Updated color in `NextTopLoader` from `--link-foreground` to `--foreground`.
- Added `sonner` package for toasts.
- Integrated `ScrollShadow` in multiple components.
- Adjusted layout styles in `page.tsx` and `MarkdownRenderer.tsx`.
- Enhanced `Chat` component with `ScrollShadow` for better scrolling behavior.
- Introduced `TextShimmer` and `UnderlineLink` components.
- Improved styling and responsiveness in various components.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

* **New Features**
  * Introduced a shimmering text effect for loading states.
  * Added a customizable toast notification system with theme support.
  * Added an underlined link component for consistent link styling.

* **Enhancements**
  * Refined chat interface with improved empty state, message rendering, feedback handling, and visual distinction between user and AI messages.
  * Updated markdown rendering for improved link, heading, list, and paragraph styles.
  * Improved icon rendering with unique ID management and monochrome support.
  * Adjusted chat page and loader bar styling for better layout and color consistency.

* **Bug Fixes**
  * Corrected React key usage in code snippet rendering to use unique platform IDs.

* **Refactor**
  * Modularized chat message rendering and feedback logic.
  * Updated import paths for scroll shadow components across multiple files.

* **Chores**
  * Updated dependencies to include the "sonner" package.
  * Cleaned up whitespace, formatting, and removed unused lint comments.
  * Adjusted Tailwind configuration for border radius and added shimmer animation.

* **Removals**
  * Deleted legacy scroll shadow component and associated CSS.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-30 19:18:10 +00:00
MananTank cd142f9ff5 [BLD-28] Dashboard: Fix error style in contract explorer (#7763)
<!--

## 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 error display section within the `InteractiveAbiFunction` component to enhance readability and styling.

### Detailed summary
- Removed the import of `InlineCode`.
- Changed the error display from a fragment (`<>`) to a `div`.
- Updated the error heading to include a bottom margin (`mb-2`).
- Replaced `InlineCode` with `PlainTextCodeBlock` for error formatting.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

* **Style**
  * Updated the visual appearance of error messages in the interactive ABI function section for improved readability, using a new code block style with enhanced formatting.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-30 19:01:16 +00:00
699 changed files with 11734 additions and 6625 deletions
+20 -54
View File
@@ -1,29 +1,22 @@
#
# Public (client)
#
# API authorized domain
# not required to build, defaults to prod
NEXT_PUBLIC_THIRDWEB_DOMAIN="localhost:3000"
# ------------------------------------------------------------------------
# API host. For local development, please use "https://api.thirdweb-dev.com"
# otherwise: "https://api.thirdweb.com"
# local host - http://127.0.0.1:3005
NEXT_PUBLIC_THIRDWEB_API_HOST="https://api.thirdweb-dev.com"
# Bridge API. For local development, please use "https://bridge.thirdweb-dev.com"
# otherwise: "https://bridge.thirdweb.com"
NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST="https://bridge.thirdweb-dev.com"
# Paper API host
# in-app wallet host
NEXT_PUBLIC_THIRDWEB_EWS_API_HOST="https://ews.thirdweb-dev.com"
# thirdweb local api host
# NEXT_PUBLIC_THIRDWEB_API_HOST="http://127.0.0.1:3005"
# Client ID
# Get the client id from https://thirdweb.com/create-api-key and set it here,
# Client ID - Required
# Get the client id from https://thirdweb.com/create-api-key and set it here, and also set DASHBOARD_SECRET_KEY below
# make sure to allow localhost:3000 in the allowed origins
NEXT_PUBLIC_DASHBOARD_CLIENT_ID="<replace_me_with_your_client_id>"
NEXT_PUBLIC_DASHBOARD_CLIENT_ID=""
# IPFS gateway url
NEXT_PUBLIC_IPFS_GATEWAY_URL="https://{clientId}.thirdwebstorage-dev.com/ipfs/{cid}/{path}"
@@ -39,62 +32,35 @@ NEXT_PUBLIC_TYPESENSE_CONTRACT_API_KEY=
# - not required for prod/staging
NEXT_PUBLIC_POSTHOG_KEY=""
# Stripe Customer portal
NEXT_PUBLIC_STRIPE_KEY=
# required for server wallet management
NEXT_PUBLIC_THIRDWEB_VAULT_URL=""
NEXT_PUBLIC_ENGINE_CLOUD_URL=""
# Demo Engine - required for showing demo engine page
NEXT_PUBLIC_DEMO_ENGINE_URL=""
NEXT_PUBLIC_STRIPE_PAYMENT_METHOD_CFG_ID=
#
# Private (server)
#
# Get the secret key from https://thirdweb.com/create-api-key and set it here, Make sure to allow localhost:3000 in the allowed origins
# ------------------------------------------------------------------------
# Get the secret key from https://thirdweb.com/create-api-key and set it here and also set NEXT_PUBLIC_DASHBOARD_CLIENT_ID above,
# Make sure to allow localhost:3000 in the allowed origins
DASHBOARD_SECRET_KEY="<replace_me_with_your_secret_key>"
# Client id for api routes
# Client id for api routes - required for contract OG image generation
API_ROUTES_CLIENT_ID=
# <moralis.io API key (used for api routes for token balances and wallet NFTs)
# - not required to build (unless using wallet NFTs and token balances)>
MORALIS_API_KEY=
# alchemy.com API key (used for wallet NFTS)
# - cannot be restricted to IP/domain because vercel has no stable IPs and it happens during build & runtime api route call
# - not required to build (unless testing wallet NFTs)>
SSR_ALCHEMY_KEY=
# Hubspot Access Token (used for contact us form)
# - not required to build (unless testing contact us form)>
HUBSPOT_ACCESS_TOKEN=
# Upload server url
NEXT_PUBLIC_DASHBOARD_UPLOAD_SERVER="https://storage.thirdweb-preview.com"
# Unthread variables - only required for submitting the support form in /support
UNTHREAD_API_KEY=""
UNTHREAD_TRIAGE_CHANNEL_ID=""
UNTHREAD_EMAIL_INBOX_ID=""
UNTHREAD_FREE_TIER_ID=""
UNTHREAD_GROWTH_TIER_ID=""
UNTHREAD_PRO_TIER_ID=""
# Demo Engine
NEXT_PUBLIC_DEMO_ENGINE_URL=""
# API server secret (required for thirdweb.com SIWE login). Copy from Vercel.
API_SERVER_SECRET=""
# Used for the Faucet page (/<chain_id>)
# Used in faucet on chain page (/<chain-id>) and login page (/login) - turnstile is disabled in localhost development
NEXT_PUBLIC_TURNSTILE_SITE_KEY=""
TURNSTILE_SECRET_KEY=""
REDIS_URL=""
ANALYTICS_SERVICE_URL=""
# required for billing parts of the dashboard (team -> settings -> billing / invoices)
STRIPE_SECRET_KEY=""
# required for server wallet management
NEXT_PUBLIC_THIRDWEB_VAULT_URL=""
NEXT_PUBLIC_ENGINE_CLOUD_URL=""
+6
View File
@@ -96,6 +96,12 @@ module.exports = {
name: "posthog-js",
},
],
patterns: [
{
group: ["**/../@/**"],
message: "Use absolute imports instead. Example: '@/foo/bar..'",
},
],
},
],
"no-restricted-syntax": [
+2 -2
View File
@@ -9,10 +9,10 @@ const ContentSecurityPolicy = `
img-src * data: blob:;
media-src * data: blob:;
object-src 'none';
style-src 'self' 'unsafe-inline' vercel.live;
style-src 'self' 'unsafe-inline' vercel.live us.posthog.com;
font-src 'self' vercel.live assets.vercel.com framerusercontent.com fonts.gstatic.com;
frame-src * data:;
script-src 'self' 'unsafe-eval' 'unsafe-inline' 'wasm-unsafe-eval' 'inline-speculation-rules' *.thirdweb.com *.thirdweb-dev.com vercel.live js.stripe.com framerusercontent.com events.framer.com challenges.cloudflare.com googletagmanager.com us-assets.i.posthog.com edit.framer.com;
script-src 'self' 'unsafe-eval' 'unsafe-inline' 'wasm-unsafe-eval' 'inline-speculation-rules' *.thirdweb.com *.thirdweb-dev.com vercel.live js.stripe.com framerusercontent.com events.framer.com challenges.cloudflare.com googletagmanager.com us-assets.i.posthog.com edit.framer.com framer.com googletagmanager.com;
connect-src * data: blob:;
worker-src 'self' blob:;
block-all-mixed-content;
+1 -2
View File
@@ -57,7 +57,6 @@
"react-error-boundary": "6.0.0",
"react-hook-form": "7.55.0",
"react-markdown": "10.1.0",
"react-qrcode-logo": "^3.0.0",
"react-table": "^7.8.0",
"recharts": "2.15.3",
"remark-gfm": "4.0.1",
@@ -130,4 +129,4 @@
"update-checkly": "npx checkly deploy"
},
"version": "3.0.0"
}
}
@@ -1,6 +1,6 @@
"use server";
import { getRawAccount } from "../../app/(app)/account/settings/getAccount";
import { getRawAccount } from "@/api/account/get-account";
export async function getRawAccountAction() {
return getRawAccount();
@@ -1,89 +0,0 @@
"use server";
import { isAddress, toTokens, ZERO_ADDRESS } from "thirdweb";
import { getWalletBalance } from "thirdweb/wallets";
import { MORALIS_API_KEY } from "@/constants/server-envs";
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
import { defineDashboardChain } from "@/lib/defineDashboardChain";
type BalanceQueryResponse = Array<{
balance: string;
decimals: number;
name?: string;
symbol: string;
token_address: string;
display_balance: string;
}>;
export async function getTokenBalancesFromMoralis(params: {
contractAddress: string;
chainId: number;
}): Promise<
| { data: BalanceQueryResponse; error: undefined }
| {
data: undefined;
error: string;
}
> {
const { contractAddress, chainId } = params;
if (!isAddress(contractAddress)) {
return {
data: undefined,
error: "invalid address",
};
}
const getNativeBalance = async (): Promise<BalanceQueryResponse> => {
// eslint-disable-next-line no-restricted-syntax
const chain = defineDashboardChain(chainId, undefined);
const balance = await getWalletBalance({
address: contractAddress,
chain,
client: serverThirdwebClient,
});
return [
{
balance: balance.value.toString(),
decimals: balance.decimals,
display_balance: toTokens(balance.value, balance.decimals),
name: "Native Token",
symbol: balance.symbol,
token_address: ZERO_ADDRESS,
},
];
};
const getTokenBalances = async (): Promise<BalanceQueryResponse> => {
const _chain = encodeURIComponent(`0x${chainId?.toString(16)}`);
const _address = encodeURIComponent(contractAddress);
const tokenBalanceEndpoint = `https://deep-index.moralis.io/api/v2/${_address}/erc20?chain=${_chain}`;
const resp = await fetch(tokenBalanceEndpoint, {
headers: {
"x-api-key": MORALIS_API_KEY,
},
method: "GET",
});
if (!resp.ok) {
resp.body?.cancel();
return [];
}
const json = await resp.json();
// biome-ignore lint/suspicious/noExplicitAny: FIXME
return json.map((balance: any) => ({
...balance,
display_balance: toTokens(BigInt(balance.balance), balance.decimals),
}));
};
const [nativeBalance, tokenBalances] = await Promise.all([
getNativeBalance(),
getTokenBalances(),
]);
return {
data: [...nativeBalance, ...tokenBalances],
error: undefined,
};
}
@@ -1,210 +0,0 @@
"use server";
import { NEXT_PUBLIC_DASHBOARD_CLIENT_ID } from "@/constants/public-envs";
import { MORALIS_API_KEY } from "@/constants/server-envs";
import {
generateAlchemyUrl,
transformAlchemyResponseToNFT,
} from "@/lib/wallet/nfts/alchemy";
import {
generateMoralisUrl,
transformMoralisResponseToNFT,
} from "@/lib/wallet/nfts/moralis";
import type { WalletNFT } from "@/lib/wallet/nfts/types";
import { getVercelEnv } from "@/utils/vercel";
import { isAlchemySupported } from "../lib/wallet/nfts/isAlchemySupported";
import { isMoralisSupported } from "../lib/wallet/nfts/isMoralisSupported";
type WalletNFTApiReturn =
| { result: WalletNFT[]; error?: undefined }
| { result?: undefined; error: string };
export async function getWalletNFTs(params: {
chainId: number;
owner: string;
isInsightSupported: boolean;
}): Promise<WalletNFTApiReturn> {
const { chainId, owner } = params;
if (params.isInsightSupported) {
const response = await getWalletNFTsFromInsight({ chainId, owner });
if (!response.ok) {
return {
error: response.error,
};
}
return { result: response.data };
}
if (isAlchemySupported(chainId)) {
const url = generateAlchemyUrl({ chainId, owner });
const response = await fetch(url, {
next: {
revalidate: 10, // cache for 10 seconds
},
});
if (response.status >= 400) {
return { error: response.statusText };
}
try {
const parsedResponse = await response.json();
const result = await transformAlchemyResponseToNFT(parsedResponse, owner);
return { error: undefined, result };
} catch (err) {
console.error("Error fetching NFTs", err);
return { error: "error parsing response" };
}
}
if (isMoralisSupported(chainId) && MORALIS_API_KEY) {
const url = generateMoralisUrl({ chainId, owner });
const response = await fetch(url, {
headers: {
"X-API-Key": MORALIS_API_KEY,
},
method: "GET",
next: {
revalidate: 10, // cache for 10 seconds
},
});
if (response.status >= 400) {
return { error: response.statusText };
}
try {
const parsedResponse = await response.json();
const result = await transformMoralisResponseToNFT(
await parsedResponse,
owner,
chainId,
);
return { result };
} catch (err) {
console.error("Error fetching NFTs", err);
return { error: "error parsing response" };
}
}
return { error: "unsupported chain" };
}
type OwnedNFTInsightResponse = {
name: string;
description: string;
image_url?: string;
background_color: string;
external_url: string;
metadata_url: string;
extra_metadata: {
customImage?: string;
customAnimationUrl?: string;
animation_original_url?: string;
image_original_url?: string;
};
collection: {
name: string;
description: string;
extra_metadata: Record<string, string>;
};
contract: {
chain_id: number;
address: string;
type: "erc1155" | "erc721";
name: string;
};
owner_addresses: string[];
token_id: string;
balance: string;
token_type: "erc1155" | "erc721";
};
async function getWalletNFTsFromInsight(params: {
chainId: number;
owner: string;
}): Promise<
| {
data: WalletNFT[];
ok: true;
}
| {
ok: false;
error: string;
}
> {
const { chainId, owner } = params;
const thirdwebDomain =
getVercelEnv() === "production" ? "thirdweb" : "thirdweb-dev";
const url = new URL(`https://insight.${thirdwebDomain}.com/v1/nfts`);
url.searchParams.append("chain", chainId.toString());
url.searchParams.append("limit", "10");
url.searchParams.append("owner_address", owner);
const response = await fetch(url, {
headers: {
"x-client-id": NEXT_PUBLIC_DASHBOARD_CLIENT_ID,
},
});
if (!response.ok) {
const errorMessage = await response.text();
return {
error: errorMessage,
ok: false,
};
}
const nftsResponse = (await response.json()) as {
data: OwnedNFTInsightResponse[];
};
const isDev = getVercelEnv() !== "production";
// NOTE: ipfscdn.io/ to thirdwebstorage-dev.com/ replacement is temporary
// This should be fixed in the insight dev endpoint
const walletNFTs = nftsResponse.data.map((nft) => {
const walletNFT: WalletNFT = {
chainId: nft.contract.chain_id,
contractAddress: nft.contract.address,
id: nft.token_id,
metadata: {
animation_url: isDev
? nft.extra_metadata.animation_original_url?.replace(
"ipfscdn.io/",
"thirdwebstorage-dev.com/",
)
: nft.extra_metadata.animation_original_url,
background_color: nft.background_color,
description: nft.description,
external_url: nft.external_url,
image: isDev
? nft.image_url?.replace("ipfscdn.io/", "thirdwebstorage-dev.com/")
: nft.image_url,
name: nft.name,
uri: isDev
? nft.metadata_url.replace("ipfscdn.io/", "thirdwebstorage-dev.com/")
: nft.metadata_url,
},
owner: params.owner,
supply: nft.balance,
tokenAddress: nft.contract.address,
tokenURI: nft.metadata_url,
type: nft.token_type === "erc721" ? "ERC721" : "ERC1155",
};
return walletNFT;
});
return {
data: walletNFTs,
ok: true,
};
}
@@ -1,7 +1,7 @@
import "server-only";
import Stripe from "stripe";
import type { Team } from "@/api/team";
import type { Team } from "@/api/team/get-team";
import { STRIPE_SECRET_KEY } from "@/constants/server-envs";
let existingStripe: Stripe | undefined;
@@ -5,7 +5,7 @@ import "server-only";
import { randomBytes } from "crypto";
import { format } from "date-fns";
import { getAuthToken } from "@/api/auth-token";
import type { Team } from "@/api/team";
import type { Team } from "@/api/team/get-team";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
export async function createTeam(options?: { name?: string; slug?: string }) {
+106 -17
View File
@@ -1,6 +1,7 @@
"use client";
import posthog from "posthog-js";
import type { Team } from "@/api/team";
import type { Team } from "@/api/team/get-team";
import type { ProductSKU } from "../types/billing";
// ----------------------------
@@ -269,22 +270,6 @@ export function reportAssetBuyFailed(properties: {
// Assets Landing Page ----------------------------
/**
* ### Why do we need to report this event?
* - To track number of asset creation started from the assets page
* - To track which asset types are being created the most
*
* ### Who is responsible for this event?
* @MananTank
*/
export function reportAssetCreationStarted(properties: {
assetType: "nft" | "coin";
}) {
posthog.capture("asset creation started", {
assetType: properties.assetType,
});
}
/**
* ### Why do we need to report this event?
* - To track number of assets imported successfully from the assets page
@@ -428,3 +413,107 @@ export function reportUpsellClicked(properties: UpsellParams) {
export function reportPaymentCardClick(properties: { id: string }) {
posthog.capture("payment card clicked", properties);
}
/**
* ### Why do we need to report this event?
* - To create a funnel "create payment link pageview" -> "payment link created" to understand the conversion rate
*
* ### Who is responsible for this event?
* @greg
*/
export function reportPaymentLinkCreated(properties: {
linkId: string;
clientId: string;
}) {
posthog.capture("payment link created", properties);
}
/**
* ### Why do we need to report this event?
* - To track funnel "payment link pageview" -> "payment link buy successful" to understand the conversion rate
*
* ### Who is responsible for this event?
* @greg
*/
export function reportPaymentLinkBuySuccessful() {
posthog.capture("payment link buy successful");
}
/**
* ### Why do we need to report this event?
* - To track the number of failed payment link buys
* - To track what errors users encounter when trying to buy from a payment link
*
* ### Who is responsible for this event?
* @greg
*/
export function reportPaymentLinkBuyFailed(properties: {
errorMessage: string;
}) {
posthog.capture("payment link buy failed", properties);
}
/**
* ### Why do we need to report this event?
* - To create a funnel for "asset pageview" -> "asset purchase successful" to understand the conversion rate
* - To understand which asset types are being viewed the most
*
* ### Who is responsible for this event?
* @MananTank
*/
export function reportAssetPageview(properties: {
assetType: "nft" | "coin";
chainId: number;
}) {
posthog.capture("asset pageview", properties);
}
/**
* ### Why do we need to report this event?
* - To track the usage of fund wallet modal
* - To create a funnel "fund wallet modal opened" -> "fund wallet buy successful" to understand the conversion rate
*
* ### Who is responsible for this event?
* @MananTank
*/
export function reportFundWalletOpened() {
posthog.capture("fund wallet opened");
}
/**
* ### Why do we need to report this event?
* - To track the number of successful fund wallet buys
* - To create a funnel "fund wallet modal opened" -> "fund wallet buy successful" to understand the conversion rate
*
* ### Who is responsible for this event?
* @MananTank
*/
export function reportFundWalletSuccessful() {
posthog.capture("fund wallet successful");
}
/**
* ### Why do we need to report this event?
* - To track the number of failed fund wallet buys
* - To track the errors that users encounter when trying to buy from a fund wallet modal
*
* ### Who is responsible for this event?
* @MananTank
*/
export function reportFundWalletFailed(params: { errorMessage: string }) {
posthog.capture("fund wallet failed", params);
}
/**
* ### Why do we need to report this event?
* - To track the conversion rate of the users choosing to create a token from new flow instead of the old flow
*
* ### Who is responsible for this event?
* @MananTank
*/
export function reportTokenUpsellClicked(params: {
assetType: "nft" | "coin";
pageType: "explore" | "deploy-contract";
}) {
posthog.capture("token upsell clicked", params);
}
@@ -1,8 +1,8 @@
import { getAuthToken } from "@/api/auth-token";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import type { Account } from "@/hooks/useApi";
import { isAccountOnboardingComplete } from "@/utils/account-onboarding";
import { loginRedirect } from "@/utils/redirects";
import { isAccountOnboardingComplete } from "../../../login/onboarding/isOnboardingRequired";
/**
* Just get the account object without enforcing onboarding.
@@ -1,5 +1,5 @@
import { getAuthToken } from "@/api/auth-token";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { getAuthToken } from "./auth-token";
export type LinkedWallet = {
createdAt: string;
File diff suppressed because it is too large Load Diff
+5 -2
View File
@@ -1,6 +1,9 @@
import { cookies } from "next/headers";
import { COOKIE_ACTIVE_ACCOUNT, COOKIE_PREFIX_TOKEN } from "@/constants/cookie";
import { LAST_USED_TEAM_ID } from "@/constants/cookies";
import {
COOKIE_ACTIVE_ACCOUNT,
COOKIE_PREFIX_TOKEN,
LAST_USED_TEAM_ID,
} from "@/constants/cookie";
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
export async function getAuthToken() {
@@ -1,7 +1,7 @@
import type { ThirdwebClient } from "thirdweb";
import { fetchDeployMetadata as sdkFetchDeployMetadata } from "thirdweb/contract";
import { removeUndefinedFromObjectDeep } from "@/utils/object";
import type { ContractId } from "./types";
import type { ContractId } from "../../components/contract-components/types";
// metadata PRE publish, only has the compiler output info (from CLI)
@@ -0,0 +1,27 @@
import type { ThirdwebContract } from "thirdweb";
export async function verifyContract(contract: ThirdwebContract) {
try {
const response = await fetch(
"https://contract.thirdweb.com/verify/contract",
{
body: JSON.stringify({
chainId: contract.chain.id,
contractAddress: contract.address,
}),
headers: {
"Content-Type": "application/json",
},
method: "POST",
},
);
if (!response.ok) {
console.error(`Error verifying contract: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error(`Error verifying contract: ${error}`);
}
}
@@ -1,7 +1,7 @@
import {
getProjectContracts,
type ProjectContract,
} from "@/api/getProjectContracts";
} from "@/api/project/getProjectContracts";
import { fetchChainWithLocalOverrides } from "@/utils/fetchChainWithLocalOverrides";
export async function getSortedDeployedContracts(params: {
@@ -1,7 +1,7 @@
import "server-only";
import type { ProjectResponse } from "@thirdweb-dev/service-utils";
import { getAuthToken } from "@/api/auth-token";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { getAuthToken } from "./auth-token";
export type Project = ProjectResponse;
@@ -1,8 +1,8 @@
"use server";
import "server-only";
import { getAuthToken } from "@/api/auth-token";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { getAuthToken } from "./auth-token";
export type AuditLogEntry = {
who: {
@@ -1,8 +1,8 @@
"use server";
import "server-only";
import { getAuthToken } from "@/api/auth-token";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { getAuthToken } from "./auth-token";
export async function createDedicatedSupportChannel(
teamIdOrSlug: string,
@@ -1,7 +1,7 @@
import "server-only";
import { getAuthToken } from "@/api/auth-token";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { getAuthToken } from "./auth-token";
export type AuthOption =
| "email"
@@ -1,11 +1,11 @@
import "server-only";
import type { TeamResponse } from "@thirdweb-dev/service-utils";
import { cookies } from "next/headers";
import { LAST_USED_TEAM_ID } from "@/constants/cookies";
import { getValidAccount } from "@/api/account/get-account";
import { getAuthToken } from "@/api/auth-token";
import { LAST_USED_TEAM_ID } from "@/constants/cookie";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { API_SERVER_SECRET } from "@/constants/server-envs";
import { getValidAccount } from "../../app/(app)/account/settings/getAccount";
import { getAuthToken } from "./auth-token";
import { getMemberByAccountId } from "./team-members";
export type Team = TeamResponse & { stripeCustomerId: string | null };
@@ -1,5 +1,5 @@
import { getAuthToken } from "@/api/auth-token";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { getAuthToken } from "./auth-token";
export type TeamInvite = {
id: string;
@@ -1,6 +1,6 @@
import "server-only";
import { getAuthToken } from "@/api/auth-token";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { getAuthToken } from "./auth-token";
const TeamAccountRole = {
MEMBER: "MEMBER",
@@ -1,8 +1,8 @@
"use server";
import "server-only";
import { getAuthToken } from "@/api/auth-token";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { getAuthToken } from "./auth-token";
export type VerifiedDomainResponse =
| {
@@ -200,7 +200,11 @@ export async function createPaymentLink(props: {
throw new Error(text);
}
return;
const response = (await res.json()) as {
data: PaymentLink;
};
return response.data;
}
export async function deletePaymentLink(props: {
@@ -18,6 +18,7 @@ export function DateRangeSelector(props: {
}) {
const { range, setRange } = props;
const daysDiff = differenceInCalendarDays(range.to, range.from);
const matchingRange =
normalizeTime(range.to).getTime() === normalizeTime(new Date()).getTime()
? durationPresets.find((preset) => preset.days === daysDiff)
@@ -85,7 +86,7 @@ export function getLastNDaysRange(id: DurationId) {
throw new Error(`Invalid duration id: ${id}`);
}
const todayDate = new Date(Date.now() + 1000 * 60 * 60 * 24); // add 1 day to avoid timezone issues
const todayDate = new Date(Date.now());
const value: Range = {
from: subDays(todayDate, durationInfo.days),
@@ -1,104 +0,0 @@
"use client";
import { useQuery } from "@tanstack/react-query";
import { usePathname, useSearchParams } from "next/navigation";
import { useState } from "react";
import type {
DurationId,
Range,
} from "@/components/analytics/date-range-selector";
import {
DateRangeSelector,
getLastNDaysRange,
} from "@/components/analytics/date-range-selector";
import { IntervalSelector } from "@/components/analytics/interval-selector";
import { useDashboardRouter } from "@/lib/DashboardRouter";
export function RangeSelector({
range,
interval,
}: {
range?: Range;
interval: "day" | "week";
}) {
const pathname = usePathname();
const searchParams = useSearchParams();
const router = useDashboardRouter();
const [localRange, setRange] = useState<Range>(
range || getLastNDaysRange("last-120"),
);
const [localInterval, setInterval] = useState<"day" | "week">(interval);
useQuery({
queryFn: async () => {
if (range) {
setRange(range);
return range;
}
if (searchParams) {
const fromStringified = searchParams.get("from");
const from = fromStringified
? new Date(fromStringified)
: getLastNDaysRange("last-120").from;
const toStringified = searchParams.get("to");
const to = toStringified ? new Date(toStringified) : new Date();
const type = (searchParams.get("type") as DurationId) || "last-120";
setRange({ from, to, type });
setInterval(interval);
return { from, to, type } satisfies Range;
}
return getLastNDaysRange("last-120");
},
queryKey: ["analytics-range", searchParams?.toString(), range],
});
// prefetch for each interval and default range
useQuery({
queryFn: async () => {
const newSearchParams = new URLSearchParams(searchParams || {});
for (const interval of ["day", "week"] as const) {
newSearchParams.set("interval", interval);
for (const type of [
"last-120",
"last-60",
"last-30",
"last-7",
] as const) {
const newRange = getLastNDaysRange(type);
newSearchParams.set("from", newRange.from.toISOString());
newSearchParams.set("to", newRange.to.toISOString());
newSearchParams.set("type", newRange.type);
router.prefetch(`${pathname}?${newSearchParams.toString()}`);
}
}
return true;
},
queryKey: ["analytics-range", searchParams?.toString()],
});
return (
<div className="flex justify-end gap-3 flex-col lg:flex-row">
<DateRangeSelector
className="rounded-full"
range={localRange}
setRange={(newRange) => {
setRange(newRange);
const newSearchParams = new URLSearchParams(searchParams || {});
newSearchParams.set("from", newRange.from.toISOString());
newSearchParams.set("to", newRange.to.toISOString());
newSearchParams.set("type", newRange.type);
router.push(`${pathname}?${newSearchParams.toString()}`);
}}
/>
<IntervalSelector
className="bg-card rounded-full"
intervalType={localInterval}
setIntervalType={(newInterval) => {
setInterval(newInterval);
const newSearchParams = new URLSearchParams(searchParams || {});
newSearchParams.set("interval", newInterval);
router.push(`${pathname}?${newSearchParams.toString()}`);
}}
/>
</div>
);
}
@@ -0,0 +1,55 @@
"use client";
import {
useResponsiveSearchParams,
useSetResponsiveSearchParams,
} from "responsive-rsc";
import {
DateRangeSelector,
type DurationId,
} from "@/components/analytics/date-range-selector";
import { IntervalSelector } from "@/components/analytics/interval-selector";
import { getFiltersFromSearchParams, normalizeTimeISOString } from "@/lib/time";
export function ResponsiveTimeFilters(props: { defaultRange: DurationId }) {
const responsiveSearchParams = useResponsiveSearchParams();
const setResponsiveSearchParams = useSetResponsiveSearchParams();
const { range, interval } = getFiltersFromSearchParams({
defaultRange: props.defaultRange,
from: responsiveSearchParams.from,
interval: responsiveSearchParams.interval,
to: responsiveSearchParams.to,
});
return (
<div className="flex justify-end gap-3 flex-col lg:flex-row">
<DateRangeSelector
className="rounded-full"
range={range}
setRange={(newRange) => {
setResponsiveSearchParams((v) => {
const newParams = {
...v,
from: normalizeTimeISOString(newRange.from),
to: normalizeTimeISOString(newRange.to),
};
return newParams;
});
}}
/>
<IntervalSelector
className="bg-card rounded-full"
intervalType={interval}
setIntervalType={(newInterval) => {
setResponsiveSearchParams((v) => {
const newParams = {
...v,
interval: newInterval,
};
return newParams;
});
}}
/>
</div>
);
}
@@ -1,7 +1,7 @@
"use client";
import { AlertTriangleIcon } from "lucide-react";
import Link from "next/link";
import type { Team } from "@/api/team";
import type { Team } from "@/api/team/get-team";
import { Button, type ButtonProps } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import type { ProductSKU } from "@/types/billing";
@@ -2,9 +2,9 @@
import { CheckIcon, DollarSignIcon } from "lucide-react";
import Link from "next/link";
import type React from "react";
import type { Team } from "@/api/team";
import type { Team } from "@/api/team/get-team";
import { CheckoutButton } from "@/components/billing/billing";
import { RenewSubscriptionButton } from "@/components/billing/renew-subscription/renew-subscription-button";
import { RenewSubscriptionButton } from "@/components/billing/renew-subscription-button";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { ToolTipLabel } from "@/components/ui/tooltip";
@@ -5,12 +5,12 @@ import { RefreshCwIcon } from "lucide-react";
import { useState, useTransition } from "react";
import { toast } from "sonner";
import { reSubscribePlan } from "@/actions/billing";
import type { Team } from "@/api/team";
import type { Team } from "@/api/team/get-team";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { pollWithTimeout } from "../../../utils/pollWithTimeout";
import { tryCatch } from "../../../utils/try-catch";
import { pollWithTimeout } from "@/utils/pollWithTimeout";
import { tryCatch } from "@/utils/try-catch";
export function RenewSubscriptionButton(props: {
teamId: string;
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from "@storybook/nextjs";
import { useState } from "react";
import type { Team } from "@/api/team";
import type { Team } from "@/api/team/get-team";
import { Label } from "@/components/ui/label";
import {
Select,
@@ -1,6 +1,6 @@
import { ExternalLinkIcon } from "lucide-react";
import Link from "next/link";
import type { Team } from "@/api/team";
import type { Team } from "@/api/team/get-team";
import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch";
import { ToolTipLabel } from "@/components/ui/tooltip";
@@ -1,6 +1,6 @@
"use client";
import type { Team } from "@/api/team";
import type { Team } from "@/api/team/get-team";
import { Badge, type BadgeProps } from "@/components/ui/badge";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { cn } from "@/lib/utils";
@@ -9,10 +9,10 @@ import type { TokenMetadata } from "@/api/universal-bridge/tokens";
import { Img } from "@/components/blocks/Img";
import { SelectWithSearch } from "@/components/blocks/select-with-search";
import { Badge } from "@/components/ui/badge";
import { fallbackChainIcon } from "@/constants/chain";
import { useAllChainsData } from "@/hooks/chains/allChains";
import { useTokensData } from "@/hooks/tokens";
import { cn } from "@/lib/utils";
import { fallbackChainIcon } from "@/utils/chain-icons";
import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler";
import { Spinner } from "../ui/Spinner/Spinner";
@@ -95,7 +95,6 @@ export const CodeSegment: React.FC<CodeSegmentProps> = ({
{!hideTabs && (
<TabButtons
hideBottomLine={!!onlyTabs}
tabClassName="text-sm gap-2 !text-sm"
tabContainerClassName="px-3 pt-1.5 gap-0.5"
tabIconClassName="size-4"
tabs={environments.map((env) => ({
@@ -1,12 +1,39 @@
"use client";
import { XIcon } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { useLocalStorage } from "@/hooks/useLocalStorage";
export function DismissibleAlert(props: {
export function DismissibleAlert(
props: {
title: React.ReactNode;
header?: React.ReactNode;
className?: string;
description: React.ReactNode;
children?: React.ReactNode;
} & (
| {
preserveState: true;
localStorageId: string;
}
| {
preserveState: false;
}
),
) {
if (props.preserveState) {
return <DismissibleAlertWithLocalStorage {...props} />;
}
return <DismissibleAlertWithoutLocalStorage {...props} />;
}
function DismissibleAlertWithLocalStorage(props: {
title: React.ReactNode;
header?: React.ReactNode;
description: React.ReactNode;
children?: React.ReactNode;
localStorageId: string;
}) {
const [isVisible, setIsVisible] = useLocalStorage(
@@ -17,19 +44,48 @@ export function DismissibleAlert(props: {
if (!isVisible) return null;
return <AlertUI {...props} onClose={() => setIsVisible(false)} />;
}
function DismissibleAlertWithoutLocalStorage(props: {
title: React.ReactNode;
description: React.ReactNode;
children?: React.ReactNode;
}) {
const [isVisible, setIsVisible] = useState(true);
if (!isVisible) return null;
return <AlertUI {...props} onClose={() => setIsVisible(false)} />;
}
function AlertUI(props: {
title: React.ReactNode;
header?: React.ReactNode;
description: React.ReactNode;
children?: React.ReactNode;
className?: string;
onClose: () => void;
}) {
return (
<div className="relative rounded-lg border border-border bg-card p-4">
<Button
aria-label="Close alert"
className="absolute top-4 right-4 h-auto w-auto p-1 text-muted-foreground"
onClick={() => setIsVisible(false)}
variant="ghost"
>
<XIcon className="size-5" />
</Button>
<div>
<h2 className="mb-0.5 font-semibold">{props.title} </h2>
<div className="text-muted-foreground text-sm">{props.description}</div>
<div className={props.className}>
<div className="relative rounded-lg border border-border bg-card p-4 lg:p-6 overflow-hidden">
<Button
aria-label="Close alert"
className="absolute top-4 right-4 h-auto w-auto p-1 text-muted-foreground"
onClick={props.onClose}
variant="ghost"
>
<XIcon className="size-5" />
</Button>
<div>
{props.header}
<h2 className="mb-0.5 font-semibold">{props.title} </h2>
<div className="text-muted-foreground text-sm">
{props.description}
</div>
{props.children}
</div>
</div>
</div>
);
@@ -0,0 +1,49 @@
import type { Meta, StoryObj } from "@storybook/nextjs";
import { useState } from "react";
import { ConnectButton, ThirdwebProvider } from "thirdweb/react";
import { Button } from "@/components/ui/button";
import { storybookThirdwebClient } from "@/storybook/utils";
import { FundWalletModal } from "./index";
const meta: Meta<typeof FundWalletModal> = {
title: "Blocks/FundWalletModal",
component: Variant,
decorators: [
(Story) => (
<ThirdwebProvider>
<div className="p-10">
<ConnectButton client={storybookThirdwebClient} />
<div className="h-4" />
<Story />
</div>
</ThirdwebProvider>
),
],
};
export default meta;
type Story = StoryObj<typeof meta>;
export const Test: Story = {
args: {},
};
function Variant() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<Button type="button" onClick={() => setIsOpen(true)}>
Open
</Button>
<FundWalletModal
open={isOpen}
onOpenChange={setIsOpen}
title="This is a title"
description="This is a description"
recipientAddress="0x83Dd93fA5D8343094f850f90B3fb90088C1bB425"
client={storybookThirdwebClient}
/>
</div>
);
}
@@ -0,0 +1,292 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react";
import { useTheme } from "next-themes";
import { useState } from "react";
import { useForm } from "react-hook-form";
import type { ThirdwebClient } from "thirdweb";
import { defineChain } from "thirdweb/chains";
import { CheckoutWidget, useActiveWalletChain } from "thirdweb/react";
import { z } from "zod";
import {
reportFundWalletFailed,
reportFundWalletSuccessful,
} from "@/analytics/report";
import { SingleNetworkSelector } from "@/components/blocks/NetworkSelectors";
import { TokenSelector } from "@/components/blocks/TokenSelector";
import { Button } from "@/components/ui/button";
import { DecimalInput } from "@/components/ui/decimal-input";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { getSDKTheme } from "@/utils/sdk-component-theme";
import { WalletAddress } from "../wallet-address";
const formSchema = z.object({
chainId: z.number({
required_error: "Chain is required",
}),
token: z.object(
{
chainId: z.number(),
address: z.string(),
symbol: z.string(),
name: z.string(),
decimals: z.number(),
},
{
required_error: "Token is required",
},
),
amount: z
.string()
.min(1, "Amount is required")
.refine((value) => {
const num = Number(value);
return !Number.isNaN(num) && num > 0;
}, "Amount must be greater than 0"),
});
type FormData = z.infer<typeof formSchema>;
type FundWalletModalProps = {
open: boolean;
onOpenChange: (open: boolean) => void;
title: string;
description: string;
recipientAddress: string;
client: ThirdwebClient;
defaultChainId?: number;
checkoutWidgetTitle?: string;
};
export function FundWalletModal(props: FundWalletModalProps) {
return (
<Dialog open={props.open} onOpenChange={props.onOpenChange}>
<DialogContent className="p-0 gap-0 !w-full !max-w-md">
<FundWalletModalContent
open={props.open}
onOpenChange={props.onOpenChange}
title={props.title}
description={props.description}
recipientAddress={props.recipientAddress}
client={props.client}
defaultChainId={props.defaultChainId}
checkoutWidgetTitle={props.checkoutWidgetTitle}
/>
</DialogContent>
</Dialog>
);
}
function FundWalletModalContent(props: FundWalletModalProps) {
const [step, setStep] = useState<"form" | "checkout">("form");
const activeChain = useActiveWalletChain();
const { theme } = useTheme();
const form = useForm<FormData>({
defaultValues: {
chainId: props.defaultChainId || activeChain?.id || 1,
token: undefined,
amount: "0.1",
},
mode: "onChange",
resolver: zodResolver(formSchema),
});
const selectedChainId = form.watch("chainId");
return (
<div>
{step === "form" ? (
<Form {...form}>
<form
onSubmit={form.handleSubmit(() => {
setStep("checkout");
})}
>
<DialogHeader className="p-4 lg:p-6">
<DialogTitle>{props.title}</DialogTitle>
<DialogDescription>{props.description}</DialogDescription>
</DialogHeader>
<div className="space-y-4 px-4 lg:px-6 pb-8">
<div>
<h3 className="text-sm font-medium"> Recipient</h3>
<WalletAddress
address={props.recipientAddress}
client={props.client}
className="py-1 h-auto"
iconClassName="size-4"
/>
</div>
<FormField
control={form.control}
name="chainId"
render={({ field }) => (
<FormItem>
<FormLabel>Chain</FormLabel>
<FormControl>
<SingleNetworkSelector
className="bg-card"
chainId={field.value}
onChange={(token) => {
field.onChange(token);
form.resetField("token", {
defaultValue: undefined,
});
}}
client={props.client}
placeholder="Select a chain"
disableDeprecated
disableTestnets
disableChainId
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="token"
render={({ field }) => {
return (
<FormItem>
<FormLabel>Token</FormLabel>
<FormControl>
<TokenSelector
className="bg-card"
disableAddress
selectedToken={
field.value
? {
chainId: field.value.chainId,
address: field.value.address,
}
: undefined
}
onChange={field.onChange}
chainId={selectedChainId}
client={props.client}
placeholder="Select a token"
enabled={!!selectedChainId}
addNativeTokenIfMissing={true}
showCheck={true}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
<FormField
control={form.control}
name="amount"
render={({ field }) => (
<FormItem>
<FormLabel>Amount</FormLabel>
<FormControl>
<div className="relative">
<DecimalInput
className="bg-card !text-xl h-auto font-semibold"
value={field.value}
onChange={field.onChange}
placeholder="0.1"
/>
{form.watch("token") && (
<div className="absolute right-4 top-1/2 -translate-y-1/2 text-sm text-muted-foreground">
{form.watch("token").symbol}
</div>
)}
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex justify-end gap-3 p-4 lg:p-6 bg-card border-t rounded-b-lg">
<Button
variant="outline"
onClick={() => props.onOpenChange(false)}
>
Cancel
</Button>
<Button type="submit" className="gap-2">
Next
<ArrowRightIcon className="w-4 h-4" />
</Button>
</div>
</form>
</Form>
) : (
<div>
<DialogHeader className="sr-only">
<DialogTitle>{props.title}</DialogTitle>
<DialogDescription>{props.description}</DialogDescription>
</DialogHeader>
<div>
<CheckoutWidget
client={props.client}
// eslint-disable-next-line no-restricted-syntax
chain={defineChain(form.getValues("chainId"))}
tokenAddress={form.getValues("token").address as `0x${string}`}
amount={form.getValues("amount")}
seller={props.recipientAddress as `0x${string}`}
showThirdwebBranding={false}
className="!w-full !max-w-full !min-w-0 !rounded-b-none !border-none"
theme={getSDKTheme(theme === "dark" ? "dark" : "light")}
name={props.checkoutWidgetTitle}
onSuccess={() => {
reportFundWalletSuccessful();
}}
onError={(error) => {
reportFundWalletFailed({
errorMessage: error.message,
});
}}
/>
</div>
<div className="flex justify-end gap-3 p-4 lg:p-6 bg-card border-t rounded-b-lg">
<Button
variant="outline"
onClick={() => setStep("form")}
className="gap-2"
>
<ArrowLeftIcon className="size-4 text-muted-foreground" />
Back
</Button>
<Button
className="gap-2"
variant="outline"
onClick={() => props.onOpenChange(false)}
>
Cancel
</Button>
</div>
</div>
)}
</div>
);
}
@@ -3,7 +3,7 @@
import { CrownIcon, LockIcon } from "lucide-react";
import Link from "next/link";
import type React from "react";
import type { Team } from "@/api/team";
import type { Team } from "@/api/team/get-team";
import { TeamPlanBadge } from "@/components/blocks/TeamPlanBadge";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
@@ -192,11 +192,11 @@ function WalletAvatar(props: {
return (
<div
className={cn("size-6 overflow-hidden rounded-full", props.iconClassName)}
className={cn("size-5 overflow-hidden rounded-full", props.iconClassName)}
>
{resolvedAvatarSrc ? (
<Img
className={cn("size-6 object-cover", props.iconClassName)}
className={cn("size-5 object-cover", props.iconClassName)}
src={resolvedAvatarSrc}
/>
) : (
@@ -4,7 +4,7 @@ import { MessageCircleIcon, XIcon } from "lucide-react";
import { useSelectedLayoutSegments } from "next/navigation";
import { useCallback, useRef, useState } from "react";
import { createThirdwebClient } from "thirdweb";
import type { Team } from "@/api/team";
import type { Team } from "@/api/team/get-team";
import { Button } from "@/components/ui/button";
import { NEXT_PUBLIC_DASHBOARD_CLIENT_ID } from "@/constants/public-envs";
import { cn } from "@/lib/utils";
@@ -2,7 +2,7 @@
import { useCallback, useState } from "react";
import type { ThirdwebClient } from "thirdweb";
import { useActiveWalletConnectionStatus } from "thirdweb/react";
import type { Team } from "@/api/team";
import type { Team } from "@/api/team/get-team";
import { Button } from "@/components/ui/button";
import { ThirdwebMiniLogo } from "../../../app/(app)/components/ThirdwebMiniLogo";
import { ChatBar } from "./ChatBar";
@@ -6,7 +6,7 @@ import {
} from "lucide-react";
import { useEffect, useRef, useState } from "react";
import type { ThirdwebClient } from "thirdweb";
import type { Team } from "@/api/team";
import type { Team } from "@/api/team/get-team";
import { MarkdownRenderer } from "@/components/blocks/markdown-renderer";
import { DynamicHeight } from "@/components/ui/DynamicHeight";
import {
@@ -1,6 +1,5 @@
"use client";
import { useFavoriteChainIds } from "@app/(dashboard)/(chain)/components/client/star-button";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
@@ -20,6 +19,7 @@ import { LazyConfigureNetworkModal } from "@/components/misc/configure-networks/
import { Button } from "@/components/ui/button";
import { popularChains } from "@/constants/popularChains";
import { useAllChainsData } from "@/hooks/chains/allChains";
import { useFavoriteChainIds } from "@/hooks/favorite-chains";
import { useStore } from "@/lib/reactive";
import {
addRecentlyUsedChainId,
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from "@storybook/nextjs";
import { ThirdwebProvider } from "thirdweb/react";
import type { ProjectContract } from "@/api/getProjectContracts";
import type { ProjectContract } from "@/api/project/getProjectContracts";
import { BadgeContainer, storybookThirdwebClient } from "@/storybook/utils";
import { ContractTableUI } from "./contract-table";
@@ -6,7 +6,7 @@ import Link from "next/link";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { toast } from "sonner";
import type { ThirdwebClient } from "thirdweb";
import type { ProjectContract } from "@/api/getProjectContracts";
import type { ProjectContract } from "@/api/project/getProjectContracts";
import { PaginationButtons } from "@/components/blocks/pagination-buttons";
import { NetworkSelectDropdown } from "@/components/contract-components/tables/NetworkSelectDropdown";
import { Badge } from "@/components/ui/badge";
@@ -14,11 +14,11 @@ import * as ERC1155Ext from "thirdweb/extensions/erc1155";
import * as ERC4337Ext from "thirdweb/extensions/erc4337";
import { useActiveAccount } from "thirdweb/react";
import { toFunctionSelector } from "thirdweb/utils";
import { getContractFunctionsFromAbi } from "@/api/contract/getContractFunctionsFromAbi";
import {
type CodeEnvironment,
CodeSegment,
} from "@/components/blocks/code/code-segment.client";
import { getContractFunctionsFromAbi } from "@/components/contract-components/getContractFunctionsFromAbi";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { TabButtons } from "@/components/ui/tabs";
@@ -743,7 +743,6 @@ export function CodeOverview(props: {
<div className="bg-background">
<TabButtons
tabs={tabList}
tabClassName="!text-sm"
tabContainerClassName="px-3 pt-2"
/>
</div>
@@ -1,8 +1,8 @@
import { moduleToBase64 } from "app/(app)/(dashboard)/published-contract/utils/module-base-64";
import { RocketIcon, ShieldCheckIcon } from "lucide-react";
import Link from "next/link";
import { fetchPublishedContractVersion } from "@/api/contract/fetch-contracts-with-versions";
import { ClientOnly } from "@/components/blocks/client-only";
import { fetchPublishedContractVersion } from "@/components/contract-components/fetch-contracts-with-versions";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
@@ -11,7 +11,7 @@ import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
import { replaceDeployerAddress } from "@/lib/publisher-utils";
import { cn } from "@/lib/utils";
import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler";
import { ContractPublisher } from "../../../../app/(app)/(dashboard)/explore/components/publisher";
import { ContractPublisher } from "./contract-publisher";
interface ContractCardProps {
publisher: string;
@@ -1,5 +1,5 @@
import Link from "next/link";
import { fetchDeployMetadata } from "@/components/contract-components/fetchDeployMetadata";
import { fetchDeployMetadata } from "@/api/contract/fetchDeployMetadata";
import { ContractIdImage } from "@/components/contract-components/shared/contract-id-image";
import {
Table,
@@ -332,7 +332,6 @@ export const ContractFunctionsPanel: React.FC<ContractFunctionsPanelProps> = ({
<div className="flex flex-col h-full relative">
<TabButtons
tabContainerClassName="px-3 pt-2"
tabClassName="!text-sm"
tabs={[
...(viewFunctions.length > 0
? [
@@ -30,7 +30,6 @@ import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { CodeClient } from "@/components/ui/code/code.client";
import { PlainTextCodeBlock } from "@/components/ui/code/plaintext-code";
import { InlineCode } from "@/components/ui/inline-code";
import { Input } from "@/components/ui/input";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Skeleton } from "@/components/ui/skeleton";
@@ -414,14 +413,13 @@ export const InteractiveAbiFunction: React.FC<InteractiveAbiFunctionProps> = (
)}
{error ? (
<>
<h3 className="text-sm font-medium">Error</h3>
<InlineCode
className="relative w-full whitespace-pre-wrap rounded-md border border-border p-4 text-red-500"
// biome-ignore lint/suspicious/noExplicitAny: FIXME
code={formatError(error as any)}
<div>
<h3 className="text-sm font-medium mb-2">Error</h3>
<PlainTextCodeBlock
className="text-red-500 bg-background"
code={formatError(error)}
/>
</>
</div>
) : readLoading ? (
<Skeleton className="h-20 w-full rounded-lg" />
) : formattedResponseData ? (
@@ -1,14 +1,10 @@
"use client";
import { CircleAlertIcon, ExternalLinkIcon } from "lucide-react";
import { useId } from "react";
import type { ThirdwebClient } from "thirdweb";
import type { Project } from "@/api/projects";
import type { Team } from "@/api/team";
import { GradientAvatar } from "@/components/blocks/avatar/gradient-avatar";
import { ProjectAvatar } from "@/components/blocks/avatar/project-avatar";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
import {
Select,
@@ -17,98 +13,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { UnderlineLink } from "@/components/ui/UnderlineLink";
import { Fieldset } from "./common";
export type MinimalTeam = Pick<Team, "id" | "name" | "image" | "slug">;
export type MinimalProject = Pick<Project, "id" | "name" | "image" | "slug">;
export type TeamAndProjectSelection = {
team: MinimalTeam | undefined;
project: MinimalProject | undefined;
};
export type MinimalTeamsAndProjects = Array<{
team: MinimalTeam;
projects: MinimalProject[];
}>;
type TeamAndProjectSelectorProps = {
teamsAndProjects: MinimalTeamsAndProjects;
selection: TeamAndProjectSelection;
enabled: boolean;
onSelectionChange: (selection: TeamAndProjectSelection) => void;
client: ThirdwebClient;
onSetEnabled: (enabled: boolean) => void;
};
export function AddToProjectCardUI(props: TeamAndProjectSelectorProps) {
const selectedTeam = props.teamsAndProjects.find(
(t) => t.team.id === props.selection.team?.id,
);
const isProjectSelected = props.selection.project && props.enabled;
return (
<Fieldset
description="Save the deployed contract in a project's contract list on thirdweb dashboard"
headerChildren={
<div className="absolute top-4 right-4 lg:top-6 lg:right-6">
<Checkbox
checked={props.enabled}
className="size-5"
onCheckedChange={(checked) => {
props.onSetEnabled(!!checked);
}}
/>
</div>
}
headerClassName="pr-14"
legend="Add To Project"
>
<div className="flex flex-col gap-5">
{/* Team & Project selectors */}
{props.enabled && (
<AddToProjectSelector
client={props.client}
onSelectionChange={props.onSelectionChange}
selection={props.selection}
teamsAndProjects={props.teamsAndProjects}
/>
)}
{/* No projects alert */}
{selectedTeam?.projects.length === 0 && (
<Alert variant="info">
<CircleAlertIcon className="size-5" />
<AlertTitle> Selected team has no projects </AlertTitle>
<AlertDescription>
<UnderlineLink
className="inline-flex items-center gap-2"
href={`/team/${selectedTeam.team.slug}`}
rel="noopener noreferrer"
target="_blank"
>
Create Project <ExternalLinkIcon className="size-3.5" />
</UnderlineLink>
</AlertDescription>
</Alert>
)}
{/* No project selected alert */}
{!isProjectSelected && (
<Alert variant="warning">
<CircleAlertIcon className="size-5" />
<AlertTitle>No Project Selected</AlertTitle>
<AlertDescription>
Deployed contract will not be saved in thirdweb dashboard
</AlertDescription>
</Alert>
)}
</div>
</Fieldset>
);
}
import type { MinimalTeamsAndProjects, TeamAndProjectSelection } from "./types";
function SlashSeparator() {
return <div className="mx-2 h-5 w-[2px] rotate-[25deg] bg-foreground/20" />;
@@ -0,0 +1,15 @@
import type { Project } from "@/api/project/projects";
import type { Team } from "@/api/team/get-team";
export type MinimalTeam = Pick<Team, "id" | "name" | "image" | "slug">;
export type MinimalProject = Pick<Project, "id" | "name" | "image" | "slug">;
export type TeamAndProjectSelection = {
team: MinimalTeam | undefined;
project: MinimalProject | undefined;
};
export type MinimalTeamsAndProjects = Array<{
team: MinimalTeam;
projects: MinimalProject[];
}>;
@@ -1,25 +0,0 @@
import type { ThirdwebClient } from "thirdweb";
import type { NFTMetadata } from "thirdweb/utils";
import { NFTMediaWithEmptyState } from "@/components/blocks/nft-media";
interface MediaCellProps {
cell: {
value: NFTMetadata;
};
client: ThirdwebClient;
}
export const MediaCell: React.FC<MediaCellProps> = ({ cell, client }) => {
const nftMetadata = cell.value;
return (
<NFTMediaWithEmptyState
className="pointer-events-none"
client={client}
height="120px"
metadata={nftMetadata}
requireInteraction
width="120px"
/>
);
};
@@ -1,4 +1,5 @@
import type { Abi } from "abitype";
import type { SourceFile } from "@/components/contract-components/types";
import {
Accordion,
AccordionContent,
@@ -6,7 +7,6 @@ import {
AccordionTrigger,
} from "@/components/ui/accordion";
import { CodeClient } from "@/components/ui/code/code.client";
import type { SourceFile } from "../types";
export function SourcesAccordion({
sources,
@@ -1,6 +1,6 @@
import type { Abi } from "abitype";
import type { SourceFile } from "@/components/contract-components/types";
import { UnderlineLink } from "@/components/ui/UnderlineLink";
import type { SourceFile } from "../types";
import { SourcesAccordion } from "./sources-accordion";
interface SourcesPanelProps {
@@ -1,9 +1,10 @@
"use client";
import { SearchIcon } from "lucide-react";
import { SearchIcon, XIcon } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import {
Select,
SelectContent,
@@ -11,7 +12,6 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import type { SearchType } from "./types";
export function AdvancedSearchInput(props: {
@@ -41,56 +41,57 @@ export function AdvancedSearchInput(props: {
};
return (
<div className="flex flex-col gap-2">
<div className="flex gap-2">
<Select
value={searchType}
onValueChange={(value) => setSearchType(value as SearchType)}
>
<SelectTrigger className="w-[140px] bg-card">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="email">Email</SelectItem>
<SelectItem value="phone">Phone</SelectItem>
<SelectItem value="id">ID</SelectItem>
<SelectItem value="address">Address</SelectItem>
<SelectItem value="externalWallet">External Wallet</SelectItem>
</SelectContent>
</Select>
<div className="flex flex-col md:flex-row gap-3">
<Select
value={searchType}
onValueChange={(value) => setSearchType(value as SearchType)}
>
<SelectTrigger className="w-[140px] bg-card">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="email">Email</SelectItem>
<SelectItem value="phone">Phone</SelectItem>
<SelectItem value="id">ID</SelectItem>
<SelectItem value="address">Address</SelectItem>
<SelectItem value="externalWallet">External Wallet</SelectItem>
</SelectContent>
</Select>
<div className="flex flex-1">
<div className="relative flex-1">
<Input
className="bg-card pl-9"
className="bg-card pl-9 border-r-0 rounded-r-none"
placeholder={`Search by ${searchType}...`}
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={handleKeyDown}
/>
<SearchIcon className="-translate-y-1/2 absolute top-1/2 left-3 size-4 text-muted-foreground" />
{props.hasResults && (
<div className="absolute top-1/2 -translate-y-1/2 right-2 ">
<Button
variant="ghost"
onClick={handleClear}
className="p-1 h-auto"
>
<XIcon className="size-4 text-muted-foreground" />
</Button>
</div>
)}
</div>
<Button
onClick={handleSearch}
variant="outline"
disabled={!query.trim() || props.isLoading}
size="sm"
className="rounded-l-none gap-2 bg-card disabled:opacity-100"
>
{props.isLoading ? "Searching..." : "Search"}
{props.isLoading && <Spinner className="size-4" />}
Search
</Button>
</div>
{props.hasResults && (
<div className="flex justify-center">
<Button
variant="outline"
size="sm"
onClick={handleClear}
className="gap-2"
>
Clear Search & Return to List
</Button>
</div>
)}
</div>
);
}
@@ -12,6 +12,7 @@ import { WalletAddress } from "@/components/blocks/wallet-address";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import {
ToolTipLabel,
Tooltip,
TooltipContent,
TooltipProvider,
@@ -21,6 +22,7 @@ import {
useAllEmbeddedWallets,
useEmbeddedWallets,
} from "@/hooks/useEmbeddedWallets";
import { CopyTextButton } from "../ui/CopyTextButton";
import { AdvancedSearchInput } from "./AdvancedSearchInput";
import { SearchResults } from "./SearchResults";
import { searchUsers } from "./searchUsers";
@@ -57,7 +59,24 @@ export function InAppWalletUsersPageContent(
columnHelper.accessor("linkedAccounts", {
cell: (cell) => {
const identifier = getUserIdentifier(cell.getValue());
return <span className="text-sm">{identifier}</span>;
if (!identifier) {
return "N/A";
}
return (
<CopyTextButton
textToShow={
identifier.length > 30
? `${identifier.slice(0, 30)}...`
: identifier
}
textToCopy={identifier}
tooltip="Copy User Identifier"
copyIconPosition="left"
variant="ghost"
/>
);
},
enableColumnFilter: true,
header: "User Identifier",
@@ -77,7 +96,7 @@ export function InAppWalletUsersPageContent(
cell: (cell) => {
const externalWallets = getExternalWallets(cell.getValue());
if (externalWallets.length === 0) {
return <span className="text-muted-foreground text-xs">None</span>;
return <span className="text-muted-foreground text-sm">None</span>;
}
return (
<div className="space-y-1">
@@ -111,9 +130,14 @@ export function InAppWalletUsersPageContent(
return;
}
return (
<span className="text-sm">
{format(new Date(value), "MMM dd, yyyy")}
</span>
<ToolTipLabel
label={format(new Date(value), "MMM dd, yyyy 'at' h:mm:ss a zzz")}
hoverable
>
<span className="text-sm">
{format(new Date(value), "MMM dd, yyyy")}
</span>
</ToolTipLabel>
);
},
header: "Created",
@@ -215,7 +239,7 @@ export function InAppWalletUsersPageContent(
return {
address: row.wallets[0]?.address || "Uninitialized",
created: row.wallets[0]?.createdAt
? format(new Date(row.wallets[0].createdAt), "MMM dd, yyyy")
? new Date(row.wallets[0].createdAt).toISOString()
: "Wallet not created yet",
external_wallets: externalWalletAddresses || "None",
login_methods: row.linkedAccounts.map((acc) => acc.type).join(", "),
@@ -243,7 +267,7 @@ export function InAppWalletUsersPageContent(
<div className="flex flex-col gap-4">
{/* Top section */}
<div className="flex flex-col gap-4">
<div className="flex items-center justify-end gap-3">
<div className="flex flex-col md:flex-row lg:items-center justify-end gap-3">
<div className="w-full max-w-lg">
<AdvancedSearchInput
onSearch={handleSearch}
@@ -253,10 +277,9 @@ export function InAppWalletUsersPageContent(
/>
</div>
<Button
className="gap-2"
className="gap-2 bg-card"
disabled={wallets.length === 0 || isPending}
onClick={downloadCSV}
size="sm"
variant="outline"
>
{isPending && <Spinner className="size-4" />}
@@ -9,6 +9,7 @@ import { Button } from "@/components/ui/button";
import { popularChains } from "@/constants/popularChains";
import { useAllChainsData } from "@/hooks/chains/allChains";
import { useActiveChainAsDashboardChain } from "@/hooks/chains/v5-adapter";
import { useFavoriteChainIds } from "@/hooks/favorite-chains";
import { ChainIconClient } from "@/icons/ChainIcon";
import { useStore } from "@/lib/reactive";
import { cn } from "@/lib/utils";
@@ -17,7 +18,6 @@ import {
recentlyUsedChainIdsStore,
type StoredChain,
} from "@/stores/chainStores";
import { useFavoriteChainIds } from "../../../app/(app)/(dashboard)/(chain)/components/client/star-button";
import { mapV4ChainToV5Chain } from "../../utils/map-chains";
import { getSDKTheme } from "../../utils/sdk-component-theme";
import { CustomChainRenderer } from "./CustomChainRenderer";
@@ -8,7 +8,7 @@ import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import type { Project } from "@/api/projects";
import type { Project } from "@/api/project/projects";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { CopyTextButton } from "@/components/ui/CopyTextButton";
@@ -1,6 +1,5 @@
"use client";
import { getFaucetClaimAmount } from "@app/api/testnet-faucet/claim/claim-amount";
import { useMutation, useQuery } from "@tanstack/react-query";
import { FaucetButton } from "app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/components/client/FaucetButton";
import { GiftIcon } from "app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/components/icons/GiftIcon";
@@ -49,6 +48,7 @@ import { useAllChainsData } from "@/hooks/chains/allChains";
import { useV5DashboardChain } from "@/hooks/chains/v5-adapter";
import { cn } from "@/lib/utils";
import type { ChainMetadataWithServices, ChainServices } from "@/types/chain";
import { getFaucetClaimAmount } from "@/utils/faucet";
import { getSDKTheme } from "@/utils/sdk-component-theme";
const GAS_FREE_CHAINS = [
@@ -79,7 +79,6 @@ export function DatePickerWithRange(props: {
<div className={cn("px-4", !props.header && "py-4")}>
<TabButtons
activeTabClassName="!bg-inverted !text-inverted-foreground"
tabClassName="!text-sm"
tabContainerClassName="gap-2"
tabs={[
{
@@ -7,12 +7,15 @@ import { cn } from "@/lib/utils";
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root> & {
size?: "sm";
}
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-foreground data-[state=unchecked]:bg-input",
className,
props.size === "sm" && "h-4.5 w-9",
)}
{...props}
ref={ref}
@@ -20,6 +23,8 @@ const Switch = React.forwardRef<
<SwitchPrimitives.Thumb
className={cn(
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0",
props.size === "sm" &&
"h-4 w-4 translate-x-4 data-[state=checked]:translate-x-4",
)}
/>
</SwitchPrimitives.Root>
+2 -2
View File
@@ -56,7 +56,7 @@ export function TabLinks(props: {
<Link
aria-disabled={tab.isDisabled}
className={cn(
"relative h-auto rounded-lg px-3 font-normal text-muted-foreground text-sm lg:text-sm",
"relative inline-flex h-auto items-center gap-1.5 rounded-lg font-medium hover:bg-accent !px-3",
!tab.isActive && !tab.isDisabled && "hover:text-foreground",
tab.isDisabled && "pointer-events-none",
tab.isActive && "!text-foreground",
@@ -125,7 +125,7 @@ export function TabButtons(props: {
>
<Button
className={cn(
"relative inline-flex h-auto items-center gap-1.5 rounded-lg px-2 font-medium text-sm hover:bg-accent lg:px-3 lg:text-base",
"relative inline-flex h-auto items-center gap-1.5 rounded-lg font-medium hover:bg-accent !px-3",
!tab.isActive &&
"text-muted-foreground hover:text-foreground",
tab.isDisabled && "cursor-not-allowed opacity-50",
+2 -9
View File
@@ -1,12 +1,5 @@
import { cookies } from "next/headers";
export const COOKIE_ACTIVE_ACCOUNT = "tw_active_account";
export const COOKIE_PREFIX_TOKEN = "tw_token_";
export async function getActiveAccountCookie() {
return (await cookies()).get(COOKIE_ACTIVE_ACCOUNT)?.value;
}
export async function getJWTCookie(address: string) {
return (await cookies()).get(COOKIE_PREFIX_TOKEN + address)?.value;
}
export const LAST_USED_PROJECT_ID = "last-used-project-id";
export const LAST_USED_TEAM_ID = "last-used-team-id";
@@ -1,2 +0,0 @@
export const LAST_USED_PROJECT_ID = "last-used-project-id";
export const LAST_USED_TEAM_ID = "last-used-team-id";
@@ -1,4 +1,4 @@
import type { Team } from "@/api/team";
import type { Team } from "@/api/team/get-team";
// Note: Growth legacy is considered higher tier in this hierarchy
export const planToTierRecordForGating: Record<Team["billingPlan"], number> = {
@@ -58,16 +58,6 @@ if (isProd && INSIGHT_SERVICE_API_KEY) {
);
}
export const MORALIS_API_KEY = process.env.MORALIS_API_KEY || "";
if (MORALIS_API_KEY) {
experimental_taintUniqueValue(
"Do not pass MORALIS_API_KEY to the client",
process,
MORALIS_API_KEY,
);
}
export const ANALYTICS_SERVICE_URL = process.env.ANALYTICS_SERVICE_URL || "";
export const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY || "";
+5 -5
View File
@@ -6,14 +6,14 @@ import { useMemo } from "react";
import type { ThirdwebClient, ThirdwebContract } from "thirdweb";
import { getContract, resolveContractAbi } from "thirdweb/contract";
import { isAddress, isValidENSName } from "thirdweb/utils";
import { useV5DashboardChain } from "@/hooks/chains/v5-adapter";
import { resolveEns } from "@/lib/ens";
import {
fetchPublishedContractVersions,
type PublishedContractWithVersion,
} from "../components/contract-components/fetch-contracts-with-versions";
import { fetchPublishedContracts } from "../components/contract-components/fetchPublishedContracts";
import { fetchPublishedContractsFromDeploy } from "../components/contract-components/fetchPublishedContractsFromDeploy";
} from "@/api/contract/fetch-contracts-with-versions";
import { fetchPublishedContracts } from "@/api/contract/fetchPublishedContracts";
import { fetchPublishedContractsFromDeploy } from "@/api/contract/fetchPublishedContractsFromDeploy";
import { useV5DashboardChain } from "@/hooks/chains/v5-adapter";
import { resolveEns } from "@/lib/ens";
export function useAllVersions(
publisherAddress: string | undefined,
@@ -0,0 +1,28 @@
"use client";
import { useQuery } from "@tanstack/react-query";
import { useActiveAccount } from "thirdweb/react";
import { apiServerProxy } from "@/actions/proxies";
async function favoriteChains() {
const res = await apiServerProxy<{ data: string[] }>({
method: "GET",
pathname: "/v1/chains/favorites",
});
if (!res.ok) {
throw new Error(res.error);
}
const result = res.data;
return result.data ?? [];
}
export function useFavoriteChainIds() {
const address = useActiveAccount()?.address;
return useQuery({
enabled: !!address,
queryFn: () => favoriteChains(),
queryKey: ["favoriteChains", address],
});
}
+1 -1
View File
@@ -1,7 +1,7 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useActiveAccount } from "thirdweb/react";
import { apiServerProxy } from "@/actions/proxies";
import type { Project } from "@/api/projects";
import type { Project } from "@/api/project/projects";
import { rotateVaultAccountAndAccessToken } from "../../app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/lib/vault.client";
import { accountKeys, authorizedWallets } from "../query-keys/cache-keys";
@@ -0,0 +1,14 @@
/** biome-ignore-all lint/correctness/useExhaustiveDependencies: we only want to run effect once */
"use client";
import { useEffect, useRef } from "react";
export function useEffectOnce(effect: () => void) {
const hasCalledEffect = useRef(false);
// eslint-disable-next-line no-restricted-syntax
useEffect(() => {
if (hasCalledEffect.current) return;
hasCalledEffect.current = true;
effect();
}, []);
}
+43 -20
View File
@@ -5,62 +5,85 @@ import {
useQueryClient,
} from "@tanstack/react-query";
import { toast } from "sonner";
import { sendAndConfirmTransaction, type ThirdwebContract } from "thirdweb";
import {
type Chain,
sendAndConfirmTransaction,
type ThirdwebClient,
type ThirdwebContract,
} from "thirdweb";
import { distribute, distributeByToken } from "thirdweb/extensions/split";
import { getOwnedTokens } from "thirdweb/insight";
import { useActiveAccount } from "thirdweb/react";
import invariant from "tiny-invariant";
import { getTokenBalancesFromMoralis } from "@/actions/getBalancesFromMoralis";
import { parseError } from "../utils/errorParser";
function getTokenBalancesQuery(contract: ThirdwebContract) {
function getTokenBalancesQuery(params: {
ownerAddress: string;
client: ThirdwebClient;
chain: Chain;
}) {
return queryOptions({
queryFn: async () => {
const res = await getTokenBalancesFromMoralis({
chainId: contract.chain.id,
contractAddress: contract.address,
return getOwnedTokens({
client: params.client,
chains: [params.chain],
ownerAddress: params.ownerAddress,
queryOptions: {
include_native: "true",
},
});
if (!res.data) {
throw new Error(res.error);
}
return res.data;
},
queryKey: ["split-balances", contract.chain.id, contract.address],
queryKey: ["getOwnedTokens", params.chain.id, params.ownerAddress],
retry: false,
});
}
export function useSplitBalances(contract: ThirdwebContract) {
return useQuery(getTokenBalancesQuery(contract));
export function useOwnedTokenBalances(params: {
ownerAddress: string;
client: ThirdwebClient;
chain: Chain;
}) {
return useQuery(getTokenBalancesQuery(params));
}
export function useSplitDistributeFunds(contract: ThirdwebContract) {
const account = useActiveAccount();
const queryClient = useQueryClient();
const params = {
ownerAddress: contract.address, // because we want to fetch the balance of split contract
client: contract.client,
chain: contract.chain,
};
return useMutation({
mutationFn: async () => {
invariant(account, "No active account");
const balances =
// get the cached data if it exists, otherwise fetch it
queryClient.getQueryData(getTokenBalancesQuery(contract).queryKey) ||
(await queryClient.fetchQuery(getTokenBalancesQuery(contract)));
queryClient.getQueryData(getTokenBalancesQuery(params).queryKey) ||
(await queryClient.fetchQuery(getTokenBalancesQuery(params)));
const distributions = balances
.filter((token) => token.display_balance !== "0.0")
.filter((token) => token.value !== 0n)
.map(async (currency) => {
const transaction =
currency.name === "Native Token"
? distribute({ contract })
: distributeByToken({
contract,
tokenAddress: currency.token_address,
tokenAddress: currency.tokenAddress,
});
const promise = sendAndConfirmTransaction({
account,
transaction,
});
toast.promise(promise, {
error: `Error distributing ${currency.name}`,
error: (err) => ({
message: `Error distributing ${currency.name}`,
description: parseError(err),
}),
loading: `Distributing ${currency.name}`,
success: `Successfully distributed ${currency.name}`,
});
@@ -69,7 +92,7 @@ export function useSplitDistributeFunds(contract: ThirdwebContract) {
return await Promise.all(distributions);
},
onSettled: () => {
queryClient.invalidateQueries(getTokenBalancesQuery(contract));
queryClient.invalidateQueries(getTokenBalancesQuery(params));
},
});
}
+16 -6
View File
@@ -1,21 +1,31 @@
import { useQuery } from "@tanstack/react-query";
import type { Chain, ThirdwebClient } from "thirdweb";
import { getOwnedNFTs } from "thirdweb/insight";
import invariant from "tiny-invariant";
import { getWalletNFTs } from "@/actions/getWalletNFTs";
export function useWalletNFTs(params: {
export function useOwnedNFTsInsight(params: {
chainId: number;
walletAddress?: string;
isInsightSupported: boolean;
client: ThirdwebClient;
chain: Chain;
}) {
return useQuery({
enabled: !!params.walletAddress,
queryFn: async () => {
invariant(params.walletAddress, "walletAddress is required");
return getWalletNFTs({
chainId: params.chainId,
isInsightSupported: params.isInsightSupported,
owner: params.walletAddress,
if (!params.isInsightSupported) {
throw new Error("Unsupported chain");
}
const res = await getOwnedNFTs({
client: params.client,
chains: [params.chain],
ownerAddress: params.walletAddress,
});
return res;
},
queryKey: ["walletNfts", params.chainId, params.walletAddress],
});
+1 -1
View File
@@ -2,8 +2,8 @@
import type { ThirdwebClient } from "thirdweb";
import { Img } from "@/components/blocks/Img";
import { fallbackChainIcon } from "@/constants/chain";
import { cn } from "@/lib/utils";
import { fallbackChainIcon } from "@/utils/chain-icons";
import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler";
type ImageProps = React.ComponentProps<"img">;
+21
View File
@@ -0,0 +1,21 @@
export function TokenIcon(props: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
role="presentation"
className={props.className}
>
<path
d="M15.9377 15.9377C19.3603 15.4795 22 12.548 22 9C22 5.13401 18.866 2 15 2C11.452 2 8.52049 4.63967 8.06227 8.06227M16 15C16 18.866 12.866 22 9 22C5.13401 22 2 18.866 2 15C2 11.134 5.13401 8 9 8C12.866 8 16 11.134 16 15Z"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
@@ -0,0 +1,21 @@
export function WalletProductIcon(props: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
role="presentation"
className={props.className}
>
<path
d="M16 7.99995V4.5006C16 3.66887 16 3.253 15.8248 2.99742C15.6717 2.77413 15.4346 2.62245 15.1678 2.57703C14.8623 2.52505 14.4847 2.69932 13.7295 3.04787L4.85901 7.14194C4.18551 7.45279 3.84875 7.60822 3.60211 7.84927C3.38406 8.06237 3.21762 8.3225 3.1155 8.60978C3 8.93474 3 9.30563 3 10.0474V14.9999M16.5 14.4999H16.51M3 11.2L3 17.7999C3 18.9201 3 19.4801 3.21799 19.9079C3.40973 20.2843 3.71569 20.5902 4.09202 20.782C4.51984 20.9999 5.07989 20.9999 6.2 20.9999H17.8C18.9201 20.9999 19.4802 21 19.908 20.782C20.2843 20.5902 20.5903 20.2843 20.782 19.9079C21 19.4801 21 18.9201 21 17.7999V11.2C21 10.0798 21 9.51979 20.782 9.09197C20.5903 8.71565 20.2843 8.40969 19.908 8.21794C19.4802 7.99995 18.9201 7.99995 17.8 7.99995L6.2 7.99995C5.0799 7.99995 4.51984 7.99995 4.09202 8.21794C3.7157 8.40968 3.40973 8.71564 3.21799 9.09197C3 9.51979 3 10.0798 3 11.2ZM17 14.4999C17 14.7761 16.7761 14.9999 16.5 14.9999C16.2239 14.9999 16 14.7761 16 14.4999C16 14.2238 16.2239 13.9999 16.5 13.9999C16.7761 13.9999 17 14.2238 17 14.4999Z"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

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