Compare commits

..
Author SHA1 Message Date
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
d617bf77b3 Version Packages (#7851)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-12 18:39:30 -07:00
Joaquim VergesandGitHub 4be655dd08 [SDK] Fix onDisconnect callback not being invoked in React Native (#7850) 2025-08-12 18:18:45 -07:00
MananTank 74d4074f42 Dashboard: Add fallback to native token balance on contract/split page when insight is not supported (#7847)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on improving the handling of token balances and recipient percentages in the `ContractSplitPage` and related components, enhancing user experience and error handling.

### Detailed summary
- Updated `transactionCount` logic in `DistributeButton` to handle undefined cases.
- Added error handling for fetching token balances using `tryCatch`.
- Improved UI structure and styling in `ContractSplitPage`.
- Changed variable naming for clarity in `useReadContract`.
- Enhanced loading states for recipient percentages and balances.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

* **Bug Fixes**
  * Improved reliability of token balances by falling back to native balance if token data fails to load.
* **Style**
  * Refreshed Split page layout: simplified headers, refined spacing, and updated table containers.
  * Adjusted column widths for Tokens and Addresses for better readability.
  * Enhanced loading skeletons and maintained clear empty/error states.
* **Refactor**
  * Streamlined data flow for recipients and balances to use a unified query object.
* **Chores**
  * Button tweaks: small size on error; hide transaction count when only one transaction.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-12 23:41:08 +00:00
MananTank a36c850b86 [BLD-108] Dashboard: Update Claim conditions UI to handle very large snapshots (#7846)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the handling of CSV uploads and snapshots in the dashboard, improving user feedback during data processing, and refining the management of claim conditions within the application.

### Detailed summary
- Updated `package.json` to remove `p-limit`.
- Improved UI for CSV upload status with additional messages.
- Refactored snapshot management in `ClaimConditionsForm`.
- Added state for `phaseSnapshots` to optimize performance.
- Enhanced snapshot handling in `SnapshotUpload` component.
- Updated data processing logic in `useCsvUpload` to track progress.

> ✨ 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
  - CSV upload shows real-time normalization progress and ENS resolution status across relevant upload screens; pending states now display current/total progress.
  - Per-phase snapshot management with centralized state; improved snapshot viewer with view/edit modes, pagination, and clearer labels.
  - Improved validation: invalid addresses highlighted with tooltips and options to remove or proceed.

- Refactor
  - CSV normalization switched to batched processing for smoother, more responsive uploads.
  - Snapshot data moved out of per-field form state to reduce rendering overhead.

- Chores
  - Removed an unused dependency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-12 22:55:13 +00:00
59a8dad48e [Playground] ai chat demo (#7833)
Co-authored-by: Cursor Agent <[email protected]>
2025-08-12 15:17:03 -07:00
MananTank f7aa3e6239 [BLD-80] Dashboard: in-app wallet settings page UI improvements (#7843)
<!--

## 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 UI components and improving the country selection functionality in the dashboard application. It includes updates to transitions, checkbox indicators, and country names for better clarity and usability.

### Detailed summary
- Updated transition duration in `DynamicHeight.tsx`.
- Added `MinusIcon` for indeterminate state in `checkbox.tsx`.
- Corrected country names in `utils.ts` for several countries.
- Improved structure and styling of the `CountrySelector` component.
- Added `ChevronDownIcon` and `Button` for toggling tiers in `country-selector.tsx`.
- Refactored `Fieldset` and `FieldsetWithDescription` components for better layout and usability in settings forms.

> ✨ 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
  - Checkbox now supports an indeterminate state with a clear visual indicator.
  - Wallet settings sections gain per-section Save buttons with loading spinners.
  - SMS country selection adds collapsible tier cards, partial-selection counters, and improved country tiles with clear selection states.

- Refactor
  - Unified fieldset layouts with consistent headers, descriptions, dividers, and footers.
  - Branding settings reorganized into a responsive two‑column layout with improved flow.
  - Country tier rendering moved to modular TierCard components.

- Bug Fixes
  - Corrected several country display names (e.g., South Korea, North Macedonia, Eswatini, Saint Lucia).

- Style
  - Adjusted a height-transition timing/easing for dynamic-height animations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-12 21:41:49 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
827819b911 Version Packages (#7826)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-12 12:41:39 -06:00
gregfromstl 0d7e5e563d [SDK] Fix: Process undefined checks in vite (#7844)
<!--

## 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 addresses the resolution of the SDK in environments where `process` is undefined, ensuring that the checks for development and test environments are more robust.

### Detailed summary
- Updated the `IS_DEV` constant to check if `process` is defined before accessing its properties.
- Modified the `IS_TEST` constant similarly to ensure it only evaluates when `process` is defined.

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

<!-- end pr-codex -->

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

* **Bug Fixes**
  * Guarded environment checks to avoid runtime errors where Node globals are absent, improving stability across browsers, serverless, and edge runtimes.
  * More reliable detection of development and test modes, reducing unexpected crashes during bundling, CI testing, and client-side use.

* **Chores**
  * Added release metadata so the fix will be published as a patch.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-12 18:07:25 +00:00
Joaquim VergesandGitHub 8665e8fe14 [Portal] Improve responsive layout for request example header (#7841) 2025-08-12 03:11:29 -07:00
438027a40e [Docs] Replace custom API endpoint with OpenAPI component (#7829)
Co-authored-by: 0xFirekeeper <[email protected]>
2025-08-12 16:28:14 +12:00
FirekeeperandGitHub 66c7df98e4 Add TikTok as auth method for In-App and Ecosystem Wallets (#7817) 2025-08-11 20:02:58 -07:00
gregandGitHub 409ae17c15 Fix: Hide manage contract button when foreign wallet (#7832) 2025-08-11 20:55:47 -06:00
Joaquim VergesandGitHub be28b0c26e [Docs] Add AI Chat API documentation and endpoint metadata (#7828) 2025-08-11 19:45:48 -07:00
gregandGitHub c661a485d0 [Dashboard] Fix: change payment links -> payments (#7831) 2025-08-11 12:43:28 -06:00
gregandGitHub 90f36bc414 [SDK] Refactor: Only show user balances in payment selection screens (#7827) 2025-08-11 09:50:15 -06:00
gregandGitHub a77e98a48a [SDK] Fix: Deprecate bridge routes sortBy (#7825) 2025-08-10 19:03:26 -06:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
fde39eb883 Version Packages (#7820)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-10 16:25:15 -07:00
c672a84fc3 Add chinese locale to connect button (#7807)
Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: yashjoisar <[email protected]>
2025-08-10 16:17:18 -07:00
Joaquim VergesandGitHub d52855dbda [SDK] Fix supportedTokens address comparison in usePaymentMethods (#7824) 2025-08-10 16:16:11 -07:00
MananTank a3909e1794 Add caching to ContractCard, speed up /explore pageload (#7822)
<!--

## 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 `ContractRow`, `ContractPublisher`, and `ContractCard` components to streamline their props and improve caching mechanisms for fetching contract data.

### Detailed summary
- Simplified `ContractRow` props by removing the interface.
- Refactored `ContractPublisher` to use props directly instead of an interface.
- Updated `ContractPublisher` to utilize `Img` and `Blobbie` for avatar rendering.
- Introduced caching for `fetchPublishedContractVersion` and `resolvePublisherEnsAndAvatar`.
- Changed how `ContractPublisher` is rendered in `ContractCard` to use new props structure.

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

<!-- end pr-codex -->

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

* **New Features**
  * Improved performance and consistency for contract publisher information by introducing server-side caching for contract version data and publisher ENS details.
  * Enhanced publisher display with avatars and ENS names, including fallback visuals when no avatar is available.

* **Refactor**
  * Streamlined the contract publisher display by replacing the previous component with a simplified implementation accepting explicit props.
  * Updated type definitions for improved code clarity without affecting user-facing behavior.

* **Chores**
  * Minor import and type annotation adjustments for better maintainability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-09 15:59:46 +00:00
nischitpra f92201d353 webhook multiselect topic (#7818)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on modifying the handling of `sigHash` to support both single and multiple signature inputs across various components, improving user experience and functionality.

### Detailed summary
- Changed `sigHash` from a string to an array in multiple components.
- Updated validation and handling of `sigHash` in forms and utilities.
- Enhanced UI to reflect multiple signature selections.
- Adjusted props and methods to support multi-select functionality in `SignatureSelector` and related 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**
  * Added support for selecting multiple signature hashes in webhook filters and selectors.
  * Enhanced signature selectors to allow both single-select and multi-select modes, with improved handling of custom signatures.

* **Improvements**
  * Updated UI labels, placeholders, and descriptions to reflect multi-selection capabilities and pluralization.
  * Review and summary steps now display multiple selected signatures with appropriate formatting.
  * Webhook payloads and validation schemas now support arrays of signature hashes for improved flexibility.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-08 20:18:28 +00:00
gregandGitHub 45ba811950 [SDK] Fix: Sort payment quotes in ascending order (#7819) 2025-08-08 12:42:53 -07:00
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
788 changed files with 16919 additions and 8153 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 -3
View File
@@ -43,7 +43,6 @@
"next-themes": "^0.4.6",
"nextjs-toploader": "^1.6.12",
"nuqs": "^2.4.3",
"p-limit": "^6.2.0",
"papaparse": "^5.5.3",
"pluralize": "^8.0.0",
"posthog-js": "1.256.1",
@@ -57,7 +56,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 +128,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"
@@ -12,6 +12,7 @@ export type AuthOption =
| "google"
| "facebook"
| "x"
| "tiktok"
| "discord"
| "farcaster"
| "telegram"
@@ -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";
@@ -10,13 +10,14 @@ interface SignatureOption {
interface SignatureSelectorProps {
options: SignatureOption[];
value: string;
onChange: (val: string) => void;
value: string | string[];
onChange: (val: string | string[]) => void;
setAbi?: (abi: string) => void;
placeholder?: string;
disabled?: boolean;
secondaryTextFormatter?: (sig: SignatureOption) => string;
className?: string;
multiSelect?: boolean;
}
export function SignatureSelector({
@@ -28,6 +29,7 @@ export function SignatureSelector({
disabled,
secondaryTextFormatter,
className,
multiSelect = false,
}: SignatureSelectorProps) {
const [searchValue, setSearchValue] = useState("");
const inputRef = useRef<HTMLInputElement>(null);
@@ -42,38 +44,96 @@ export function SignatureSelector({
}));
}, [options, secondaryTextFormatter]);
// Check if the current value is a custom value (not in options)
const isCustomValue = value && !options.some((opt) => opt.value === value);
// Add the custom value as an option if needed
const allOptions = useMemo(() => {
if (isCustomValue && value) {
return [...formattedOptions, { label: value, value }];
// Handle both single and multi-select values
const currentValues = useMemo((): string[] => {
if (multiSelect) {
if (Array.isArray(value)) {
return value.filter(
(val): val is string =>
val !== undefined && val !== null && val !== "",
);
} else {
return value ? [value] : [];
}
} else {
if (Array.isArray(value)) {
return value.length > 0 && value[0] ? [value[0]] : [];
} else {
return value ? [value] : [];
}
}
return formattedOptions;
}, [formattedOptions, isCustomValue, value]);
}, [value, multiSelect]);
// Single-select MultiSelect wrapper
// Check if the current values include custom values (not in options)
const customValues = useMemo((): string[] => {
return currentValues.filter(
(val): val is string =>
val !== undefined &&
val !== null &&
val !== "" &&
!options.some((opt) => opt.value === val),
);
}, [currentValues, options]);
// Add the custom values as options if needed
const allOptions = useMemo(() => {
const customOptions = customValues.map((val) => ({
label: val,
value: val,
}));
return [...formattedOptions, ...customOptions];
}, [formattedOptions, customValues]);
// Multi-select or single-select MultiSelect wrapper
const handleSelectedValuesChange = useCallback(
(selected: string[]) => {
// Always use the last selected value for single-select behavior
const selectedValue =
selected.length > 0 ? (selected[selected.length - 1] ?? "") : "";
onChange(selectedValue);
const found = options.find((opt) => opt.value === selectedValue);
if (setAbi) {
setAbi(found?.abi || "");
if (multiSelect) {
// Multi-select behavior
onChange(selected);
// For multi-select, we'll use the ABI from the first selected option that has one
const firstOptionWithAbi = selected.find((selectedValue) => {
const found = options.find((opt) => opt.value === selectedValue);
return found?.abi;
});
if (setAbi && firstOptionWithAbi) {
const found = options.find((opt) => opt.value === firstOptionWithAbi);
setAbi(found?.abi || "");
}
} else {
// Single-select behavior (maintain backward compatibility)
const selectedValue =
selected.length > 0 ? (selected[selected.length - 1] ?? "") : "";
onChange(selectedValue);
const found = options.find((opt) => opt.value === selectedValue);
if (setAbi) {
setAbi(found?.abi || "");
}
}
setSearchValue("");
},
[onChange, setAbi, options],
[onChange, setAbi, options, multiSelect],
);
// Handle custom value entry
const handleInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === "Enter" && searchValue.trim()) {
if (!options.some((opt) => opt.value === searchValue.trim())) {
onChange(searchValue.trim());
if (multiSelect) {
// Add to existing values for multi-select
const currentArray = Array.isArray(value)
? value
: value
? [value]
: [];
const filteredArray = currentArray.filter(
(val): val is string => val !== undefined && val !== null,
);
const newValues = [...filteredArray, searchValue.trim()];
onChange(newValues);
} else {
// Replace value for single-select
onChange(searchValue.trim());
}
if (setAbi) setAbi("");
setSearchValue("");
// Optionally blur input
@@ -106,7 +166,7 @@ export function SignatureSelector({
customSearchInput={customSearchInput}
customTrigger={null}
disabled={disabled}
maxCount={1}
maxCount={multiSelect ? 100 : 1}
onSelectedValuesChange={handleSelectedValuesChange}
options={allOptions}
overrideSearchFn={(option, searchTerm) =>
@@ -116,11 +176,13 @@ export function SignatureSelector({
placeholder={placeholder}
renderOption={(option) => <span>{option.label}</span>}
searchPlaceholder={placeholder}
selectedValues={value ? [value] : []}
selectedValues={currentValues}
/>
{isCustomValue && (
{customValues.length > 0 && (
<div className="mt-2 rounded border border-warning-200 bg-warning-50 px-2 py-1 text-warning-700 text-xs">
You entered a custom signature. Please provide the ABI below.
{multiSelect
? `You entered ${customValues.length} custom signature${customValues.length > 1 ? "s" : ""}. Please provide the ABI below.`
: "You entered a custom signature. Please provide the ABI below."}
</div>
)}
</div>
@@ -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>
);
}
@@ -64,6 +64,7 @@ export const MultiSelect = forwardRef<HTMLButtonElement, MultiSelectProps>(
popoverContentClassName,
showSelectedValuesInModal = false,
customSearchInput,
customTrigger,
...props
},
ref,
@@ -144,13 +145,18 @@ export const MultiSelect = forwardRef<HTMLButtonElement, MultiSelectProps>(
// scroll to top when options change
const popoverElRef = useRef<HTMLDivElement>(null);
// Filter out customTrigger from props to avoid passing it to Button
const buttonProps = Object.fromEntries(
Object.entries(props).filter(([key]) => key !== "customTrigger"),
) as React.ButtonHTMLAttributes<HTMLButtonElement>;
return (
<Popover modal onOpenChange={setIsPopoverOpen} open={isPopoverOpen}>
<PopoverTrigger asChild>
{props.customTrigger || (
{customTrigger || (
<Button
ref={ref}
{...props}
{...buttonProps}
className={cn(
"flex h-auto min-h-10 w-full items-center justify-between rounded-md border border-border bg-inherit p-3 hover:bg-inherit",
className,
@@ -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,19 +1,20 @@
import { moduleToBase64 } from "app/(app)/(dashboard)/published-contract/utils/module-base-64";
import { RocketIcon, ShieldCheckIcon } from "lucide-react";
import { unstable_cache } from "next/cache";
import Link from "next/link";
import { ClientOnly } from "@/components/blocks/client-only";
import { fetchPublishedContractVersion } from "@/components/contract-components/fetch-contracts-with-versions";
import { resolveAvatar } from "thirdweb/extensions/ens";
import { fetchPublishedContractVersion } from "@/api/contract/fetch-contracts-with-versions";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
import { resolveEns } from "@/lib/ens";
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 {
type ContractCardProps = {
publisher: string;
contractId: string;
titleOverride?: string;
@@ -29,7 +30,7 @@ interface ContractCardProps {
moduleId: string;
version?: string;
}[];
}
};
function getContractUrl(
{
@@ -74,6 +75,72 @@ function getContractUrl(
return replaceDeployerAddress(pathName);
}
const cached_fetchPublishedContractVersion = unstable_cache(
async (publisher: string, contractId: string, version: string = "latest") => {
const result = await fetchPublishedContractVersion(
publisher,
contractId,
serverThirdwebClient,
version,
).catch(() => null);
if (!result) {
return null;
}
const publisherEnsAndAvatar = result.publisher
? await cached_resolvePublisherEnsAndAvatar(result.publisher)
: undefined;
// Note: Do not return BigInt - it can't be serialized and cached by unstable_cache and will throw an error
return {
name: result.name,
displayName: result.displayName,
description: result.description,
publisher: {
address: result.publisher,
ensName: publisherEnsAndAvatar?.ensName,
ensAvatar: publisherEnsAndAvatar?.ensAvatar,
},
version: result.version,
audit: result.audit,
};
},
["fetchPublishedContractVersion"],
{
revalidate: 3600, // 1 hour
},
);
const cached_resolvePublisherEnsAndAvatar = unstable_cache(
async (_addressOrEns: string) => {
const addressOrEns = replaceDeployerAddress(_addressOrEns);
const [ensNameInfo, ensAvatar] = await Promise.allSettled([
resolveEns(addressOrEns, serverThirdwebClient),
resolveAvatar({
client: serverThirdwebClient,
name: addressOrEns,
}),
]);
return {
ensName:
ensNameInfo.status === "fulfilled"
? ensNameInfo.value?.ensName
: undefined,
address:
ensNameInfo.status === "fulfilled"
? ensNameInfo.value?.address
: undefined,
ensAvatar: ensAvatar.status === "fulfilled" ? ensAvatar.value : undefined,
};
},
["resolvePublisherEnsAndAvatar"],
{
revalidate: 3600, // 1 hour
},
);
export async function ContractCard({
publisher,
contractId,
@@ -83,12 +150,11 @@ export async function ContractCard({
modules = [],
isBeta,
}: ContractCardProps) {
const publishedContractResult = await fetchPublishedContractVersion(
const publishedContractResult = await cached_fetchPublishedContractVersion(
publisher,
contractId,
serverThirdwebClient,
version,
).catch(() => null);
);
if (!publishedContractResult) {
return null;
@@ -186,13 +252,12 @@ export async function ContractCard({
!modules?.length && "mt-auto",
)}
>
{publishedContractResult.publisher && (
<ClientOnly ssr={<Skeleton className="size-5 rounded-full" />}>
<ContractPublisher
addressOrEns={publishedContractResult.publisher}
client={getClientThirdwebClient()}
/>
</ClientOnly>
{publishedContractResult.publisher.address && (
<ContractPublisher
address={publishedContractResult.publisher.address}
ensName={publishedContractResult.publisher.ensName || undefined}
ensAvatar={publishedContractResult.publisher.ensAvatar || undefined}
/>
)}
<div className="flex items-center justify-between">
@@ -0,0 +1,36 @@
"use client";
import Link from "next/link";
import { Blobbie } from "thirdweb/react";
import { Img } from "@/components/blocks/Img";
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler";
import { shortenIfAddress } from "@/utils/usedapp-external";
export function ContractPublisher(props: {
ensName: string | undefined;
address: string;
ensAvatar: string | undefined;
}) {
const displayName = props.ensName || props.address;
return (
<Link
className="flex shrink-0 items-center gap-1.5 hover:underline"
href={`/${displayName}`}
target="_blank"
>
<Img
className="size-5 rounded-full object-cover"
src={
resolveSchemeWithErrorHandler({
client: getClientThirdwebClient(),
uri: props.ensAvatar,
}) || ""
}
fallback={<Blobbie address={props.address} />}
/>
<span className="text-xs"> {shortenIfAddress(displayName)}</span>
</Link>
);
}
@@ -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={[
{
@@ -17,9 +17,7 @@ export function DynamicHeight(props: {
boxSizing: "border-box",
height: height ? `${height}px` : "auto",
overflow: "hidden",
transition:
props.transition ||
"height 210ms cubic-bezier(0.175, 0.885, 0.32, 1.1)",
transition: props.transition || "height 250ms ease",
}}
>
<div
@@ -1,7 +1,7 @@
"use client";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { CheckIcon } from "lucide-react";
import { CheckIcon, MinusIcon } from "lucide-react";
import * as React from "react";
import { cn } from "@/lib/utils";
@@ -21,7 +21,11 @@ const Checkbox = React.forwardRef<
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")}
>
<CheckIcon className="size-4" />
{props.checked === "indeterminate" ? (
<MinusIcon className="size-3" />
) : (
<CheckIcon className="size-4" />
)}
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
));
@@ -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";
+34 -14
View File
@@ -1,5 +1,4 @@
import { useQuery } from "@tanstack/react-query";
import pLimit from "p-limit";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import Papa from "papaparse";
import { useCallback, useState } from "react";
import { isAddress, type ThirdwebClient, ZERO_ADDRESS } from "thirdweb";
@@ -95,6 +94,7 @@ export function useCsvUpload<
// Always gonna need the wallet address
T extends { address: string },
>(props: Props<T>) {
const queryClient = useQueryClient();
const [rawData, setRawData] = useState<
T[] | Array<T & { [key in string]: unknown }>
>(props.defaultRawData || []);
@@ -132,16 +132,29 @@ export function useCsvUpload<
[props.csvParser],
);
const [normalizeProgress, setNormalizeProgress] = useState({
total: 0,
current: 0,
});
const normalizeQuery = useQuery({
queryFn: async () => {
const limit = pLimit(50);
const results = await Promise.all(
rawData.map((item) => {
return limit(() =>
const batchSize = 50;
const results = [];
for (let i = 0; i < rawData.length; i += batchSize) {
const batch = rawData.slice(i, i + batchSize);
setNormalizeProgress({
total: rawData.length,
current: i,
});
const batchResults = await Promise.all(
batch.map((item) =>
checkIsAddress({ item: item, thirdwebClient: props.client }),
);
}),
);
),
);
results.push(...batchResults);
}
return {
invalidFound: !!results.find((item) => !item?.isValid),
result: processAirdropData(results),
@@ -153,12 +166,18 @@ export function useCsvUpload<
const removeInvalid = useCallback(() => {
const filteredData = normalizeQuery.data?.result.filter(
({ isValid }) => isValid,
(d) => d.isValid && d.resolvedAddress !== ZERO_ADDRESS,
);
// double type assertion is save here because we don't really use this variable (only check for its length)
// Also filteredData's type is the superset of T[]
setRawData(filteredData as unknown as T[]);
}, [normalizeQuery.data?.result]);
if (filteredData && normalizeQuery.data) {
// Directly update the query result instead of setting new state to avoid triggering refetch
queryClient.setQueryData(["snapshot-check-isAddress", rawData], {
...normalizeQuery.data,
result: filteredData,
invalidFound: false, // Since we removed all invalid items
});
}
}, [normalizeQuery.data, queryClient, rawData]);
const processData = useCallback(
(data: T[]) => {
@@ -181,5 +200,6 @@ export function useCsvUpload<
removeInvalid,
reset,
setFiles,
normalizeProgress,
};
}
@@ -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();
}, []);
}

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