Compare commits

..
Author SHA1 Message Date
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
c7cb6ae7a4 Version Packages (#6728)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-17 18:01:02 -07:00
Joaquim VergesandGitHub 7ecfcb95af [SDK] Add thirdweb branding to PayEmbed (#6760) 2025-04-17 17:55:11 -07:00
Joaquim VergesGitHubCopilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
4cf15a2475 [SDK] Use insight for 1155 getNFTs, getOwnedNFTs and getNFT (#6753)
Signed-off-by: Joaquim Verges <[email protected]>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-04-17 17:01:20 -07:00
Joaquim VergesandGitHub 62ce05ec35 [SDK] Improve error message for getUser (#6758) 2025-04-17 16:12:42 -07:00
MananTank 563c4c7df1 Dashboard: disable prefetch for footer links (#6756)
<!--

## 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 refactors the `AppFooter` component in `app-footer.tsx` by introducing a new `footerLinks` array to manage footer link data and replaces individual `Link` components with a new `FooterLink` function to enhance maintainability and readability.

### Detailed summary
- Added `footerLinks` array containing link objects with `href` and `label`.
- Replaced individual `Link` components with a mapped `FooterLink` component.
- Introduced `FooterLink` function to streamline link rendering and improve code clarity.

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

<!-- end pr-codex -->
2025-04-17 19:41:27 +00:00
Joe-ThirdwebandGitHub 32c91abc02 [Dashboard] Adding telegram field to support ticket creation (#6754) 2025-04-17 21:41:09 +02:00
Manan TankandGitHub 7a2dc25129 [NEB-167] Add Nebula floating chat button in chain and contract pages (#6750) 2025-04-17 18:00:20 +02:00
jnsdls 32568fd421 Add "Invite Team Members" button to team overview page (#6755)
# Add "Invite Team Members" Button to Team Overview Page

This PR adds a new "Invite Team Members" button to the Team Overview page, replacing the previous implementation. The changes include:

- Created a new reusable `InviteTeamMembersButton` component
- Added the button to the Team Overview page header
- Removed the old "Share Button" component from the TeamProjectsPage
- Improved the layout of the Team Overview page header to accommodate the new button
- Maintained analytics tracking for the invite action

The button links to the team members settings page and includes a user plus icon for better visual indication of its purpose.
2025-04-17 08:06:51 +00:00
GiselleNessi 9091e813ab added invite team button in dash homepage (#6751)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new `ShareButton` component to the `TeamProjectsPage`, allowing users to invite team members. It also tracks the button click event for analytics and includes a link to the team members settings.

### Detailed summary
- Added `ShareButton` component for inviting team members.
- Integrated `useTrack` hook to track button click events.
- Added a link to the team members settings using `Link`.
- Imported `UserPlus` icon from `lucide-react`.

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

<!-- end pr-codex -->
2025-04-17 05:21:12 +00:00
Joaquim VergesandGitHub 23d375778b [SDK] Simplify RPC request handling (#6752) 2025-04-16 18:07:46 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
e7b0110803 chore(deps-dev): bump msw from 2.7.3 to 2.7.4 (#6723)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-16 16:49:50 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1825ac857e chore(deps-dev): bump vite from 6.2.5 to 6.2.6 in the npm_and_yarn group (#6711)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-16 16:09:15 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
3ce2d0602e chore(deps-dev): bump @types/react-dom from 19.1.1 to 19.1.2 (#6721)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-16 16:08:43 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
4bde2eb157 chore(deps): bump @abstract-foundation/agw-react from 1.5.4 to 1.6.4 (#6722)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-16 16:06:23 -07:00
Joaquim VergesandGitHub 8d4d991e57 [SDK] Use insight for erc721/getNFTs and erc721/getOwnedNFTs (#6741) 2025-04-16 15:53:14 -07:00
MananTank 22090cc400 [NEB-170] Nebula: Various UI changes (#6747)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on enhancing the user interface and experience of the `Nebula` app by updating text content, adjusting styles, and introducing a new `StyledMarkdownRenderer` component for better message rendering.

### Detailed summary
- Updated text in `EmptyStateChatPageContent.tsx` and `ContextFilters.tsx` to reflect new terminology.
- Changed section headings from `Context` to `Settings`.
- Improved styling for various components, including `ChatSidebarLink` and buttons.
- Introduced `StyledMarkdownRenderer` for consistent message formatting.
- Adjusted scrollbar styles in `global.css`.
- Modified button states and classes for better responsiveness and appearance.

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

<!-- end pr-codex -->
2025-04-16 21:16:02 +00:00
MananTank 95a6c38703 [NEB-173]: Hide testnets in nebula chain selector (#6746)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces the ability to hide test networks in the `MultiNetworkSelector` component and updates its props accordingly.

### Detailed summary
- Added `hideTestnets` prop to `MultiNetworkSelector` in `NetworkSelectors.tsx`.
- Implemented filtering of `sortedChains` to exclude test networks if `hideTestnets` is true.
- Updated dependency array in `useEffect` to include `props.hideTestnets`.

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

<!-- end pr-codex -->
2025-04-16 21:16:01 +00:00
MananTank b1060de7da [NEB-172]: Remove Nebula waitlist (#6745)
<!--

## 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 removing components and related functionality for managing the Nebula waitlist in the application. It simplifies the codebase by eliminating unused imports and references to the waitlist, streamlining the `ProjectSidebarLayout`, and adjusting the layout logic.

### Detailed summary
- Deleted files related to the Nebula waitlist functionality.
- Removed `getTeamNebulaWaitList` function and its associated type.
- Updated `ProjectSidebarLayout` to eliminate the `showNebula` prop.
- Simplified `ProjectLayout` by removing waitlist checks and related logic.
- Adjusted `Page` component to remove references to `NebulaWaitListPage`.
- Cleaned up the `Layout` component by removing waitlist handling and related imports.

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

<!-- end pr-codex -->
2025-04-16 21:16:01 +00:00
jnsdls 0aca56317a Fix RPC usage page to handle missing peak date (#6748)
This PR adds a null check for the peak date in the RPC Usage page to handle cases where there are no requests in the last 24 hours. It displays "No Requests in last 24 hours" instead of attempting to format a non-existent date. Additionally, it destructures the `averageRate` directly from the API data to improve code readability when passing it to the RateGraph and CountGraph components.
2025-04-16 16:42:07 +00:00
MananTank 5463baa7da Nebula: Fix chat session randomly showing empty state after first prompt (#6744)
<!--

## 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 an issue with updating the page URL in the `ChatPageContent.tsx` file due to changes in Next.js behavior. The existing method for updating the URL is commented out, and a temporary solution is proposed.

### Detailed summary
- Commented out the line: `window.history.replaceState({}, "", `/chat/${sessionId}`);`
- Added comments explaining that the URL update does not work anymore due to Next.js changes.
- Stated intention to not update the URL until a better solution is found.

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

<!-- end pr-codex -->
2025-04-16 11:34:58 +00:00
MananTank 64ffe199af Dashboard: Remove reo.dev from CSP script-src in next.config (#6740)
<!--

## 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 Content Security Policy (CSP) in the `next.config.ts` file by modifying the `script-src` directive, removing `static.reo.dev` from the list of allowed sources.

### Detailed summary
- Removed `static.reo.dev` from the `script-src` directive in the CSP.
- No other changes to the CSP directives were made.

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

<!-- end pr-codex -->
2025-04-16 10:53:29 +00:00
MananTank 76b522e7bc Portal: Use next-themes for theme management instead of custom code (#6739)
<!--

## 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 theme management in the application by integrating `next-themes` for better theme switching functionality and adding a new `Skeleton` component for loading states.

### Detailed summary
- Removed `theme.tsx` and its usage.
- Added `next-themes` dependency in `package.json`.
- Introduced `Skeleton` component in `skeleton.tsx`.
- Updated `ThemeSwitcher` to use `next-themes` for theme management.
- Implemented `useIsClientMounted` for client-side rendering checks.
- Wrapped the application in `ThemeProvider` in `layout.tsx`.
- Modified CSS for dark and light themes.

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

<!-- end pr-codex -->
2025-04-16 10:43:16 +00:00
Joe-Thirdweb 49786404b3 [Dashboard] Feature : Adding Next Steps guide into chain pages (#6743)
Add actionable next step guide for users who visit a chain page.

Steps to Test:
1. Visit thirdweb.com/{chainID}
2. Scroll to the bottom and you should see
![image](https://github.com/user-attachments/assets/df68ad4b-a0b7-4384-9e2d-1bb207400087)
3. Click on the article
4. Get redirected to a blog post.

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new component called `NextSteps` to the `page.tsx` file in the dashboard application. This component provides users with actionable links and information related to integrating in-app wallets, enhancing user experience and adoption.

### Detailed summary
- Added `NextSteps` component to `page.tsx`.
- `NextSteps` displays a title and a link for creating a login for the specified `chain`.
- Integrated tracking for link clicks using `useTrack` to monitor user engagement.
- Included a brief description under the link in `NextSteps`.

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

<!-- end pr-codex -->
2025-04-16 05:24:27 +00:00
MananTank 64bd603301 Nebula: Do not override chains set from search params (#6738)
<!--

## 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 refines the logic for updating `updatedContextFilters.chainIds` by ensuring that it only attempts to retrieve last used chains from storage when `chainIds` is empty. It also maintains error handling for local storage access.

### Detailed summary
- Moved the check for `updatedContextFilters.chainIds` length to before retrieving last used chains.
- Retained the logic to fetch last used chain IDs from storage if `chainIds` is empty.
- Preserved error handling for local storage access.

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

<!-- end pr-codex -->
2025-04-15 22:16:26 +00:00
MananTank 60a5b7c3e7 [NEB-123] Nebula: Allow setting context with search params (#6736)
<!--

## 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 chat functionality by introducing `prefillMessage` support across various components and updating the way parameters are handled in the chat interface.

### Detailed summary
- Replaced `initialPrompt` with `initialParams` in chat components.
- Added `prefillMessage` prop to `ChatBar`, `EmptyStateChatPageContent`, and related story files.
- Updated `NebulaLogin` to handle search parameters.
- Modified `ChatPageContent` to utilize `initialParams` for message handling.
- Introduced a new function `getChainIds` for fetching chain IDs based on user input.

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

<!-- end pr-codex -->
2025-04-15 21:44:25 +00:00
jnsdls baf95b9077 Fix RPC usage graphs and improve rate limit visualization (#6737)
# Improve RPC Usage Graphs

This PR enhances the RPC usage graphs in the dashboard:

- Only show rate limited requests in the legend when there are actually rate limited requests
- Fix data processing by removing the `.slice(1, -1)` which was incorrectly trimming data points
- Ensure numeric values by explicitly converting to Number
- Move data slicing to the parent component for better control
- Add a type assertion comment to handle the TypeScript warning
- Add a check to conditionally display relevant chart configuration

These changes improve the accuracy and presentation of the RPC usage data visualization.

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the `CountGraph` component by improving the handling of rate-limited data and formatting the `currentRateLimit` value for better readability.

### Detailed summary
- Updated the `currentRateLimit` display to use `toLocaleString()`.
- Added a check for `hasAnyRateLimited` to conditionally configure the chart.
- Modified the `config` object to include `rateLimitedCount` only if there are any rate-limited requests.
- Converted `includedCount` and `rateLimitedCount` to `Number` type during data mapping.

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

<!-- end pr-codex -->
2025-04-15 21:16:37 +00:00
jnsdls e29f749342 Add support for starter, accelerate, and scale tiers (#6735)
fixes: CORE-826

# Add support for new pricing tiers in ticket creation

This PR adds support for the new pricing tiers (starter, accelerate, scale) in the ticket creation system. It updates the `planToCustomerId` mapping to include these new tiers and improves the fallback logic to use the free tier when a customer's plan doesn't have a corresponding Unthread customer ID.
2025-04-15 22:15:28 +02:00
Joaquim VergesandGitHub 2a7df337f7 [SDK] Improve error messages for failed HTTP requests (#6734) 2025-04-15 12:43:54 -07:00
jnsdls 348e3ab4ad Add RPC usage dashboard with rate limit monitoring (#6733)
currently blocked by analytics API needing to go to prod

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new RPC usage dashboard for monitoring requests over the last 24 hours. It adds new components for visualizing request data and enhances the existing layout to accommodate this functionality.

### Detailed summary
- Added a new `RPC` tab in the usage layout.
- Introduced `CountGraph` and `RateGraph` components for visualizing RPC data.
- Implemented API call to fetch last 24 hours RPC usage data.
- Enhanced `ThirdwebAreaChart` to support max limit display.
- Updated error handling and user feedback for API data fetching.

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

<!-- end pr-codex -->
2025-04-15 19:25:24 +00:00
MananTank e4590a7ada [NEB-168] Nebula: Improve Execute Transaction Card UI (#6731)
![image.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/yNOf1svJ8o3zjO7zQouZ/5807d025-2166-4b28-bedb-2baccd52054c.png)
2025-04-16 00:42:26 +05:30
gregandGitHub a45c558e06 [SDK] Fix: Improve PayEmbed error message parsing (#6732) 2025-04-15 11:45:48 -07:00
a3e7300723 Get events from insight (#6683)
Co-authored-by: Joaquim Verges <[email protected]>
2025-04-15 04:02:59 -07:00
YashandGitHub 4c1f384a63 Fix dynamic contract publish (#6729) 2025-04-15 02:55:20 -07:00
arcoraven ec099a781a chore: Update rate limit to sliding window (10s) (#6730)
Updates the rate limit to use a sliding window

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the rate limiting functionality in the `rateLimit` function to utilize a sliding window approach instead of a fixed window, enhancing how requests are counted over time.

### Detailed summary
- Renamed `RATE_LIMIT_WINDOW_SECONDS` to `SLIDING_WINDOW_SECONDS`.
- Modified Redis interaction to use `mget` for batch fetching request counts.
- Updated the logic to calculate total requests over the last 10 seconds.
- Removed `sampleRate` handling and its related logic.
- Changed error messages for exceeding rate limits.
- Adjusted tests to align with the new sliding window logic.

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

<!-- end pr-codex -->
2025-04-15 09:30:46 +00:00
Joaquim VergesandGitHub 185d2f309c [SDK] Add Insight API client (#6706) 2025-04-14 20:33:40 -07:00
gregandGitHub e8fb41752e [Dashboard] Feature: bridge UI (#6727) 2025-04-14 16:53:46 -07:00
jnsdls 3f42f522b7 Add RPC usage chart to dashboard (#6719)
closes: TOOL-4079

### TL;DR

Added RPC usage chart to the team usage overview page.

### What changed?

- Created a new API endpoint `fetchRPCUsage` to retrieve RPC usage data from the analytics service
- Added a stacked bar chart to visualize RPC usage in the team usage overview page
- The chart displays both regular RPC requests and rate-limited requests
- Replaced the static "Unlimited Requests" display with an interactive visualization

### How to test?

1. Navigate to a team's usage overview page
2. Verify that the RPC Requests section now shows a stacked bar chart
3. Confirm that the chart displays data for the past 7 days
4. Check that the chart properly differentiates between regular RPC requests and rate-limited requests

### Why make this change?

This change provides teams with better visibility into their RPC usage patterns over time, allowing them to monitor both successful requests and rate-limited requests. This visualization helps teams understand their API consumption patterns and make informed decisions about their plan requirements.

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new feature to fetch and display RPC usage data in the dashboard. It enhances the usage overview by integrating RPC metrics, providing insights into included and rate-limited requests.

### Detailed summary
- Added `RPCUsageDataItem` type and `fetchRPCUsage` function in `rpc.ts`.
- Updated `page.tsx` to fetch RPC usage data alongside account usage and subscriptions.
- Passed `rpcUsage` data to the `Usage` component.
- Modified `Usage` component to process and display RPC usage data using `ThirdwebBarChart`.

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

<!-- end pr-codex -->
2025-04-14 20:27:11 +00:00
MananTank bb29e1f705 [TOOL-4033] Playground: UI revamp and cleanup, Improved engine playgrounds, updated theme (#6720)
<!--

## 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 removing unused assets and files, updating configurations, and enhancing UI components across the `playground-web` application. It also adds new constants and improves code structure for better maintainability.

### Detailed summary
- Deleted numerous unused image files and TypeScript files.
- Updated `postcss.config.js` and added Tailwind CSS configurations.
- Enhanced UI components with consistent styling changes.
- Introduced new constants for `mintExample` and `claimExample`.
- Improved API handling in the `engine` actions.
- Refactored several components to use `PageLayout` for better structure.
- Added code examples for minting and airdrop functionalities.

> The following files were skipped due to too many changes: `apps/playground-web/src/app/engine/airdrop/_components/airdrop-code.tsx`, `apps/playground-web/src/app/connect/pay/commerce/page.tsx`, `apps/playground-web/src/components/code/code-example.tsx`, `apps/playground-web/src/app/connect/in-app-wallet/ecosystem/page.tsx`, `apps/playground-web/src/components/blocks/APIHeader.tsx`, `apps/playground-web/src/app/connect/account-abstraction/connect/page.tsx`, `apps/playground-web/src/app/connect/pay/fund-wallet/page.tsx`, `apps/playground-web/src/app/connect/account-abstraction/sponsor/page.tsx`, `apps/playground-web/src/components/blocks/NetworkSelectors.tsx`, `apps/playground-web/src/app/connect/ui/page.tsx`, `apps/playground-web/src/app/connect/in-app-wallet/sponsor/page.tsx`, `apps/playground-web/src/components/in-app-wallet/custom-login-form.tsx`, `apps/playground-web/src/app/connect/pay/transactions/page.tsx`, `apps/playground-web/src/app/connect/in-app-wallet/page.tsx`, `apps/playground-web/src/components/in-app-wallet/profile-sections.tsx`, `apps/playground-web/src/app/connect/account-abstraction/native-aa/page.tsx`, `apps/playground-web/src/app/engine/airdrop/_components/airdrop-preview.tsx`, `apps/playground-web/src/components/headless-ui/token-examples.tsx`, `apps/playground-web/src/components/headless-ui/nft-examples.tsx`, `apps/playground-web/src/app/connect/sign-in/headless/page.tsx`, `apps/playground-web/src/app/connect/auth/page.tsx`, `apps/playground-web/src/app/globals.css`, `apps/playground-web/src/app/engine/webhooks/_components/webhooks-preview.tsx`, `apps/playground-web/src/app/connect/pay/embed/LeftSection.tsx`, `apps/playground-web/src/app/engine/airdrop/_components/airdrop-card.tsx`, `apps/playground-web/src/app/connect/blockchain-api/page.tsx`, `apps/playground-web/src/app/engine/minting/_components/mint-preview.tsx`, `apps/playground-web/src/components/headless-ui/account-examples.tsx`, `pnpm-lock.yaml`

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

<!-- end pr-codex -->
2025-04-14 19:54:24 +00:00
jnsdls 76822982f3 Add billing warning and open invoice filtering (#6718)
# Add Warning for Teams with Invalid Payment Methods

This PR adds a warning message for teams with invalid payment methods, preventing them from upgrading plans until they resolve outstanding invoices.

## Changes:

- Added a warning component that displays when a team has an `invalidPayment` billing status
- Added a filter to view only open invoices in the invoices page
- Updated the `getTeamInvoices` function to accept an optional `status` parameter
- Disabled checkout buttons for teams with invalid payment methods
- Added a warning in the cancel plan modal for teams with unpaid invoices
- Added a link to direct users to pay their outstanding invoices

These changes help users understand why they can't upgrade their plan and guides them to resolve payment issues.
2025-04-14 14:19:42 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
52261a5740 Version Packages (#6715)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-14 00:44:22 -07:00
Joaquim VergesandGitHub cd30ba4f3a [Dashboard] Add partner-based analytics for ecosystem wallet usage (#6717) 2025-04-13 22:13:15 -07:00
gregandGitHub 0ea898712b [SDK] Fix: Error message cleanup on UB embed (#6716) 2025-04-13 19:17:21 -07:00
gregfromstl 1b2caecfc4 [Dashboard] Feature: Improve chainlist bridge copy (#6714)
Closes: tool-4076
![Screenshot 2025-04-13 at 17 24 15](https://github.com/user-attachments/assets/1e6a3f04-cf0d-4dfa-9924-b5b611caaa7a)

<!--

## 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 `BuyFundsSection` component to reflect a shift in terminology from "Buy Funds" to "Bridge Funds," enhancing clarity regarding the functionality offered.

### Detailed summary
- Changed the `SectionTitle` from "Buy Funds" to "Bridge".
- Updated the heading text from "Buy Funds on {sanitizedChainName} using Universal Bridge" to "Bridge Funds to {sanitizedChainName}".
- Modified the paragraph text to clarify the process of obtaining the native currency.
- Altered the `PayModalButton` label from "Buy Funds on {sanitizedChainName}" to "Bridge to {sanitizedChainName}".

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

<!-- end pr-codex -->
2025-04-14 01:28:24 +00:00
Joaquim VergesandGitHub e574114188 [SDK] Don't retry on user connection rejection in EIP1193 connector (#6712) 2025-04-13 17:28:28 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
aa733b7b3e Version Packages (#6709)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-13 15:50:19 -07:00
Joaquim VergesandGitHub fa0d1141da [SDK] Add retry logic to predictAccountAddress (#6707) 2025-04-13 15:31:08 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
f618e2bffb Version Packages (#6705)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-11 22:40:10 +02:00
Jonas DanielsandGitHub 3ab31c8210 Fix incrBy to incrby in rate limit implementation (#6704) 2025-04-11 22:38:34 +02:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
8eb5d8c01e Version Packages (#6703)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-11 22:30:14 +02:00
Jonas DanielsandGitHub d036b87ae1 Refactor rate limiting to use separate read/write Redis operations (#6702) 2025-04-11 22:26:11 +02:00
YashandGitHub c86cac4d61 Fix getClaimConditions (#6701) 2025-04-11 19:19:32 +02:00
MananTank af09576247 Dashboard: Fix contract overpage page overflow when there are too many events in single tx (#6700)
<!--

## 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 `ContractOverviewPage` and `LatestEvents` components in a React application by adding overflow handling and improving the display of event data.

### Detailed summary
- Added `overflow-hidden` class to the `div` in `ContractOverviewPage.tsx`.
- Refactored the mapping of `allEvents` in `LatestEvents.tsx` to use a block structure for better readability.
- Limited the displayed events to 3 and added a button to show additional events if more than 3 exist.

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

<!-- end pr-codex -->
2025-04-11 17:01:47 +00:00
Manan TankandGitHub 316cef1a58 [TOOL-4057] Dashboard: Update Storage pinning card in team usage page (#6697) 2025-04-11 18:08:19 +02:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1bfcd375e4 Version Packages (#6696)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-10 20:51:36 -07:00
Joaquim VergesandGitHub 86070fa309 [Pay] Add feePayer option for direct_payment mode in PayEmbed (#6695) 2025-04-10 20:43:43 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
c5bf5a8594 Version Packages (#6694)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-10 14:16:49 -07:00
Joaquim VergesandGitHub c1d6bb7848 [Pay] Add extra gas buffer for quote and transfer transactions (#6693) 2025-04-10 14:10:45 -07:00
Toomas OosaluandGitHub e9db59e8da update insight plaground routes (#6692) 2025-04-10 12:55:59 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1db216545a Version Packages (#6691)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-10 12:55:22 -07:00
Joaquim VergesandGitHub 0a798c58b3 [SDK] Add smartAccountAddress to getUser result (#6690) 2025-04-10 12:17:31 -07:00
Joe-ThirdwebandGitHub cfacfc28a8 [Dashboard] Fix : Adding COTI chain page details (#6689)
Signed-off-by: Joe-Thirdweb <[email protected]>
2025-04-10 19:20:31 +02:00
KimaniandGitHub 2d978aea35 [Portal] Fix: updates broken-links on nebula sessions docs (#6687) 2025-04-10 19:17:19 +02:00
MananTank ca382f442a Dashboard: Remove certain warning logs (#6681)
<!--

## 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 package dependencies and configuration settings for the project, including the addition of `prettier` and adjustments to `@aws-sdk/client-sso-oidc` references in the `pnpm-lock.yaml` file.

### Detailed summary
- Added `public-hoist-pattern` entries in `.npmrc`.
- Updated `next.config.ts` to modify `config.externals`.
- Added `prettier` dependency in `package.json`.
- Updated `pnpm-lock.yaml` to reflect the new `prettier` version.
- Cleaned up references to `@aws-sdk/client-sso-oidc` in `pnpm-lock.yaml`.

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

<!-- end pr-codex -->
2025-04-10 12:59:07 +00:00
0xFirekeeper 840b1a264f [Portal] Hotfix Unity sidebar link (#6688)
<!--

## 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 sidebar navigation in the `sidebar.tsx` file, changing the name and link for a specific menu item.

### Detailed summary
- Changed the menu item name from `"Payments"` to `"Universal Bridge"`.
- Updated the `href` from `"/dotnet/pay/quickstart"` to `"/dotnet/universal-bridge/quickstart"`.

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

<!-- end pr-codex -->
2025-04-10 12:42:27 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
d803d37879 Version Packages (#6656)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-09 16:26:39 -07:00
Joaquim VergesandGitHub e10c1e696e [SDK] Pass through desired chain when signing in with wallet for ecosystems (#6686) 2025-04-09 16:03:43 -07:00
kumaryash90 a2305329fd Update deploy config (#6667)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces support for contracts that have no fees by adding new properties and constants to handle fee exemption logic. It modifies several components to utilize the new metadata and fee configurations, ensuring that contracts can be deployed without fees based on specific versions and chains.

### Detailed summary
- Added `contractMetadataNoFee` prop to `DeployFormForUri` and `CustomContractForm`.
- Introduced `ZERO_FEE_VERSIONS` and `ZERO_FEE_CHAINS` constants in `fee-config.ts`.
- Updated `DeployFormForPublishInfo` to fetch metadata for contracts without fees.
- Adjusted fee logic in `CustomContractForm` to use `ZERO_FEE_CHAINS`.
- Modified deployment logic to conditionally use fee-exempt metadata.

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

<!-- end pr-codex -->
2025-04-09 22:24:46 +00:00
MananTank 60235ade34 [TOOL-4028] Dashboard: Limit team name to 32 chars (#6685)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a maximum length restriction for the `teamName` input field in the `TeamGeneralSettingsPageUI` component and applies this restriction to the `TeamInfoFormUI` component as well, enhancing user experience by preventing excessively long team names.

### Detailed summary
- Added `maxLength` attribute with a value of `32` to the `teamName` input in `TeamGeneralSettingsPageUI`.
- Updated the `onChange` handler in `TeamGeneralSettingsPageUI` to set the `teamName` directly from the input value.
- Introduced a new constant `maxTeamNameLength` in `TeamInfoForm.tsx` set to `32`.
- Added `maxLength={maxTeamNameLength}` to the input field in `TeamInfoFormUI`.

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

<!-- end pr-codex -->
2025-04-09 21:19:40 +00:00
MananTank 811384b91f [TOOL-4029] Dashboard: Remove create engine page, Various UI udpates in engine instances page (#6684) 2025-04-09 20:57:36 +00:00
Jonas DanielsandGitHub 22dc4d7cd1 update dependencies (#6678) 2025-04-09 20:57:45 +02:00
gregfromstl 3074e7c67d Fix flaky ETH price test (#6682)
<!--

## 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 tests in the `cryptoToFiat` and `Status` files, improving assertions and changing transaction details to reflect new data.

### Detailed summary
- In `cryptoToFiat.test.ts`:
  - Changed assertion from checking if `data.result` is greater than 1500 to ensuring it's greater than 0.

- In `Status.test.ts`:
  - Updated `transactionHash` and related transaction details, including:
    - Modified `destinationAmount`, `destinationChainId`, and `destinationTokenAddress`.
    - Adjusted `originAmount` and `originTokenAddress`.

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

<!-- end pr-codex -->
2025-04-09 17:48:09 +00:00
iuwqyir dd99410ccd update insight playground routes (#6680)
<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the `insightBlueprints` in the `insightBlueprints.ts` file by adding new paths for token and NFT lookups and modifying an existing path name for clarity.

### Detailed summary
- Added a new path for token lookup:
  - `name`: "Token lookup",
  - `path`: "/v1/tokens/lookup"
- Added a new path for getting NFT collections:
  - `name`: "Get collection",
  - `path`: "/v1/nfts/collections/{contract_address}"
- Changed the name of an existing path from "Get NFT by token" to "Get NFT by token ID".

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

<!-- end pr-codex -->
2025-04-09 12:33:24 +00:00
MananTank b4e5dc1f3c [Dashboard] Migrate missing extension overlay from chakra to tailwind css (#6677)
<!--

## 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 related to settings by adding `overflow="hidden"` to `Card` components and refining the `SettingDetectedState` component's structure and styling for improved clarity and responsiveness.

### Detailed summary
- Added `overflow="hidden"` to `Card` components in multiple files.
- Refactored `SettingDetectedState` to use a function declaration instead of a functional component type.
- Improved the layout and styling of the loading and error states in `SettingDetectedState`.
- Replaced `SimpleGrid` with a `div` for better control over layout.
- Updated text structure and classes for better styling consistency.

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

<!-- end pr-codex -->
2025-04-08 22:00:12 +00:00
MananTank 8b4091e848 [Dashboard] Contracts sources page: migrate from chakra to tailwind+shadcn (#6676)
<!--

## 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 `SourcesPanel` and `SourcesAccordion` components by improving their structure and styling. It also introduces a new modal for contract verification and updates the UI elements for better user experience.

### Detailed summary
- Replaced `Text` and `Link` with `p` and `UnderlineLink` in `SourcesPanel`.
- Updated `SourcesAccordion` to use `SourceAccordionItem` for rendering items.
- Modified `VerifyContractModal` to a new functional component, `VerifyContractModalContent`.
- Enhanced loading and error handling in the verification process.
- Changed the layout and styling of the `ContractSourcesPage`.
- Introduced `Dialog` for contract verification instead of using a modal.
- Updated button styles and loading indicators for better UX.

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

<!-- end pr-codex -->
2025-04-08 21:34:47 +00:00
MananTank 2d15e6f509 Dashboard: Update pricing cards, style tweaks (#6675) 2025-04-08 18:41:28 +00:00
MananTank e450d320a0 [TOOL-3969] Dashboard: Update Plans, Add cancel plan, and other related UI updates (#6674)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on enhancing the billing and subscription management system by adding new team plans, updating existing components, and improving the user interface for better user experience.

### Detailed summary
- Added new plans: `accelerate` and `scale`.
- Updated `TeamPlan` type to include new plans.
- Modified `PlanInfoCard` to support new plans and improved UI.
- Refactored billing components to use new plan data.
- Enhanced `InviteSection` with new team plan options.
- Updated storybook files for new billing plans.
- Improved `CancelPlanModal` for better user feedback.
- Added helper functions for plan CTA logic.

> The following files were skipped due to too many changes: `apps/dashboard/src/components/onboarding/ApplyForOpCreditsModal.tsx`

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

<!-- end pr-codex -->
2025-04-08 17:58:37 +00:00
MananTank 838f8fa547 Dashboard: Update contracts page header: render on top of sidebar (#6666) 2025-04-07 20:21:39 +00:00
MananTank 1dae74b9d2 Dashboard: Update NFT token details page styles, reduce chakra usage (#6665)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on improving the UI components of the dashboard, enhancing the styling and structure of various elements, and adding new features like tooltips and breadcrumbs for better navigation.

### Detailed summary
- Changed `className` from `font-semibold` to `font-bold` in the `h1` element.
- Updated `NftProperty` component to use `div` instead of `Card`.
- Added `toolTip` property to `TabButtons`.
- Wrapped `Button` in `ToolTipLabel` for tooltips.
- Introduced `IPFSLinkGroup` component for displaying IPFS links.
- Enhanced `NFTName` and `NFTDescription` styling.
- Removed unnecessary imports and unused variables for cleaner code.

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

<!-- end pr-codex -->
2025-04-07 16:38:03 +00:00
MananTank 672f7d00bd Dashboard: Contract page style adjustments, fixes, reduce chakra usage (#6664)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on enhancing the UI components within the dashboard, improving layout consistency, and updating text for better clarity and user experience.

### Detailed summary
- Updated `Button` styles in `primary-dashboard-button.tsx`.
- Increased gap in layout for `ContractOverviewPage.tsx`.
- Changed headings to `h2` for better accessibility in auction and listing pages.
- Introduced `StatCard` component for consistent stats display.
- Renamed `tokenBalanceQuery` to `ownedTokenBalanceQuery` for clarity.
- Updated `SupplyCards` to use `StatCard` instead of `Stat`.
- Improved layout structure with new divs and classes for better styling.
- Added `ArrowRightIcon` to navigation links for visual cues.

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

<!-- end pr-codex -->
2025-04-07 13:19:42 +00:00
MananTank a9eda9fc8d Dashboard: Migrate contract overview page components to tailwind/shadcn (#6662)
<!--

## 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 and improving the UI components related to token and NFT details, enhancing the overall structure and user experience in the dashboard application.

### Detailed summary
- Removed unused files: `data.ts`, `types.ts`, and `TokenDetails.tsx`.
- Renamed `StatCard` to `Stat` for consistency.
- Updated UI components to improve layout and styling.
- Enhanced `TokenDetailsCard` and `NFTDetails` for better presentation.
- Added storybook entries for `PermissionsTable` and `LatestEvents`.
- Improved handling of loading states in various components.
- Replaced Chakra UI components with custom UI components for consistency.

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

<!-- end pr-codex -->
2025-04-07 09:23:59 +00:00
Manan TankandGitHub a3b4720f69 Dashboard: Remove chakra usage from contract analytics page, style tweaks (#6661) 2025-04-07 11:22:45 +02:00
Jonas DanielsandGitHub c2a5f65239 update dependencies (#6658) 2025-04-07 11:22:10 +02:00
Joaquim VergesandGitHub 7ab76ac55a [Dashboard] Add team ID to ecosystem API requests (#6659) 2025-04-06 17:04:50 -07:00
MananTank 386138e4f0 Dashboard: Team invoices page style tweaks (#6660)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on refactoring the rendering of the invoice history page and improving the `BillingHistory` component's layout and functionality, including UI updates and pagination controls.

### Detailed summary
- Replaced `Card` components with a `div` structure for the invoice history.
- Updated headings and descriptions with new styling.
- Changed badge components for invoice statuses to use variants.
- Refined pagination controls with improved styling and layout.

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

<!-- end pr-codex -->
2025-04-05 13:53:12 +00:00
Reza RahemtolaandGitHub 309d1d544b [Docs] Fix: Add missing import (#6657)
Signed-off-by: Reza Rahemtola <[email protected]>
2025-04-05 10:17:51 +02:00
kumaryash90 59be4ea2fb [SDK/Dashboard] fix: ERC1155 single phase claim conditions (#6655)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on adding single phase functionality for `erc1155` claim conditions in the `thirdweb` dashboard and related utilities, enhancing the ability to manage claim eligibility and conditions for tokens.

### Detailed summary
- Introduced `isMultiphase` and `isSinglePhase` properties in various components.
- Updated `setClaimPhasesTx` to handle single phase options.
- Modified `getClaimConditions` to support both multi-phase and single-phase claims.
- Enhanced `resetClaimEligibility` to work with single phase drops.
- Updated tests to validate new single phase functionality.

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

<!-- end pr-codex -->
2025-04-05 02:26:03 +00:00
MananTank 9c737a1905 Insight Playground: Show error when no chains selected (#6654) 2025-04-04 19:59:22 +00:00
saminacodes 646a5a3788 Ub docs 2 (#6653)
CORE-0000
---

## PR-Codex overview
This PR focuses on updating the terminology and content related to crypto transactions and user identity features in the application. It enhances documentation and descriptions to better reflect the functionality of the components.

### Detailed summary
- Updated `description` in `PayEmbed` to "Prebuilt UI to buy crypto by swapping or fiat onramps".
- Changed title in `fund-wallets` from "Fund wallets" to "Buy Crypto".
- Added new `Details` sections in `faqs` for Universal Bridge operation and KYC requirements.
- Removed user identity links in `sidebar` and reorganized sections.

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

<!-- end pr-codex -->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the terminology and descriptions related to crypto purchases within the application, particularly changing references from "Fund wallets" to "Buy Crypto." It also adds new FAQ sections and modifies existing sidebar navigation.

### Detailed summary
- Updated `description` in `page.mdx` files to reflect "Buy Crypto" instead of "Fund wallets."
- Added new FAQ entries in `faqs/page.mdx` about Universal Bridge operations and KYC requirements.
- Changed sidebar navigation to include new entries and removed outdated user identity links.

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

<!-- end pr-codex -->
2025-04-04 17:55:24 +00:00
MananTank 7597418872 [TOOL-3564] Playground: Select multiple chains in insight playground (#6652)
<!-- start pr-codex -->

## PR-Codex overview
This PR introduces a `MultiSelect` component along with a `useShowMore` hook to enhance the selection of network chains in a UI. It improves the user experience by allowing more items to be displayed as the user scrolls and provides a more flexible selection interface.

### Detailed summary
- Added `useShowMore` hook to manage displaying more items on scroll.
- Introduced `MultiNetworkSelector` for selecting multiple network chains.
- Updated `BlueprintPlayground` to utilize `MultiNetworkSelector`.
- Modified `ParameterSection` to handle array values for chain selection.
- Created `MultiSelect` component for better multi-item selection.
- Enhanced UI with search functionality and option rendering in `MultiSelect`.
- Implemented badge display for selected items in `MultiSelect`.

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

<!-- end pr-codex -->
2025-04-04 17:46:52 +00:00
MananTank 5204bd506f [TOOL-3954] Dashboard: Add tracking in contracts page (#6651) 2025-04-04 16:27:49 +00:00
MananTank 663627715a [TOOL-3876] Dashboard: Merge Framer sitemap.xml to dashboard sitemap.xml (#6648)
<!-- start pr-codex -->

## PR-Codex overview
This PR primarily introduces the `fast-xml-parser` package to the project and updates the `fetchChainsFromApi` function to fetch and parse XML data. It also modifies the sitemap generation logic to utilize the newly added functionality.

### Detailed summary
- Added `fast-xml-parser` to `package.json` and `pnpm-lock.yaml`.
- Updated `next-sitemap.config.js` to require `fast-xml-parser`.
- Introduced a new function `getFramerXML` to fetch and parse XML data.
- Modified `additionalPaths` to use data from `getFramerXML`.
- Adjusted the return structure of URLs in `additionalPaths`.

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

<!-- end pr-codex -->
2025-04-04 14:58:58 +00:00
MananTank 8f7bcaeadc Dashboard: Fix sitemap.xml and other files on public folder not loading (#6647) 2025-04-04 14:20:17 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
ad7f78ca0b Version Packages (#6644)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-04 12:18:29 +02:00
Jonas DanielsandGitHub 0967a5d53a Fix newline at end of monad-testnet.ts (#6646) 2025-04-04 12:07:42 +02:00
KrishangandGitHub d93765ac54 [SDK] Feature: Export monadTestnet from thirdweb/chains (#6645) 2025-04-04 02:11:49 -07:00
169ae22a86 Add team capabilities to service-utils (#6641)
Co-authored-by: Phillip Ho <[email protected]>
2025-04-04 12:37:23 +08:00
YashandGitHub 04d2bc72e8 Update deploy config (#6643) 2025-04-04 16:55:41 +13:00
MananTank 1a236fd103 Dashboard: Storybook cleanup (#6642)
<!--

## 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 Storybook stories across various components by standardizing the export format, updating layout parameters, and removing unnecessary imports. It aims to improve consistency and maintainability in the codebase.

### Detailed summary
- Changed `export const Desktop` to `export const Variants` in multiple stories.
- Removed `parameters: { layout: "centered" }` from many stories.
- Updated container classes for consistent styling.
- Removed `Toaster` components from stories.
- Refactored story decorators for better structure.
- Deleted unused imports like `mobileViewport` from several files.
- Consolidated `BadgeContainer` usage for better readability.

> The following files were skipped due to too many changes: `apps/dashboard/src/app/team/components/Analytics/CombinedBarChartCard.stories.tsx`, `apps/dashboard/src/app/team/[team_slug]/[project_slug]/nebula/components/analytics/nebula-analytics-ui.stories.tsx`, `apps/dashboard/src/app/nebula-app/(app)/components/ChatPageLayout.stories.tsx`, `apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/modules/components/batchMetadata.stories.tsx`, `apps/dashboard/src/components/dashboard/StepsCard.stories.tsx`, `apps/dashboard/src/@/components/blocks/SettingsCard.stories.tsx`

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

<!-- end pr-codex -->
2025-04-03 22:02:08 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
4691343c7d Version Packages (#6638)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-03 14:14:43 -07:00
Joaquim VergesandGitHub 93f225b8a3 [SDK] Fix ethers6 provider override (#6640) 2025-04-03 14:13:12 -07:00
Joaquim VergesandGitHub e9649a2b53 Update Next.js to 15.2.4 and other dependencies (#6632) 2025-04-03 12:51:19 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
4d39fb6ee3 Version Packages (#6620)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-03 12:28:27 -07:00
MananTank 1e092a7bff Dashboard: Fix timestamp ordering in various charts, migrate them to use ThirdwebBarChart (#6636)
<!-- start pr-codex -->

## PR-Codex overview
This PR primarily focuses on updating various components to improve chart functionalities and data handling in the dashboard, including the introduction of the `ThirdwebBarChart`. It also refines date handling and enhances the user interface for better data representation.

### Detailed summary
- Updated date handling in `stubbedData` from February to December.
- Introduced `BadgeContainer` for displaying time ranges in `TotalSponsoredChartCard`.
- Enhanced `searchParams` structure in the analytics page to include `from` and `to` dates.
- Added `customHeader`, `toolTipValueFormatter`, and `emptyChartState` to `ThirdwebBarChart`.
- Replaced `BarChart` with `ThirdwebBarChart` in multiple components for consistency.
- Improved data mapping and sorting in `TotalSponsoredChartCard`.
- Created new stubs for `EcosystemWalletUsersChartCard` to simulate user data over various days.
- Refactored empty state components to improve user guidance when no data is available.

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

<!-- end pr-codex -->
2025-04-03 18:36:21 +00:00
MananTank 80ee3d6670 [TOOL-3950] Dashboard: link to /explore page for contracts card on chain page (#6637)
<!--

## 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 a link in the `products.ts` file for the dashboard component, changing the URL for a specific product.

### Detailed summary
- Updated the `link` property for the product from `https://thirdweb.com/contracts` to `https://thirdweb.com/explore`.

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

<!-- end pr-codex -->
2025-04-03 17:05:00 +00:00
MananTank a7fd4e51a3 [TOOL-3946] Dashboard: Fix in-app wallet users chart timestamp incorrect ordering (#6635) 2025-04-03 13:09:56 +00:00
MananTank 61583cf6f3 Dashboard: Fix project settings page max-width (#6634)
<!--

## 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 layout of the `settings` page in the project dashboard. It enhances the styling and structure of the header and the content area.

### Detailed summary
- Replaced the existing `<h1>` element with a new structure wrapped in a `<div>` with a class of `border-b py-10`.
- Changed the font size of the `<h1>` from `text-3xl` to `text-2xl` for better responsiveness.
- Added a container with a maximum width of `7xl` around the header.
- Adjusted the padding of the content area to `pt-6 pb-20`.

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

<!-- end pr-codex -->
2025-04-03 12:58:26 +00:00
iuwqyir cf02af06a4 fix insight blueprints navigation (#6633)
## [Dashboard] Feature: Update API path format and add token lookup endpoint

## Notes for the reviewer

This PR updates the API path format in `insightBlueprints.ts` to use curly braces for path parameters (e.g., changing `/v1/transactions/:contractAddress` to `/v1/transactions/{contractAddress}`). This standardizes our path format to match OpenAPI specifications.

## How to test

The changes can be verified by checking the updated paths in the playground interface to ensure they display correctly with the new format.

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the URL path parameters in the `insightBlueprints` to use curly braces `{}` instead of colons `:` for better clarity and consistency in API endpoints.

### Detailed summary
- Changed path parameters from `:paramName` to `{paramName}` for various endpoints:
  - `/v1/transactions/:contractAddress` to `/v1/transactions/{contractAddress}`
  - `/v1/wallets/:wallet_address/transactions` to `/v1/wallets/{wallet_address}/transactions`
  - Updated multiple other endpoints similarly, including:
    - `/v1/events/:contractAddress` to `/v1/events/{contractAddress}`
    - `/v1/tokens/transfers/:contract_address` to `/v1/tokens/transfers/{contract_address}`
    - `/v1/nfts/:contract_address` to `/v1/nfts/{contract_address}`
    - And many more throughout the file.

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

<!-- end pr-codex -->
2025-04-03 12:15:39 +00:00
MananTank 6001f2487e [TOOL-3909] Dashboard: Add footer links to project pages & header style adjustments (#6627)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on enhancing the layout and structure of various components in the application, introducing new footer sections, and improving the user interface for better organization and navigation.

### Detailed summary
- Removed the `SupportedPlatformLink.tsx` file.
- Updated `href` in `react-native/v5/page.mdx` from `/installation` to `/getting-started`.
- Enhanced layout in multiple components with added `container` classes for better styling.
- Introduced new `FooterCard` component in various pages for documentation and tutorial links.
- Replaced `AAFooterSection` with `AAFooter` in `AccountAbstractionPage.tsx`.
- Updated `InAppWalletFooterSection` to `InAppWalletsFooter` with a new structure.
- Modified headers and added spacing in several components for improved visual hierarchy.
- Introduced new `InsightFooter` component with relevant links.
- Adjusted various components to use consistent `container` class for layout consistency.

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

<!-- end pr-codex -->
2025-04-03 17:23:24 +05:30
Joaquim VergesandGitHub 3f21445369 [SDK] Emit accountChanged event when smart wallets switch chains (#6626) 2025-04-02 18:19:14 -07:00
MananTank 9e8fd89ec0 [TOOL-3942] Playground: Fix slow dev server - generate insight sidebar links with script (#6631)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new script for managing insight blueprints, updates the `getSidebarLinks` function to use pre-fetched blueprints, and modifies the `Page` component to display these blueprints. It also refines the utility functions for fetching blueprint data.

### Detailed summary
- Added `update-insight-blueprints` script in `package.json`.
- Created `insightBlueprints.ts` to store auto-generated blueprint data.
- Updated `getSidebarLinks` to use `insightBlueprints` instead of fetching blueprints dynamically.
- Modified `Page` component to render blueprints from `insightBlueprints`.
- Enhanced utility functions in `utils.ts` for fetching blueprint data.

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

<!-- end pr-codex -->
2025-04-02 23:28:15 +00:00
Joaquim VergesandGitHub 95a87530e3 [SDK] Fix updateListing date handling in marketplace direct listings (#6628) 2025-04-02 16:27:41 -07:00
0xFirekeeper 5faaf1be48 [Portal] Universal Bridge .NET (#6625)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on the removal of several payment-related pages in the `.NET SDK` documentation and the addition of a new section for the `Universal Bridge`, including examples and detailed API usage.

### Detailed summary
- Deleted pages:
  - `quickstart/page.mdx`
  - `buywithfiat/page.mdx`
  - `buywithcrypto/page.mdx`
  - `getbuyhistory/page.mdx`
  - `getbuywithfiatquote/page.mdx`
  - `getbuywithfiatstatus/page.mdx`
  - `getbuywithcryptoquote/page.mdx`
  - `getbuywithcryptostatus/page.mdx`
  - `getbuywithfiatcurrencies/page.mdx`
- Updated `sidebar.tsx` to remove payment links and add a new `Bridge` section.
- Introduced `Universal Bridge` documentation with:
  - Metadata creation
  - Core API examples for buying, selling, and transferring tokens.
  - Code snippets for `C#` usage of the `ThirdwebBridge`.

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

<!-- end pr-codex -->
2025-04-02 18:26:19 +00:00
jnsdls 4847ada819 Add /brand-kit to framer rewrites (#6624)
Added a rewrite rule for the `/brand-kit` path in the Framer rewrites configuration.
2025-04-02 17:53:14 +00:00
MananTank 08eea95f9b [TOOL-3764] Dashboard: Add Nebula FTUX (#6616)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new `ClientIDSection` component to streamline the display of the Client ID across various components. It also modifies the `WaitingForIntegrationCard` to utilize this new component, improving code organization and reducing redundancy.

### Detailed summary
- Added `ClientIDSection` component to display the Client ID.
- Updated `InsightFTUX` to include `ClientIDSection` instead of inline code.
- Modified `WaitingForIntegrationCard` to accept children for rendering.
- Created `NebulaFTUX` component to integrate Nebula features and display Client ID.
- Adjusted styles in various components for consistency.

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

<!-- end pr-codex -->
2025-04-02 09:12:32 +00:00
MananTank 737155ba21 [TOOL-3765] Dashboard: Add Insight FTUX (#6614)
<!--

## 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 `Insight` page by introducing new components for better integration and user experience, specifically around project insights and blueprints. It refactors existing code and adds new features for displaying API usage examples.

### Detailed summary
- Updated `isProjectActive` function to accept a structured parameter.
- Added `InsightFTUX` component for displaying integration instructions.
- Introduced `BlueprintCard` component with feature descriptions.
- Modified `WaitingForIntegrationCard` to display the client ID without a period.
- Enhanced `page.tsx` to conditionally render `InsightFTUX` or `BlueprintCard` based on project status.

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

<!-- end pr-codex -->
2025-04-02 09:12:31 +00:00
MananTank 73ae5817f2 [TOOL-3763] Dashboard: Update Contracts page (#6611)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the user interface and functionality of the contracts management section. It introduces new components and modifies existing ones to improve user experience, particularly around contract deployment and management.

### Detailed summary
- Removed the `GetStartedWithContractsDeploy` component.
- Added `DeployViaCLIOrImportCard` component for contract deployment options.
- Updated the `DeployedContractsPage` to include the new `DeployViaCLIOrImportCard`.
- Modified the `ContractTableUI` to display a message and button when no contracts are present.
- Increased the height of the no contracts message section.
- Added a link to discover contracts with an external icon.

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

<!-- end pr-codex -->
2025-04-02 09:12:31 +00:00
Joaquim VergesandGitHub 6a0704a19b Connect to current wallet chain when connecting other wallets in PayEmbed (#6621) 2025-04-02 02:09:39 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2ef8f2a371 chore(deps): bump biomejs/setup-biome from 2.3.0 to 2.5.0 (#6598)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-02 01:50:04 -07:00
d6373b2c67 [SDK] Feature: added ru_RU locale to thirdweb package (#6520)
Co-authored-by: Denis Vasilev <[email protected]>
Co-authored-by: Joaquim Verges <[email protected]>
2025-04-02 01:46:15 -07:00
MananTank 7eaf86a607 [TOOL-3762] Dashboard: Update In-app wallet analytics page (#6610)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on refactoring components related to in-app wallets and account abstraction analytics. It introduces new loading states, replaces certain components with `StatCard`, and enhances the handling of asynchronous data fetching.

### Detailed summary
- Removed `loading.tsx` files for in-app wallets and account abstraction.
- Updated `InAppWalletsHeader` to no longer accept props.
- Replaced `Stat` with `StatCard` in `EcosystemWalletsSummary` and `AccountAbstractionSummary`.
- Added `isPending` state to `StatCard` for loading indication.
- Refactored `InAppWalletsSummary` and `AccountAbstractionSummary` to utilize async data fetching with `Suspense`.
- Enhanced `InAppWalletAnalytics` to manage loading states and data fetching more effectively.

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

<!-- end pr-codex -->
2025-04-02 07:10:08 +00:00
gregGitHubgraphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
5788cbdbad [SDK] Feature: Adds action field to Bridge transactions (#6613)
Signed-off-by: greg <[email protected]>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2025-04-02 00:08:03 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
d9a39cde33 Version Packages (#6619)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-01 19:43:56 -07:00
Joaquim VergesandGitHub 62c8e7c0c4 [SDK] show transfer amounts with fees (#6618) 2025-04-01 19:20:15 -07:00
Joaquim VergesandGitHub f618c5308c [SDK] Handle entrypoint resolution inside createAndSignUserOp (#6615) 2025-04-01 18:17:17 -07:00
gregandGitHub 04943d730b [Dashboard] Feature: Token symbol search (#6617) 2025-04-01 15:06:52 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2606da6d87 Version Packages (#6606)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-01 13:02:59 -07:00
kumaryash90 9cf137660a Remove custom config for Ronin (#6612)
Create2 deployer restrictions are lifted by the chain. Enable deployments through default create2 factory.

<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on removing the custom configuration for the `Ronin` blockchain within the `thirdweb` package.

### Detailed summary
- Removed the `Ronin` configuration from the gas price settings in the `create-2-factory.ts` file.
- Updated the gas price for `Ronin` from `110n * 10n ** 9n` to a static value of `110000n`.

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

<!-- end pr-codex -->
2025-04-01 19:06:06 +00:00
MananTank 58ab8c2ee7 [TOOL-3766] Dashboard: Account Abstraction page updates (#6609)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on improving the UI and functionality of various components in the dashboard by modifying styles, enhancing layout elements, and integrating new features related to account abstraction and user operation statistics.

### Detailed summary
- Removed `lg:gap-6` from grid classes in multiple components.
- Increased header sizes from `text-lg` to `text-xl` in `SponsoredTransactionsTableUI.tsx`.
- Adjusted margins and padding for improved spacing in various components.
- Added new `GasCreditAlert` component for displaying gas credit information.
- Integrated `AccountAbstractionSummary` into `AccountAbstractionAnalytics`.
- Updated `userOpStats` handling in `Page` component to use promises for better performance.
- Replaced certain text with more concise phrasing in descriptions.
- Removed unused imports and optimized component structure for clarity.

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

<!-- end pr-codex -->
2025-04-01 18:12:49 +00:00
MananTank 447a519ddc [TOOL-3768] Dashboard: Move Nebula from team to project layout (#6608)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on enhancing the `Nebula` components in the dashboard application by improving layout styles, adding project ID handling in analytics, and adjusting waitlist checks.

### Detailed summary
- Updated class names for better styling in `nebula-analytics-filter.tsx` and `nebula-waitlist-page.tsx`.
- Added `projectId` parameter to `fetchNebulaAnalytics`.
- Modified `NebulaWaitListPageUI` layout.
- Integrated `showNebula` prop in `ProjectSidebarLayout`.
- Adjusted project layout and waitlist checks in `layout.tsx`.
- Enhanced `NebulaAnalyticsPage` to pass `projectId`.
- Updated page handling in `page.tsx` to include project fetching and redirects.

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

<!-- end pr-codex -->
2025-04-01 11:56:32 +00:00
Joaquim VergesandGitHub a9b7633d05 [SDK] Show past transactions in transactions modal screen (#6607) 2025-04-01 03:14:28 -07:00
Joaquim VergesandGitHub 38e089a5f3 [SDK] Only onramp for required transaction amount (#6605) 2025-04-01 02:01:11 -07:00
Joaquim VergesandGitHub ef2f86cff0 Version Packages (#6589) 2025-03-31 23:05:33 -07:00
gregfromstl 8fdef82147 [Playground] Fix: Filter UB Backend API for only GETs (#6604)
<!--

## 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 layout and functionality of the `Page` component in `apps/playground-web/src/app/connect/pay/backend/page.tsx`. It improves the presentation of the Universal Bridge REST API information and adds a link to view all endpoints.

### Detailed summary
- Wrapped the header and description in a `div` for better layout.
- Added a `Link` component with a button to "View all endpoints".
- Changed the title of the `BlueprintSection` from "Available endpoints" to "Available GET endpoints".
- Updated the filtering of `blueprints` to only include those with defined `get` properties.

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

<!-- end pr-codex -->
2025-04-01 01:02:26 +00:00
MananTank cbb15692a7 [TOOL-3880] Playground: Add utm_source on links (#6597)
<!-- start pr-codex -->

## PR-Codex overview
This PR primarily updates various `docsLink` URLs across multiple components in the `playground-web` application to include a query parameter (`?utm_source=playground`). This change likely aims to track traffic sources more effectively.

### Detailed summary
- Updated `docsLink` in `apps/playground-web/src/app/connect/auth/page.tsx`
- Updated `docsLink` in `apps/playground-web/src/app/connect/pay/page.tsx`
- Updated `docsLink` in `apps/playground-web/src/app/connect/blockchain-api/page.tsx`
- Updated `docsLink` in `apps/playground-web/src/app/connect/pay/commerce/page.tsx`
- Updated `docsLink` in `apps/playground-web/src/app/connect/pay/fund-wallet/page.tsx`
- Updated `docsLink` in `apps/playground-web/src/app/connect/pay/transactions/page.tsx`
- Updated `docsLink` in `apps/playground-web/src/app/connect/sign-in/embed/page.tsx`
- Updated `docsLink` in `apps/playground-web/src/app/connect/sign-in/button/page.tsx`
- Updated `docsLink` in `apps/playground-web/src/app/connect/ui/page.tsx`
- Updated `docsLink` in `apps/playground-web/src/app/connect/sign-in/headless/page.tsx`
- Updated `docsLink` in `apps/playground-web/src/app/connect/in-app-wallet/layout.tsx`
- Updated `docsLink` in `apps/playground-web/src/app/insight/layout.tsx`
- Updated `docsLink` in `apps/playground-web/src/components/blocks/APIHeader.tsx`
- Updated `docsLink` in `apps/playground-web/src/app/connect/pay/backend/layout.tsx`
- Updated `docsLink` in `apps/playground-web/src/app/connect/account-abstraction/connect/page.tsx`
- Updated `docsLink` in `apps/playground-web/src/app/connect/account-abstraction/sponsor/page.tsx`
- Updated `docsLink` in `apps/playground-web/src/app/connect/ui/nft/page.tsx`
- Updated `docsLink` in `apps/playground-web/src/app/otherLinks.ts`
- Updated `docsLink` in `apps/playground-web/src/app/connect/account-abstraction/native-aa/page.tsx`
- Updated `docsLink` in `apps/playground-web/src/app/connect/ui/chain/page.tsx`
- Updated `docsLink` in `apps/playground-web/src/app/connect/ui/token/page.tsx`
- Updated `docsLink` in `apps/playground-web/src/app/connect/social/page.tsx`
- Updated `docsLink` in `apps/playground-web/src/app/connect/ui/wallet/page.tsx`
- Updated `href` in `apps/playground-web/src/app/connect/pay/embed/LeftSection.tsx`
- Updated `href` in `apps/playground-web/src/app/insight/page.tsx`
- Updated `href` in `apps/playground-web/src/components/auth/gated-content.tsx`
- Updated `href` in `apps/playground-web/src/components/engine/minting/erc1155-mint-to.tsx`
- Updated `href` in `apps/playground-web/src/app/connect/sign-in/components/InAppWalletFormGroup.tsx`
- Updated `href` in `apps/playground-web/src/app/connect/sign-in/button/LeftSection.tsx`

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

<!-- end pr-codex -->
2025-03-31 19:37:09 +00:00
MananTank 1d23e5cd9c Dashboard: Fix AA page alert styles (#6596)
<!--

## 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 `SmartWalletsBillingAlert` component in the `Alerts.tsx` file. It updates the alert's UI and removes the dismissible functionality, while also changing the import paths for components used in the alert.

### Detailed summary
- Removed unused imports related to `Alert`, `AlertDescription`, `AlertIcon`, `AlertTitle`, `Flex`, and `IconButton`.
- Updated imports for `Alert`, `AlertDescription`, `AlertTitle`, `TrackedUnderlineLink`, and `CircleAlertIcon`.
- Removed the `dismissible` prop and related logic.
- Simplified the alert structure with a new layout.
- Changed the alert icon and styling.

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

<!-- end pr-codex -->
2025-03-31 19:07:57 +00:00
MananTank a17d893811 [TOOL-3873] Fix pageview tracking in dashboard, Add posthog to playground (#6595) 2025-03-31 18:38:58 +00:00
kumaryash90 0dcde077f5 [SDK] Fix bytecode caching (#6593)
<!--

## 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 bytecode caching mechanism in the `thirdweb` package by including additional metadata in the cache key, which improves cache differentiation and management.

### Detailed summary
- Updated the `cacheKey` in `deploy-metadata.ts` to include `compilerMetadata.publisher` and `compilerMetadata.version` along with `compilerMetadata.name` and `chain.id`.
- Removed the `version` and `publisher` fields from the deployment test case in `process-ref-deployments.test.ts`.

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

<!-- end pr-codex -->
2025-03-31 18:30:28 +00:00
jnsdls 05a041fac3 Add glossary slug route to framer rewrites (#6594)
### TL;DR

Added a rewrite rule for glossary detail pages in the dashboard app.

### What changed?

Added a new path `/learn/glossary/:glossary_slug` to the framer-rewrites.js file to support individual glossary term pages.

### How to test?

1. Navigate to a glossary term detail page (e.g., `/learn/glossary/some-term`)
2. Verify that the page loads correctly and the routing works as expected

### Why make this change?

To enable proper routing for individual glossary term pages, allowing users to access specific glossary entries directly through their unique URLs. This complements the existing glossary index page that was already configured.
2025-03-31 18:00:30 +00:00
MananTank 3d140eed97 [TOOL-3874] Portal: Fix pl.js script CSP error (#6591)
<!--

## 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 Content Security Policy in the `next.config.mjs` file to enhance security by modifying the `script-src` directive.

### Detailed summary
- Updated the `script-src` directive to include `thirdweb.com` and `thirdweb-dev.com` without a wildcard prefix.
- Retained existing sources for `self`, `unsafe-eval`, `unsafe-inline`, `wasm-unsafe-eval`, `inline-speculation-rules`, `vercel.live`, and `js.stripe.com`.

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

<!-- end pr-codex -->
2025-03-31 17:43:57 +00:00
jnsdls a5f005fff0 Remove tutorial routes from framer rewrites (#6592)
closes: CORE-802

Redirect `/learn/tutorials` to `/learn/guides`

This PR removes `/learn/tutorials` and `/learn/tutorials/:tutorial_slug` from the framer-rewrites list and adds redirects from these paths to their corresponding `/learn/guides` equivalents.
2025-03-31 17:14:04 +00:00
saminacodes fb70cab6d4 [portal] added module warning (#6590)
CORE-0000

<!-- start pr-codex -->

---

## PR-Codex overview
This PR adds a warning callout to the `page.mdx` file in the `cross-chain/agglayer` directory, informing users that the Agglayer module is in beta and has deployment limitations.

### Detailed summary
- Added a warning callout indicating that the Agglayer module is in beta.
- Specified that deployments are limited to `Sepolia` and `Cardona` due to requirements for Ethereum as L1 and zkEVM as L2.

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

<!-- end pr-codex -->
2025-03-31 14:53:39 +00:00
Joaquim VergesandGitHub cd9ea69c13 [SDK] Add erc20Value for makeOffer extension function (#6588) 2025-03-31 00:24:42 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
dc892bd136 Version Packages (#6585)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-30 16:14:40 -07:00
Joaquim VergesandGitHub 3d7c009d72 [CLI] Fix secret key handling (#6584) 2025-03-30 15:49:45 -07:00
YashandGitHub 6dbed0d4fc [SDK] fix: Use default contract params with optional overrides (#6581) 2025-03-30 15:45:13 -07:00
MananTank adee1903d5 [TOOL-3814] Dashboard: Show all active coupons in team billing page (#6568)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on refactoring the coupon management system within the billing section of the dashboard. It introduces a new structure for handling coupons, including a client component for API interactions and UI components for displaying and applying coupons.

### Detailed summary
- Deleted obsolete files: `CouponCard.tsx`, `CouponDetails.stories.tsx`, `ApplyCouponCard.stories.tsx`.
- Updated global styles import in `preview.tsx`.
- Replaced `CouponSection` with `Coupons` in `index.tsx`.
- Introduced `CouponsClient` for managing coupon API interactions.
- Created `AsyncCoupons` to fetch coupon data asynchronously.
- Developed `Coupons` component to handle loading states and display.
- Added `CouponsUI` for rendering coupon information and actions.
- Implemented coupon application and deletion logic with error handling.
- Created a story for `CouponsUI` to demonstrate different states and interactions.

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

<!-- end pr-codex -->
2025-03-30 22:18:56 +00:00
gregandGitHub a1d785b8ed Fix: Increase commerce price to avoid minimum purchase errors (#6582) 2025-03-29 20:50:25 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
ba92cd2cbe Version Packages (#6575)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-29 03:41:26 -07:00
Joaquim VergesandGitHub c3d0ea0059 [PayEmbed] Respect supportedTokens prop (#6580) 2025-03-29 03:35:22 -07:00
MananTank 35c5f57179 [TOOL-3839] Fix double scrollbars in project settings page (#6579) 2025-03-29 07:51:23 +00:00
gregandGitHub 2a1f4be48b Fix: Not Found Status should show Pending (#6578) 2025-03-28 20:11:43 -07:00
gregandGitHub 0121ccd702 fix: reduce cost of playground commerce example (#6577) 2025-03-28 19:19:03 -07:00
Toomas OosaluandGitHub 12bcfc596a insight webhooks docs (#6574) 2025-03-29 11:11:25 +13:00
MananTank a4ff738268 [TOOL-3833] Dashboard: Fix overflow scroll created by sr-only element (#6576) 2025-03-28 19:17:40 +00:00
YashandGitHub b49c191355 Support stylus contracts publish and deploy (#6495) 2025-03-29 07:34:37 +13:00
saminaandGitHub 06f0765665 Update page.mdx (#6572)
Signed-off-by: samina <[email protected]>
2025-03-27 19:54:50 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
12adbd8314 Version Packages (#6570)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-27 17:25:33 -07:00
saminacodes 48e014d426 added FAQ (#6567)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces new details to the `page.mdx` file, providing guidance on how to enhance the performance of the `Nebula` character and improve its responses.

### Detailed summary
- Added a section on how to make `Nebula` play a certain character better, including:
  - Defining the role clearly.
  - Avoiding personality traits in responses.
  - Setting output expectations.
  - Preloading context for specific domains.
  - Utilizing session handling features.

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

<!-- end pr-codex -->
2025-03-27 23:41:37 +00:00
saminacodes 826748bb5d [Portal] fee-changes (#6573)
CORE-0000

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the fee structure and related information for the `Buy With Crypto` and swap transactions in the `fee-sharing` documentation.

### Detailed summary
- Removed references to a 1% fee and 70% sharing for `Buy With Crypto`.
- Updated the protocol fee to 0.3% for swap transactions.
- Allowed developers to set their own additional fees.
- Clarified fee structure for buying with crypto and swap transactions.

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

<!-- end pr-codex -->
2025-03-27 22:54:25 +00:00
gregfromstl d4f5a9db12 [Portal] Add cross-chain swapping guide and add bridge submodules (#6571)
<!--

## 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 documentation and functionality surrounding the `thirdweb` Universal Bridge, particularly for cross-chain swapping and related features. It updates guides, introduces new functionalities, and improves the clarity of the code.

### Detailed summary
- Updated `yarn` command in `page.mdx` for installing `thirdweb`.
- Renamed the guide from "Build a Custom Experience" to "Build a Custom Onramp Experience" in `sidebar.tsx`.
- Added a new guide for "Cross-Chain Swapping" in `page.mdx`.
- Enhanced tag filtering in `slugs.ts` to include `@bridge`.
- Updated comments in `Buy.ts` and `Sell.ts` to specify `@bridge Buy` and `@bridge Sell`.
- Improved sidebar link generation in `getSidebarLinkGroups.ts` to include `@bridge` links.
- Added comprehensive steps for cross-chain swapping in the new guide, including installation, obtaining quotes, preparing transactions, and checking swap status.

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

<!-- end pr-codex -->
2025-03-27 19:07:18 +00:00
MananTank 3b827ea32c [NEB-148] Do not add active chain to nebula context by default (#6564) 2025-03-27 18:58:20 +00:00
MananTank 2231db7980 [NEB-125] Nebula: Show popular chains at the top of the list (#6565) 2025-03-27 18:49:02 +00:00
MananTank f9872f442c [TOOL-3761] Dashboard: Use new sidebar layout in project pages (#6551)
<!--

## 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 layout and styling of the application, particularly around sidebar and project-related components. It introduces new sidebar features, refines the UI, and improves mobile responsiveness.

### Detailed summary
- Removed unused files: `tabs.tsx`, `layout.tsx` in `connect`, and `settings/layout.tsx`.
- Simplified layout in `DeployedContractsPage.tsx` by removing unnecessary divs.
- Added `AnnouncementBanner` to multiple layouts for notifications.
- Introduced `useActiveSidebarLink` for better sidebar link management.
- Enhanced `Sidebar` components with new features and improved styling.
- Added `Header` component in `ProjectOverviewPage` for better title display.
- Updated Tailwind CSS configuration to include sidebar styles.
- Improved mobile responsiveness with `useIsMobile` hook.
- Cleaned up `AnnouncementBanner` logic and structure.

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

<!-- end pr-codex -->
2025-03-27 18:38:45 +00:00
Joe-Thirdweb 473a25237e [Dashboard] reactive(1597) chainlist page update (#6566)
Added custom chain page info for Reactive Network

* Banner image
* CTA image
* About section

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces new assets and functionality for a "Reactive Mainnet" section in the dashboard. It adds images and a call-to-action that promotes Web3 automation features.

### Detailed summary
- Added `reactiveBanner.jpg` and `reactiveCTA.jpg` assets.
- Introduced a new section for "Reactive Mainnet" in `chainMetaRecord`:
  - Set `headerImgUrl` to `reactiveBanner.src`.
  - Added an `about` description for Web3 automation.
  - Configured `cta` with `backgroundImageUrl`, `title`, `buttonLink`, and `buttonText`.

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

<!-- end pr-codex -->
2025-03-27 18:23:19 +00:00
Manan TankandGitHub 2d4c5ff07c Fix "Cannot read properties of undefined (reading 'forwardRef')" error on certain bundler setups (#6569) 2025-03-27 10:41:53 -07:00
saminacodes ab738eb7ba new narrative (#6563)
<!--

## 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 `AnnouncementBanner` and `Banner` components to promote the new `Universal Bridge` feature, enhancing the messaging around payment integration, and refining the content in the documentation to reflect these changes.

### Detailed summary
- Updated `AnnouncementBanner` with new `href`, `label`, and `trackingLabel`.
- Changed `Banner` ID, `text`, and `href` to reflect the `Universal Bridge` launch.
- Revised documentation for webhook events to include `Universal Bridge`.
- Enhanced content in the overview page for `Universal Bridge`.
- Updated descriptions and titles for multiple `FeatureCard` components.
- Removed fee-sharing section and added new integration options for `Universal Bridge`.

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

<!-- end pr-codex -->
2025-03-27 14:55:00 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
8e6651e591 Version Packages (#6546)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-26 11:37:15 -07:00
gregandGitHub e5026a10ab [SDK] Fix: Account for bigint values in engine typed data (#6562) 2025-03-26 11:10:51 -07:00
MananTank 9c1c5f2fc8 [TOOL-3789] Dashboard: Replace simplehash with insight (#6554) 2025-03-26 14:49:27 +00:00
Phillip HoandGitHub 45cdb86911 chore: hide Engine projects from list (#6560) 2025-03-26 22:05:00 +08:00
Joaquim VergesandGitHub fe3351ce79 [TypeDoc] Remove Gist update from workflow (#6559) 2025-03-26 02:45:20 -07:00
Joaquim VergesandGitHub fc657e0b89 [Portal] Redesign documentation homepage with SDK cards and product icons (#6558) 2025-03-26 02:37:35 -07:00
Phillip HoandGitHub cff2481c06 chore: changset for make req optional when calling usage from cloudflare (#6557) 2025-03-26 12:14:05 +08:00
Phillip HoandGitHub e0e31826a8 chore: make req optional when calling usage from cloudflare (#6556) 2025-03-26 11:57:03 +08:00
Joaquim VergesandGitHub fe8b3ce5a5 [Playground] Rename "Fund Wallet" to "Buy Crypto" across pay components (#6555) 2025-03-25 17:48:21 -07:00
kumaryash90 3c120a7584 [SDK] Functions to manage extensions on a dynamic contract (#6550)
<!--

## 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 `thirdweb` library by adding functionality to manage extensions on dynamic contracts, including the ability to install and uninstall extensions.

### Detailed summary
- Introduced `uninstallExtension` function to remove extensions from dynamic contracts.
- Added `installPublishedExtension` function to install extensions with various options.
- Updated `generateExtensionFunctionsFromAbi` to use a specific string format for `functionSelector`.
- Enhanced tests for `uninstallExtension` and `installPublishedExtension` to ensure proper functionality.
- Updated generated files for `addExtension` and `removeExtension` methods with new parameter types and encoding functions.

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

<!-- end pr-codex -->
2025-03-26 00:24:19 +00:00
MananTank b51157c0ff [TOOL-3735] Fix minor typos (#6531)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on fixing typos and improving text clarity in various components of the application.

### Detailed summary
- In `PayConfig.tsx`, corrected "will are" to "are" in the `bottomText` prop.
- In `page.tsx` (commerce), corrected "paymets" to "payments".
- In `page.tsx` (headless), corrected "Seemlessly" to "Seamlessly" in the `description`.
- In `page.tsx` (embed), corrected "Seemlessly" to "Seamlessly" in the `description`.

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

<!-- end pr-codex -->
2025-03-25 22:48:05 +00:00
MananTank f1df19e612 [TOOL-3689] Dashboard: Keep switch in disabled state if field value is empty in Sponsorship rules form (#6535)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on improving the `transformedQueryData` computation by filtering out empty values from the `policy` properties. It enhances data handling for `allowedContractAddresses`, `allowedWallets`, `blockedWallets`, and `allowedChainIds`, ensuring cleaner and more accurate data is returned.

### Detailed summary
- Refactored `transformedQueryData` to filter out empty values from `allowedContractAddresses`, `allowedWallets`, `blockedWallets`, and `allowedChainIds`.
- Simplified return conditions for these properties.
- Changed handling of `allowedChainIds` to return an empty array instead of null to prevent API issues.

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

<!-- end pr-codex -->
2025-03-25 22:39:38 +00:00
MananTank 98781531bb [TOOL-3786] Portal: Improve llms.txt, llms-full.txt (#6532) 2025-03-26 03:59:07 +05:30
saminacodes d3a5ea7833 fixed broken links (#6553)
<!--

## 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 modifying the sidebar structure in the `Nebula` application, including changes to links and adding a new FAQ section.

### Detailed summary
- Added a new FAQ section in `faqs/page.mdx` about using Nebula in a stateless manner.
- Changed the sidebar entry from `MCP Server` to `MCP Clients` in `sidebar.tsx`.
- Updated the link for `Examples` in `sidebar.tsx` to point to a GitHub repository.

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

<!-- end pr-codex -->
2025-03-25 20:43:24 +00:00
MananTank 1a9448e152 Portal: Fix lint error (#6552)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on restructuring the `sidebar` object in the `apps/portal/src/app/nebula/sidebar.tsx` file, ensuring proper formatting of the sidebar items and improving the overall readability of the code.

### Detailed summary
- Adjusted the formatting of the `sidebar` entries for `MCP Server` and `Tools`.
- Converted closing brackets from separate lines to inline, enhancing code clarity.
- Ensured consistent object structure for items in the `sidebar`.

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

<!-- end pr-codex -->
2025-03-25 19:36:16 +00:00
saminaandGitHub 2fe16119f5 [Portal] AI updated docs (#6536) 2025-03-25 02:33:38 -07:00
Joaquim VergesandGitHub c549d5934e [Bridge] Skip tests without TW_SECRET_KEY environment variable (#6545) 2025-03-25 02:32:53 -07:00
Joaquim VergesandGitHub dd65506dfb [Playground] Refactor Universal Bridge sidebar links and update order (#6543) 2025-03-24 19:30:22 -07:00
MananTank 26cfd1b30e [TOOL-3682] Dashboard: Change deploy contract status for in-app wallet (#6533) 2025-03-25 01:57:05 +00:00
MananTank 495d8edfa2 [TOOL-3757] Dashboard: Prefill last used project in contract deployment page (#6534) 2025-03-25 01:49:04 +00:00
kumaryash90 88c213b6d5 Handle default extensions (#6497)
<!--

## 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 deployment and transaction management of dynamic contracts within the `thirdweb` framework, adding support for extensions and improving the handling of contract metadata.

### Detailed summary
- Improved return formatting in `bootstrap.ts`.
- Updated `platformFeeBps` handling in `custom-contract.tsx`.
- Added tests for dynamic contract transactions in `get-required-transactions.test.ts`.
- Enhanced logic for deploying extensions in `deploy-published.ts`.
- Introduced new functions for managing dynamic contract extensions in `get-required-transactions.ts`.
- Updated `deploy-marketplace.ts` to include dynamic extensions in marketplace contracts.

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

<!-- end pr-codex -->
2025-03-24 17:21:13 +00:00
Joaquim VergesandGitHub d9091bd2e7 [Dashboard] Disable engineCloud service by default (#6530) 2025-03-24 02:31:52 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
ab7bf1ad1d Version Packages (#6528)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-24 00:28:06 -07:00
Joaquim VergesandGitHub 21389bbef6 [Service-Utils] feat: Add engine-cloud service definition (#6526) 2025-03-24 00:16:18 -07:00
Joaquim VergesandGitHub 4f51201249 [SDK] Update @mobile-wallet-protocol/client to 1.0.0 (#6527) 2025-03-24 00:15:56 -07:00
pink hoodie.andGitHub a0cee488e9 quick get started docs fixes per Jem's revisions (#6525) 2025-03-23 14:46:23 -07:00
Phillip HoandGitHub 0b8907cb7c chore: Add jittered retry on auth call (#6524) 2025-03-23 22:51:59 +08:00
Reza RahemtolaandGitHub 85a78d4460 [Docs] Fix: typo (#6517)
Signed-off-by: Reza Rahemtola <[email protected]>
2025-03-23 17:42:29 +13:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Jake Loo
2a6778eb42 chore(deps): bump the npm_and_yarn group across 5 directories with 1 update (#6522)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jake Loo <[email protected]>
2025-03-23 15:59:58 +13:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
882fd3dcbd Version Packages (#6519)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-22 04:59:50 -07:00
Joaquim VergesandGitHub 744491f853 [SDK] fix: Handle wallet chain mismatch on post onramp flow (#6518) 2025-03-22 04:52:59 -07:00
d3f788471b [Portal] Neb Changes (#6451)
Signed-off-by: samina <[email protected]>
Co-authored-by: Manan Tank <[email protected]>
2025-03-22 00:48:04 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
6db81ef68d Version Packages (#6516)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-22 00:39:59 -07:00
Alberto GarcíaandGitHub f8b2bdc64b [Dashboard] Fix: wrong URL (#6510) 2025-03-22 00:18:06 -07:00
Joaquim VergesandGitHub c407bd10c7 [SDK] fix: Fetch fresh quotes before post onramp step in PayEmbed (#6515) 2025-03-22 00:14:45 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
e540dc11f5 Version Packages (#6508)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-22 15:36:55 +13:00
Joaquim VergesandGitHub 7476b7927d [SDK] fix: Improve swap transaction details UI (#6509) 2025-03-21 02:45:34 -07:00
Joaquim VergesandGitHub ccb641698e [SDK] fix: Force redirect to phantom app on mobile (#6507) 2025-03-20 18:22:05 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
aa9d858f1c Version Packages (#6504)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-20 17:18:57 -07:00
Joaquim VergesandGitHub d8540216f1 [SDK] fix: Pass chainId to internal 1193 provider when connecting (#6506) 2025-03-20 14:52:26 -07:00
gregfromstl 654f8795b4 [SDK] Feature: Adds Universal Bridge (#6464)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new `Bridge` module to the `thirdweb` SDK, enabling users to perform token buy and sell operations across chains, along with route discovery and transaction status checks.

### Detailed summary
- Added `Bridge` module for Universal Bridge operations.
- Implemented `Bridge.Buy` and `Bridge.Sell` for token transactions.
- Created `quote` and `prepare` functions for both buy and sell operations.
- Introduced `routes` function to discover available bridge routes.
- Added `status` function to check transaction statuses.
- Defined TypeScript types: `Route`, `Status`, `Quote`, `PreparedQuote`.

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

<!-- end pr-codex -->
2025-03-20 21:27:26 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
279fb918f5 chore(deps): bump lucide-react from 0.479.0 to 0.482.0 (#6487)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-20 02:00:22 -07:00
pink hoodie.andGitHub c5cff26faa Docs Revisions: In-app & ecosystem wallet FAQs page, Pay -> Univ Bridge, Wallets Get Started (#6494)
Signed-off-by: pink hoodie. <[email protected]>
2025-03-20 01:59:57 -07:00
Joaquim VergesandGitHub 78901457cf [SDK] fix: Enforce requireApproval option for wallet connection (#6505) 2025-03-20 01:59:33 -07:00
Joaquim VergesandGitHub 47f8cd6d8d [SDk] fix: Improve multistep swap UI in PayEmbed (#6503) 2025-03-20 00:08:12 -07:00
918 changed files with 41972 additions and 26538 deletions
+17 -6
View File
@@ -16,6 +16,7 @@ env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TW_SECRET_KEY: ${{ secrets.TW_SECRET_KEY }}
TW_CLIENT_ID: ${{ secrets.TW_CLIENT_ID }}
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
jobs:
@@ -60,7 +61,7 @@ jobs:
uses: ./.github/composite-actions/install
- name: Setup Biome
uses: biomejs/setup-biome@c016c38f26f2c4a6eb3662679143614a254263fd # v2.3.0
uses: biomejs/setup-biome@f382a98e582959e6aaac8e5f8b17b31749018780 # v2.5.0
with:
version: latest
@@ -102,7 +103,7 @@ jobs:
runs-on: ubuntu-latest-8
strategy:
matrix:
package_manager: [npm, yarn, pnpm, bun]
package_manager: [pnpm] # TODO, reenable [npm, yarn, pnpm, bun]
bundler: [vite, webpack, esbuild]
steps:
- name: Check out the code
@@ -123,7 +124,14 @@ jobs:
mkdir test-project
cd test-project
npm init -y
${{ matrix.package_manager }} add react react-dom ../packages/thirdweb
# Handle different package managers
if [ "${{ matrix.package_manager }}" = "pnpm" ]; then
# Create pnpm workspace
echo '{"name": "test-project", "private": true, "workspaces": ["."]}' > package.json
echo '{"packages": ["../packages/*"]}' > pnpm-workspace.yaml
pnpm add react react-dom ../packages/thirdweb -w
fi
- name: Create test file
run: |
cd test-project
@@ -133,7 +141,7 @@ jobs:
if: matrix.bundler == 'vite'
run: |
cd test-project
${{matrix.package_manager}} add vite
${{matrix.package_manager}} add vite -w
echo 'import { defineConfig } from "vite"; import {resolve} from "path"; export default defineConfig({ build: { lib: { entry: resolve(__dirname, "index.js"), name: "e2e_test" }, outDir: "dist" }});' > vite.config.js
npx vite build
@@ -141,7 +149,7 @@ jobs:
if: matrix.bundler == 'webpack'
run: |
cd test-project
${{matrix.package_manager}} add webpack webpack-cli
${{matrix.package_manager}} add webpack webpack-cli -w
echo 'const path = require("path"); module.exports = { mode: "production", entry: "./index.js", output: { path: path.resolve(__dirname, "dist"), filename: "bundle.js" }};' > webpack.config.js
npx webpack
@@ -149,7 +157,7 @@ jobs:
if: matrix.bundler == 'esbuild'
run: |
cd test-project
${{matrix.package_manager}} add esbuild
${{matrix.package_manager}} add esbuild -w
npx esbuild index.js --bundle --outdir=dist
- name: Verify bundle
@@ -177,6 +185,9 @@ jobs:
- name: Setup & Install
uses: ./.github/composite-actions/install
- name: Build Packages
run: pnpm build
- name: Report bundle size
uses: andresz1/size-limit-action@94bc357df29c36c8f8d50ea497c3e225c3c95d1d # v1.8.0
with:
-20
View File
@@ -28,23 +28,3 @@ jobs:
- name: Run TypeDoc
run: pnpm typedoc
- name: Update Gist
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: ${{ secrets.GIST_TOKEN }}
script: |
const fs = require('fs');
const content = fs.readFileSync('./packages/thirdweb/typedoc/parsed.json', 'utf8');
const gistId = '678fe1f331a01270bb002fee660f285d';
await github.rest.gists.update({
gist_id: gistId,
files: {
'data.json': {
content: content
}
}
});
console.log(`Permalink: https://gist.githubusercontent.com/raw/${gistId}/data.json`);
+3
View File
@@ -0,0 +1,3 @@
public-hoist-pattern[]=*import-in-the-middle*
public-hoist-pattern[]=*require-in-the-middle*
public-hoist-pattern[]=*pino-pretty*
+9 -14
View File
@@ -1,5 +1,5 @@
import type { Preview } from "@storybook/react";
import "@/styles/globals.css";
import "../src/global.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { MoonIcon, SunIcon } from "lucide-react";
import { ThemeProvider, useTheme } from "next-themes";
@@ -81,23 +81,18 @@ function StoryLayout(props: {
return (
<QueryClientProvider client={queryClient}>
<div className="flex min-h-dvh min-w-0 flex-col bg-background text-foreground">
<div className="flex justify-end gap-2 border-b p-4">
<div className="fixed right-0 bottom-0 z-50 flex justify-end gap-2 p-4">
<Button
onClick={() => setTheme("dark")}
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
size="sm"
variant={theme === "dark" ? "default" : "outline"}
variant="outline"
className="h-auto w-auto shrink-0 rounded-full p-2"
>
<MoonIcon className="size-4" />
</Button>
<Button
onClick={() => setTheme("light")}
size="sm"
variant={theme === "light" ? "default" : "outline"}
className="h-auto w-auto shrink-0 rounded-full p-2"
>
<SunIcon className="size-4" />
{theme === "dark" ? (
<MoonIcon className="size-4" />
) : (
<SunIcon className="size-4" />
)}
</Button>
</div>
+3 -2
View File
@@ -49,12 +49,13 @@ module.exports = [
"/templates/:template_slug",
// -- learn --
"/learn",
"/learn/tutorials",
"/learn/tutorials/:tutorial_slug",
"/learn/guides",
"/learn/guides/:guide_slug",
"/learn/courses",
"/learn/glossary",
"/learn/glossary/:glossary_slug",
// -- faucets --
"/faucets",
// -- brand kit --
"/brand-kit",
];
+2 -1
View File
@@ -12,6 +12,7 @@
"ignoreDependencies": [
"@storybook/blocks",
"@thirdweb-dev/service-utils",
"@types/color"
"@types/color",
"fast-xml-parser"
]
}
+32 -10
View File
@@ -1,7 +1,9 @@
// @ts-check
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { XMLParser } = require("fast-xml-parser");
/**
*
* @returns {Promise<Array<{chainId: number, name: string, slug: string}>>}
*/
async function fetchChainsFromApi() {
const res = await fetch("https://api.thirdweb.com/v1/chains", {
@@ -82,16 +84,20 @@ module.exports = {
};
},
additionalPaths: async (config) => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const FRAMER_PATHS = require("./framer-rewrites");
const allChains = await fetchChainsFromApi();
const [framerUrls, allChains] = await Promise.all([
getFramerXML(),
fetchChainsFromApi(),
]);
return [
...FRAMER_PATHS.map((path) => ({
loc: path,
changefreq: config.changefreq,
priority: config.priority,
lastmod: config.autoLastmod ? new Date().toISOString() : undefined,
})),
...framerUrls.map((url) => {
return {
loc: url.loc,
changefreq: config.changefreq,
priority: config.priority,
lastmod: config.autoLastmod ? new Date().toISOString() : undefined,
};
}),
...allChains.map((chain) => {
return {
loc: `/${chain.slug}`,
@@ -136,3 +142,19 @@ async function createSearchRecordSitemaps(config) {
// filter out any failed requests
return chainsForLines.filter(Boolean);
}
async function getFramerXML() {
const framerSiteMapText = await fetch(
"https://landing.thirdweb.com/sitemap.xml",
).then((res) => res.text());
const parser = new XMLParser();
const xmlObject = parser.parse(framerSiteMapText);
/**
* @type {Array<{loc: string}>}
*/
const urls = xmlObject.urlset.url;
return urls;
}
+1 -2
View File
@@ -12,7 +12,7 @@ const ContentSecurityPolicy = `
style-src 'self' 'unsafe-inline' vercel.live;
font-src 'self' vercel.live assets.vercel.com framerusercontent.com fonts.gstatic.com;
frame-src * data:;
script-src 'self' 'unsafe-eval' 'unsafe-inline' 'wasm-unsafe-eval' 'inline-speculation-rules' *.thirdweb.com *.thirdweb-dev.com vercel.live js.stripe.com framerusercontent.com events.framer.com challenges.cloudflare.com static.reo.dev;
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;
connect-src * data: blob:;
worker-src 'self' blob:;
block-all-mixed-content;
@@ -203,7 +203,6 @@ function getConfig(): NextConfig {
type: "memory",
});
}
config.externals.push("pino-pretty");
config.module = {
...config.module,
exprContextCritical: false,
+50 -48
View File
@@ -29,29 +29,29 @@
"@emotion/styled": "11.14.0",
"@hookform/resolvers": "^3.9.1",
"@marsidev/react-turnstile": "^1.1.0",
"@radix-ui/react-accordion": "^1.2.3",
"@radix-ui/react-alert-dialog": "^1.1.6",
"@radix-ui/react-avatar": "^1.1.3",
"@radix-ui/react-checkbox": "^1.1.4",
"@radix-ui/react-dialog": "1.1.6",
"@radix-ui/react-dropdown-menu": "^2.1.6",
"@radix-ui/react-hover-card": "^1.1.6",
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-popover": "^1.1.6",
"@radix-ui/react-progress": "^1.1.2",
"@radix-ui/react-radio-group": "^1.2.3",
"@radix-ui/react-select": "^2.1.6",
"@radix-ui/react-separator": "^1.1.2",
"@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-switch": "^1.1.3",
"@radix-ui/react-tooltip": "1.1.8",
"@sentry/nextjs": "9.5.0",
"@shazow/whatsabi": "0.20.0",
"@tanstack/react-query": "5.67.3",
"@radix-ui/react-accordion": "^1.2.4",
"@radix-ui/react-alert-dialog": "^1.1.7",
"@radix-ui/react-avatar": "^1.1.4",
"@radix-ui/react-checkbox": "^1.1.5",
"@radix-ui/react-dialog": "1.1.7",
"@radix-ui/react-dropdown-menu": "^2.1.7",
"@radix-ui/react-hover-card": "^1.1.7",
"@radix-ui/react-label": "^2.1.3",
"@radix-ui/react-popover": "^1.1.7",
"@radix-ui/react-progress": "^1.1.3",
"@radix-ui/react-radio-group": "^1.2.4",
"@radix-ui/react-select": "^2.1.7",
"@radix-ui/react-separator": "^1.1.3",
"@radix-ui/react-slot": "^1.2.0",
"@radix-ui/react-switch": "^1.1.4",
"@radix-ui/react-tooltip": "1.2.0",
"@sentry/nextjs": "9.12.0",
"@shazow/whatsabi": "0.21.0",
"@tanstack/react-query": "5.72.1",
"@tanstack/react-table": "^8.21.2",
"@thirdweb-dev/service-utils": "workspace:*",
"@vercel/functions": "2.0.0",
"@vercel/og": "^0.6.5",
"@vercel/og": "^0.6.8",
"abitype": "1.0.8",
"chakra-react-select": "^4.7.6",
"class-variance-authority": "^0.7.1",
@@ -59,6 +59,7 @@
"color": "5.0.0",
"compare-versions": "^6.1.0",
"date-fns": "4.1.0",
"fast-xml-parser": "^5.2.0",
"fetch-event-stream": "0.1.5",
"flat": "^6.0.1",
"framer-motion": "12.5.0",
@@ -67,8 +68,8 @@
"input-otp": "^1.4.1",
"ioredis": "^5.6.0",
"ipaddr.js": "^2.2.0",
"lucide-react": "0.482.0",
"next": "15.2.2",
"lucide-react": "0.487.0",
"next": "15.2.4",
"next-plausible": "^3.12.4",
"next-themes": "^0.4.6",
"nextjs-toploader": "^1.6.12",
@@ -77,25 +78,26 @@
"papaparse": "^5.5.2",
"pluralize": "^8.0.0",
"posthog-js": "1.67.1",
"prettier": "3.5.3",
"qrcode": "^1.5.3",
"react": "19.0.0",
"react": "19.1.0",
"react-children-utilities": "^2.10.0",
"react-day-picker": "^8.10.1",
"react-dom": "19.0.0",
"react-dom": "19.1.0",
"react-dropzone": "^14.3.8",
"react-error-boundary": "^5.0.0",
"react-hook-form": "7.54.2",
"react-hook-form": "7.55.0",
"react-markdown": "^9.0.1",
"react-table": "^7.8.0",
"recharts": "2.15.1",
"recharts": "2.15.2",
"remark-gfm": "4.0.1",
"responsive-rsc": "0.0.7",
"server-only": "^0.0.1",
"shiki": "1.27.0",
"sonner": "2.0.1",
"sonner": "2.0.3",
"spdx-correct": "^3.2.0",
"stripe": "17.7.0",
"swagger-ui-react": "^5.20.1",
"swagger-ui-react": "^5.20.7",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7",
"thirdweb": "workspace:*",
@@ -106,41 +108,41 @@
"devDependencies": {
"@chakra-ui/cli": "^2.4.1",
"@chromatic-com/storybook": "3.2.6",
"@next/bundle-analyzer": "15.2.2",
"@next/eslint-plugin-next": "15.2.2",
"@next/bundle-analyzer": "15.2.4",
"@next/eslint-plugin-next": "15.2.4",
"@playwright/test": "1.51.1",
"@storybook/addon-essentials": "8.6.4",
"@storybook/addon-interactions": "8.6.4",
"@storybook/addon-links": "8.6.4",
"@storybook/addon-onboarding": "8.6.4",
"@storybook/addon-viewport": "8.6.4",
"@storybook/blocks": "8.6.4",
"@storybook/nextjs": "8.6.4",
"@storybook/react": "8.6.4",
"@storybook/test": "8.6.4",
"@storybook/addon-essentials": "8.6.12",
"@storybook/addon-interactions": "8.6.12",
"@storybook/addon-links": "8.6.12",
"@storybook/addon-onboarding": "8.6.12",
"@storybook/addon-viewport": "8.6.12",
"@storybook/blocks": "8.6.12",
"@storybook/nextjs": "8.6.12",
"@storybook/react": "8.6.12",
"@storybook/test": "8.6.12",
"@types/color": "4.2.0",
"@types/node": "22.13.10",
"@types/node": "22.14.0",
"@types/papaparse": "^5.3.15",
"@types/pluralize": "^0.0.33",
"@types/qrcode": "^1.5.5",
"@types/react": "19.0.10",
"@types/react-dom": "19.0.4",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.2",
"@types/react-table": "^7.7.20",
"@types/spdx-correct": "^3.1.3",
"@types/swagger-ui-react": "^4.19.0",
"@typescript-eslint/eslint-plugin": "7.14.1",
"@typescript-eslint/parser": "7.14.1",
"autoprefixer": "^10.4.21",
"checkly": "5.0.1",
"checkly": "5.2.0",
"eslint": "8.57.0",
"eslint-config-biome": "1.9.4",
"eslint-plugin-react-compiler": "19.0.0-beta-40c6c23-20250301",
"eslint-plugin-storybook": "0.11.6",
"knip": "5.46.0",
"eslint-plugin-react-compiler": "19.0.0-beta-e993439-20250405",
"eslint-plugin-storybook": "0.12.0",
"knip": "5.47.0",
"next-sitemap": "^4.2.3",
"postcss": "8.5.3",
"storybook": "8.6.4",
"storybook": "8.6.12",
"tailwindcss": "3.4.17",
"typescript": "5.8.2"
"typescript": "5.8.3"
}
}
+13
View File
@@ -361,6 +361,19 @@ async function redirects() {
"/team/:team_slug/:project_slug/connect/universal-bridge/:path*",
permanent: false,
},
// all /learn/tutorials (and sub-routes) -> /learn/guides
{
source: "/learn/tutorials/:path*",
destination: "/learn/guides/:path*",
permanent: false,
},
{
source: "/learn/tutorials",
destination: "/learn/guides",
permanent: false,
},
...legacyDashboardToTeamRedirects,
];
}
+123 -31
View File
@@ -10,12 +10,9 @@ import {
isMoralisSupported,
transformMoralisResponseToNFT,
} from "lib/wallet/nfts/moralis";
import {
generateSimpleHashUrl,
isSimpleHashSupported,
transformSimpleHashResponseToNFT,
} from "lib/wallet/nfts/simpleHash";
import type { WalletNFT } from "lib/wallet/nfts/types";
import { getVercelEnv } from "../../lib/vercel-utils";
import { DASHBOARD_THIRDWEB_CLIENT_ID } from "../constants/env";
type WalletNFTApiReturn =
| { result: WalletNFT[]; error?: undefined }
@@ -24,40 +21,20 @@ type WalletNFTApiReturn =
export async function getWalletNFTs(params: {
chainId: number;
owner: string;
isInsightSupported: boolean;
}): Promise<WalletNFTApiReturn> {
const { chainId, owner } = params;
const supportedChainSlug = await isSimpleHashSupported(chainId);
if (supportedChainSlug && process.env.SIMPLEHASH_API_KEY) {
const url = generateSimpleHashUrl({ chainSlug: supportedChainSlug, owner });
if (params.isInsightSupported) {
const response = await getWalletNFTsFromInsight({ chainId, owner });
const response = await fetch(url, {
method: "GET",
headers: {
"X-API-KEY": process.env.SIMPLEHASH_API_KEY,
},
next: {
revalidate: 10, // cache for 10 seconds
},
});
if (response.status >= 400) {
if (!response.ok) {
return {
error: response.statusText,
error: response.error,
};
}
try {
const parsedResponse = await response.json();
const result = await transformSimpleHashResponseToNFT(
parsedResponse,
owner,
);
return { result };
} catch {
return { error: "error parsing response" };
}
return { result: response.data };
}
if (isAlchemySupported(chainId)) {
@@ -104,6 +81,7 @@ export async function getWalletNFTs(params: {
const result = await transformMoralisResponseToNFT(
await parsedResponse,
owner,
chainId,
);
return { result };
@@ -115,3 +93,117 @@ export async function getWalletNFTs(params: {
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": DASHBOARD_THIRDWEB_CLIENT_ID,
},
});
if (!response.ok) {
const errorMessage = await response.text();
return {
ok: false,
error: errorMessage,
};
}
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 = {
id: nft.token_id,
contractAddress: nft.contract.address,
metadata: {
uri: isDev
? nft.metadata_url.replace("ipfscdn.io/", "thirdwebstorage-dev.com/")
: nft.metadata_url,
name: nft.name,
description: nft.description,
image: isDev
? nft.image_url.replace("ipfscdn.io/", "thirdwebstorage-dev.com/")
: nft.image_url,
animation_url: isDev
? nft.extra_metadata.animation_original_url?.replace(
"ipfscdn.io/",
"thirdwebstorage-dev.com/",
)
: nft.extra_metadata.animation_original_url,
external_url: nft.external_url,
background_color: nft.background_color,
},
owner: params.owner,
tokenURI: nft.metadata_url,
type: nft.token_type === "erc721" ? "ERC721" : "ERC1155",
supply: nft.balance,
tokenAddress: nft.contract.address,
chainId: nft.contract.chain_id,
};
return walletNFT;
});
return {
data: walletNFTs,
ok: true,
};
}
@@ -1,40 +0,0 @@
"use server";
import { getAuthToken } from "../../app/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";
export async function joinTeamWaitlist(options: {
teamSlug: string;
// currently only 'nebula' is supported
scope: "nebula";
}) {
const { teamSlug, scope } = options;
const token = await getAuthToken();
if (!token) {
return {
errorMessage: "You are not authorized to perform this action",
};
}
const res = await fetch(`${API_SERVER_URL}/v1/teams/${teamSlug}/waitlist`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
scope,
}),
});
if (!res.ok) {
return {
errorMessage: "Failed to join waitlist",
};
}
return {
success: true,
};
}
@@ -23,7 +23,7 @@ function getStripe() {
export async function getTeamInvoices(
team: Team,
options?: { cursor?: string },
options?: { cursor?: string; status?: "open" },
) {
try {
const customerId = team.stripeCustomerId;
@@ -37,6 +37,8 @@ export async function getTeamInvoices(
customer: customerId,
limit: 10,
starting_after: options?.cursor,
// Only return open invoices if the status is open
status: options?.status,
});
return invoices;
+9 -5
View File
@@ -270,9 +270,10 @@ type ActiveStatus = {
ecosystemWallet: boolean;
};
export async function isProjectActive(
params: AnalyticsQueryParams,
): Promise<ActiveStatus> {
export async function isProjectActive(params: {
teamId: string;
projectId: string;
}): Promise<ActiveStatus> {
const searchParams = buildSearchParams(params);
const res = await fetchAnalytics(
`v2/active-usage?${searchParams.toString()}`,
@@ -345,7 +346,7 @@ export async function getEcosystemWalletUsage(args: {
searchParams.append("period", period);
}
const res = await fetchAnalytics(
`v2/wallets/connects/${ecosystemSlug}?${searchParams.toString()}`,
`v2/wallet/connects?${searchParams.toString()}`,
{
method: "GET",
headers: {
@@ -355,7 +356,10 @@ export async function getEcosystemWalletUsage(args: {
);
if (res?.status !== 200) {
console.error("Failed to fetch ecosystem wallet stats");
const reason = await res?.text();
console.error(
`Failed to fetch ecosystem wallet stats: ${res?.status} - ${res.statusText} - ${reason}`,
);
return null;
}
-28
View File
@@ -74,31 +74,3 @@ export async function getDefaultTeam() {
}
return null;
}
type TeamNebulaWaitList = {
onWaitlist: boolean;
createdAt: null | string;
};
export async function getTeamNebulaWaitList(teamSlug: string) {
const token = await getAuthToken();
if (!token) {
return null;
}
const res = await fetch(
`${API_SERVER_URL}/v1/teams/${teamSlug}/waitlist?scope=nebula`,
{
headers: {
Authorization: `Bearer ${token}`,
},
},
);
if (res.ok) {
return (await res.json()).result as TeamNebulaWaitList;
}
return null;
}
+113
View File
@@ -0,0 +1,113 @@
import "server-only";
import { unstable_cache } from "next/cache";
export type RPCUsageDataItem = {
date: string;
usageType: "included" | "overage" | "rate-limit";
count: string;
};
export const fetchRPCUsage = unstable_cache(
async (params: {
teamId: string;
projectId?: string;
authToken: string;
from: string;
to: string;
period: "day" | "week" | "month" | "year" | "all";
}) => {
const analyticsEndpoint = process.env.ANALYTICS_SERVICE_URL as string;
const url = new URL(`${analyticsEndpoint}/v2/rpc/usage-types`);
url.searchParams.set("teamId", params.teamId);
if (params.projectId) {
url.searchParams.set("projectId", params.projectId);
}
url.searchParams.set("from", params.from);
url.searchParams.set("to", params.to);
url.searchParams.set("period", params.period);
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${params.authToken}`,
},
});
if (!res.ok) {
const error = await res.text();
return {
ok: false as const,
error: error,
};
}
const resData = await res.json();
return {
ok: true as const,
data: resData.data as RPCUsageDataItem[],
};
},
["rpc-usage"],
{
revalidate: 60 * 60, // 1 hour
},
);
type Last24HoursRPCUsageApiResponse = {
peakRate: {
date: string;
peakRPS: number;
};
averageRate: {
date: string;
averageRate: number;
includedCount: number;
rateLimitedCount: number;
overageCount: number;
}[];
totalCounts: {
includedCount: number;
rateLimitedCount: number;
overageCount: number;
};
};
export const getLast24HoursRPCUsage = unstable_cache(
async (params: {
teamId: string;
projectId?: string;
authToken: string;
}) => {
const analyticsEndpoint = process.env.ANALYTICS_SERVICE_URL as string;
const url = new URL(`${analyticsEndpoint}/v2/rpc/24-hours`);
url.searchParams.set("teamId", params.teamId);
if (params.projectId) {
url.searchParams.set("projectId", params.projectId);
}
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${params.authToken}`,
},
});
if (!res.ok) {
const error = await res.text();
return {
ok: false as const,
error: error,
};
}
const resData = await res.json();
return {
ok: true as const,
data: resData.data as Last24HoursRPCUsageApiResponse,
};
},
["rpc-usage-last-24-hours"],
{
revalidate: 60, // 1 minute
},
);
+61 -26
View File
@@ -1,6 +1,8 @@
"use client";
import { useMutation } from "@tanstack/react-query";
import { AlertTriangleIcon } from "lucide-react";
import Link from "next/link";
import { toast } from "sonner";
import type {
GetBillingCheckoutUrlAction,
@@ -8,6 +10,7 @@ import type {
GetBillingPortalUrlAction,
GetBillingPortalUrlOptions,
} from "../actions/billing";
import type { Team } from "../api/team";
import { cn } from "../lib/utils";
import { Spinner } from "./ui/Spinner/Spinner";
import { Button, type ButtonProps } from "./ui/button";
@@ -16,6 +19,7 @@ type CheckoutButtonProps = Omit<GetBillingCheckoutUrlOptions, "redirectUrl"> & {
getBillingCheckoutUrl: GetBillingCheckoutUrlAction;
buttonProps?: Omit<ButtonProps, "children">;
children: React.ReactNode;
billingStatus: Team["billingStatus"];
};
export function CheckoutButton({
@@ -25,6 +29,7 @@ export function CheckoutButton({
getBillingCheckoutUrl,
children,
buttonProps,
billingStatus,
}: CheckoutButtonProps) {
const getUrlMutation = useMutation({
mutationFn: async () => {
@@ -40,35 +45,65 @@ export function CheckoutButton({
const errorMessage = "Failed to open checkout page";
return (
<Button
{...buttonProps}
className={cn(buttonProps?.className, "gap-2")}
disabled={getUrlMutation.isPending || buttonProps?.disabled}
onClick={async (e) => {
buttonProps?.onClick?.(e);
getUrlMutation.mutate(undefined, {
onSuccess: (res) => {
if (!res.url) {
toast.error(errorMessage);
return;
}
<div className="flex w-full flex-col items-center gap-2">
{/* show warning if the team has an invalid payment method */}
{billingStatus === "invalidPayment" && (
<BillingWarning teamSlug={teamSlug} />
)}
<Button
{...buttonProps}
className={cn(buttonProps?.className, "w-full gap-2")}
disabled={
// disable button if the team has an invalid payment method
// api will return 402 error if the team has an invalid payment method
billingStatus === "invalidPayment" ||
getUrlMutation.isPending ||
buttonProps?.disabled
}
onClick={async (e) => {
buttonProps?.onClick?.(e);
getUrlMutation.mutate(undefined, {
onSuccess: (res) => {
if (!res.url) {
toast.error(errorMessage);
return;
}
const tab = window.open(res.url, "_blank");
const tab = window.open(res.url, "_blank");
if (!tab) {
if (!tab) {
toast.error(errorMessage);
return;
}
},
onError: () => {
toast.error(errorMessage);
return;
}
},
onError: () => {
toast.error(errorMessage);
},
});
}}
>
{getUrlMutation.isPending && <Spinner className="size-4" />}
{children}
</Button>
},
});
}}
>
{getUrlMutation.isPending && <Spinner className="size-4" />}
{children}
</Button>
</div>
);
}
function BillingWarning({ teamSlug }: { teamSlug: string }) {
return (
<div className="flex items-center gap-2 rounded-md border border-amber-200 bg-amber-50 p-3 text-amber-800 dark:border-amber-700/50 dark:bg-amber-900/30 dark:text-amber-400">
<AlertTriangleIcon className="h-5 w-5 flex-shrink-0" />
<p className="text-sm">
You have outstanding invoices. Please{" "}
<Link
href={`/team/${teamSlug}/~/settings/invoices?status=open`}
className="font-medium text-amber-700 underline transition-colors hover:text-amber-900 dark:text-amber-400 dark:hover:text-amber-300"
>
pay them
</Link>{" "}
to continue.
</p>
</div>
);
}
@@ -8,13 +8,12 @@ import { GradientAvatar } from "./GradientAvatar";
const meta = {
title: "blocks/Avatars/GradientAvatar",
component: Story,
parameters: {},
} satisfies Meta<typeof Story>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
export const Variants: Story = {
args: {},
};
@@ -22,14 +21,14 @@ const client = getThirdwebClient();
function Story() {
return (
<div className="flex flex-col gap-10 p-10">
<p> All images below are set with size-6 className </p>
<div className="container flex max-w-6xl flex-col gap-10 py-10">
<p> All images below are set with size-20 className </p>
<BadgeContainer label="No Src, No id - Skeleton">
<GradientAvatar
id={undefined}
src={undefined}
className="size-6"
className="size-20"
client={client}
/>
</BadgeContainer>
@@ -38,7 +37,7 @@ function Story() {
<GradientAvatar
id={"foo"}
src={undefined}
className="size-6"
className="size-20"
client={client}
/>
</BadgeContainer>
@@ -47,7 +46,7 @@ function Story() {
<GradientAvatar
id={"foo"}
src={""}
className="size-6"
className="size-20"
client={client}
/>
</BadgeContainer>
@@ -56,7 +55,7 @@ function Story() {
<GradientAvatar
id={"bar"}
src={""}
className="size-6"
className="size-20"
client={client}
/>
</BadgeContainer>
@@ -65,7 +64,7 @@ function Story() {
<GradientAvatar
src="invalid-src"
id={undefined}
className="size-6"
className="size-20"
client={client}
/>
</BadgeContainer>
@@ -74,7 +73,7 @@ function Story() {
<GradientAvatar
src="https://picsum.photos/200/300"
id={undefined}
className="size-6"
className="size-20"
client={client}
/>
</BadgeContainer>
@@ -83,7 +82,7 @@ function Story() {
<GradientAvatar
src="ipfs://QmZbeJYEs7kCJHyQxjxU2SJUtjSAr4m87wzJFJUyWomKdj/Smily.svg"
id={undefined}
className="size-6"
className="size-20"
client={client}
/>
</BadgeContainer>
@@ -123,14 +122,14 @@ function ToggleTest() {
<GradientAvatar
src={data?.src}
id={data?.id}
className="size-6"
className="size-20"
client={client}
/>
</BadgeContainer>
<BadgeContainer label="invalid Src">
<GradientAvatar
className="size-6"
className="size-20"
src={data ? "invalid-src" : undefined}
id={undefined}
client={client}
@@ -14,7 +14,7 @@ const meta = {
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
export const Variants: Story = {
args: {},
};
@@ -22,7 +22,7 @@ const client = getThirdwebClient();
function Story() {
return (
<div className="flex flex-col gap-10 p-10">
<div className="container flex max-w-6xl flex-col gap-10 py-10">
<p> All images below are set with size-6 className </p>
<BadgeContainer label="No Src - Skeleton">
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react";
import { BadgeContainer, mobileViewport } from "../../../stories/utils";
import { BadgeContainer } from "../../../stories/utils";
import { DangerSettingCard } from "./DangerSettingCard";
const meta = {
@@ -15,17 +15,10 @@ const meta = {
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
export const Variants: Story = {
args: {},
};
export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};
function Story() {
return (
<div className="container flex max-w-[1000px] flex-col gap-8 py-10">
@@ -9,13 +9,12 @@ import { Img } from "./Img";
const meta = {
title: "blocks/Img",
component: Story,
parameters: {},
} satisfies Meta<typeof Story>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
export const Variants: Story = {
args: {},
};
@@ -19,30 +19,7 @@ export function MobileSidebar(props: {
triggerClassName?: string;
}) {
const [isOpen, setIsOpen] = useState(false);
const pathname = usePathname();
const activeLink = useMemo(() => {
function isActive(link: SidebarBaseLink) {
if (link.exactMatch) {
return link.href === pathname;
}
return pathname?.startsWith(link.href);
}
for (const link of props.links) {
if ("group" in link) {
for (const subLink of link.links) {
if (isActive(subLink)) {
return subLink;
}
}
} else {
if (isActive(link)) {
return link;
}
}
}
}, [props.links, pathname]);
const activeLink = useActiveSidebarLink(props.links);
const defaultTrigger = (
<Button
@@ -75,3 +52,32 @@ export function MobileSidebar(props: {
</Dialog>
);
}
export function useActiveSidebarLink(links: SidebarLink[]) {
const pathname = usePathname();
const activeLink = useMemo(() => {
function isActive(link: SidebarBaseLink) {
if (link.exactMatch) {
return link.href === pathname;
}
return pathname?.startsWith(link.href);
}
for (const link of links) {
if ("group" in link) {
for (const subLink of link.links) {
if (isActive(subLink)) {
return subLink;
}
}
} else if ("href" in link) {
if (isActive(link)) {
return link;
}
}
}
}, [links, pathname]);
return activeLink;
}
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from "@storybook/react";
import { useState } from "react";
import { BadgeContainer, mobileViewport } from "../../../stories/utils";
import { BadgeContainer } from "../../../stories/utils";
import { MultiNetworkSelector } from "./NetworkSelectors";
const meta = {
@@ -16,20 +16,13 @@ const meta = {
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
export const Variants: Story = {
args: {},
};
export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};
function Story() {
return (
<div className="container flex max-w-[1000px] flex-col gap-8 lg:p-10">
<div className="container flex max-w-6xl flex-col gap-8 py-10">
<Variant label="No Chains selected by default" selectedChainIds={[]} />
<Variant
label="Polygon, Ethereum selected by default"
@@ -4,7 +4,7 @@ import { MultiSelect } from "@/components/blocks/multi-select";
import { SelectWithSearch } from "@/components/blocks/select-with-search";
import { Badge } from "@/components/ui/badge";
import { useCallback, useMemo } from "react";
import { ChainIcon } from "../../../components/icons/ChainIcon";
import { ChainIconClient } from "../../../components/icons/ChainIcon";
import { useAllChainsData } from "../../../hooks/chains/allChains";
function cleanChainName(chainName: string) {
@@ -18,17 +18,44 @@ export function MultiNetworkSelector(props: {
onChange: (chainIds: number[]) => void;
disableChainId?: boolean;
className?: string;
priorityChains?: number[];
hideTestnets?: boolean;
}) {
const { allChains, idToChain } = useAllChainsData();
const options = useMemo(() => {
return allChains.map((chain) => {
let sortedChains = allChains;
if (props.priorityChains) {
const priorityChainsSet = new Set();
for (const chainId of props.priorityChains || []) {
priorityChainsSet.add(chainId);
}
const priorityChains = (props.priorityChains || [])
.map((chainId) => {
return idToChain.get(chainId);
})
.filter((v) => !!v);
const otherChains = allChains.filter(
(chain) => !priorityChainsSet.has(chain.chainId),
);
sortedChains = [...priorityChains, ...otherChains];
}
if (props.hideTestnets) {
sortedChains = sortedChains.filter((chain) => !chain.testnet);
}
return sortedChains.map((chain) => {
return {
label: cleanChainName(chain.name),
value: String(chain.chainId),
};
});
}, [allChains]);
}, [allChains, props.priorityChains, idToChain, props.hideTestnets]);
const searchFn = useCallback(
(option: Option, searchValue: string) => {
@@ -55,7 +82,7 @@ export function MultiNetworkSelector(props: {
return (
<div className="flex justify-between gap-4">
<span className="flex grow gap-2 truncate text-left">
<ChainIcon
<ChainIconClient
className="size-5"
ipfsSrc={chain.icon?.url}
loading="lazy"
@@ -150,7 +177,7 @@ export function SingleNetworkSelector(props: {
return (
<div className="flex justify-between gap-4">
<span className="flex grow gap-2 truncate text-left">
<ChainIcon
<ChainIconClient
className="size-5"
ipfsSrc={chain.icon?.url}
loading="lazy"
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react";
import { BadgeContainer, mobileViewport } from "../../../stories/utils";
import { BadgeContainer } from "../../../stories/utils";
import { SettingsCard } from "./SettingsCard";
const meta = {
@@ -15,139 +15,130 @@ const meta = {
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
export const Variants: Story = {
args: {},
};
export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};
function Story() {
return (
<div className="min-h-dvh bg-background py-6 text-foreground">
<div className="container flex max-w-[1100px] flex-col gap-10 lg:p-10">
<BadgeContainer label="No Header">
<SettingsCard
bottomText="This is bottom text"
header={undefined}
errorText={undefined}
noPermissionText={undefined}
saveButton={undefined}
>
<ChildrenPlaceholder />
</SettingsCard>
</BadgeContainer>
<div className="container flex max-w-6xl flex-col gap-10 py-10">
<BadgeContainer label="No Header">
<SettingsCard
bottomText="This is bottom text"
header={undefined}
errorText={undefined}
noPermissionText={undefined}
saveButton={undefined}
>
<ChildrenPlaceholder />
</SettingsCard>
</BadgeContainer>
<BadgeContainer label="Header">
<SettingsCard
bottomText="This is bottom text"
header={{
title: "Some Title",
description: "This is some description for this card",
}}
errorText={undefined}
noPermissionText={undefined}
saveButton={undefined}
>
<ChildrenPlaceholder />
</SettingsCard>
</BadgeContainer>
<BadgeContainer label="Header">
<SettingsCard
bottomText="This is bottom text"
header={{
title: "Some Title",
description: "This is some description for this card",
}}
errorText={undefined}
noPermissionText={undefined}
saveButton={undefined}
>
<ChildrenPlaceholder />
</SettingsCard>
</BadgeContainer>
<BadgeContainer label="Error">
<SettingsCard
bottomText="This is bottom text"
header={{
title: "Some Title",
description: "This is some description for this card",
}}
errorText="This is error text"
noPermissionText={undefined}
saveButton={undefined}
>
<ChildrenPlaceholder />
</SettingsCard>
</BadgeContainer>
<BadgeContainer label="Error">
<SettingsCard
bottomText="This is bottom text"
header={{
title: "Some Title",
description: "This is some description for this card",
}}
errorText="This is error text"
noPermissionText={undefined}
saveButton={undefined}
>
<ChildrenPlaceholder />
</SettingsCard>
</BadgeContainer>
<BadgeContainer label="No Permission">
<SettingsCard
bottomText="This is bottom text"
header={{
title: "Some Title",
description: "This is some description for this card",
}}
saveButton={{
disabled: false,
isPending: false,
onClick: () => {},
}}
noPermissionText="You do not have permission to edit this"
errorText={undefined}
>
<ChildrenPlaceholder />
</SettingsCard>
</BadgeContainer>
<BadgeContainer label="No Permission">
<SettingsCard
bottomText="This is bottom text"
header={{
title: "Some Title",
description: "This is some description for this card",
}}
saveButton={{
disabled: false,
isPending: false,
onClick: () => {},
}}
noPermissionText="You do not have permission to edit this"
errorText={undefined}
>
<ChildrenPlaceholder />
</SettingsCard>
</BadgeContainer>
<BadgeContainer label="Save">
<SettingsCard
bottomText="This is bottom text"
header={{
title: "Some Title",
description: "This is some description for this card",
}}
saveButton={{
disabled: false,
isPending: false,
onClick: () => {},
}}
noPermissionText={undefined}
errorText={undefined}
>
<ChildrenPlaceholder />
</SettingsCard>
</BadgeContainer>
<BadgeContainer label="Save">
<SettingsCard
bottomText="This is bottom text"
header={{
title: "Some Title",
description: "This is some description for this card",
}}
saveButton={{
disabled: false,
isPending: false,
onClick: () => {},
}}
noPermissionText={undefined}
errorText={undefined}
>
<ChildrenPlaceholder />
</SettingsCard>
</BadgeContainer>
<BadgeContainer label="Save Disabled">
<SettingsCard
bottomText="This is bottom text"
header={{
title: "Some Title",
description: "This is some description for this card",
}}
saveButton={{
disabled: true,
isPending: false,
onClick: () => {},
}}
noPermissionText={undefined}
errorText={undefined}
>
<ChildrenPlaceholder />
</SettingsCard>
</BadgeContainer>
<BadgeContainer label="Save Disabled">
<SettingsCard
bottomText="This is bottom text"
header={{
title: "Some Title",
description: "This is some description for this card",
}}
saveButton={{
disabled: true,
isPending: false,
onClick: () => {},
}}
noPermissionText={undefined}
errorText={undefined}
>
<ChildrenPlaceholder />
</SettingsCard>
</BadgeContainer>
<BadgeContainer label="Save Loading">
<SettingsCard
bottomText="This is bottom text"
header={{
title: "Some Title",
description: "This is some description for this card",
}}
saveButton={{
disabled: false,
isPending: true,
onClick: () => {},
}}
noPermissionText={undefined}
errorText={undefined}
>
<ChildrenPlaceholder />
</SettingsCard>
</BadgeContainer>
</div>
<BadgeContainer label="Save Loading">
<SettingsCard
bottomText="This is bottom text"
header={{
title: "Some Title",
description: "This is some description for this card",
}}
saveButton={{
disabled: false,
isPending: true,
onClick: () => {},
}}
noPermissionText={undefined}
errorText={undefined}
>
<ChildrenPlaceholder />
</SettingsCard>
</BadgeContainer>
</div>
);
}
@@ -2,11 +2,13 @@ import { ExternalLinkIcon } from "lucide-react";
import type React from "react";
import { cn } from "../../lib/utils";
import { NavLink } from "../ui/NavLink";
import { Separator } from "../ui/separator";
export type SidebarBaseLink = {
href: string;
label: React.ReactNode;
exactMatch?: boolean;
icon?: React.FC<{ className?: string }>;
tracking?: {
category: string;
action: string;
@@ -19,6 +21,9 @@ export type SidebarLink =
| {
group: string;
links: SidebarBaseLink[];
}
| {
separator: true;
};
type SidebarContentProps = {
@@ -27,21 +32,18 @@ type SidebarContentProps = {
className?: string;
};
export function Sidebar(props: SidebarContentProps) {
export function CustomSidebar(props: SidebarContentProps) {
return (
<aside
className={cn(
"sticky top-0 hidden w-[230px] flex-shrink-0 self-start lg:block",
props.className,
)}
>
<div className="py-7">
{props.header}
<div className="flex flex-col gap-1">
<RenderSidebarLinks links={props.links} />
<div className={cn("hidden w-[230px] shrink-0 lg:block", props.className)}>
<aside className="sticky top-0 self-start">
<div className="py-7">
{props.header}
<div className="flex flex-col gap-1">
<RenderSidebarLinks links={props.links} />
</div>
</div>
</div>
</aside>
</aside>
</div>
);
}
@@ -61,6 +63,10 @@ export function RenderSidebarLinks(props: { links: SidebarLink[] }) {
);
}
if ("separator" in link) {
return <Separator className="my-2" />;
}
const isExternal = link.href.startsWith("http");
return (
<NavLink
@@ -68,9 +74,10 @@ export function RenderSidebarLinks(props: { links: SidebarLink[] }) {
key={i}
href={link.href}
className="flex items-center gap-2 rounded-md px-3 py-2 text-muted-foreground text-sm hover:bg-accent"
activeClassName="text-foreground"
activeClassName="text-foreground bg-accent"
exactMatch={link.exactMatch}
>
{link.icon && <link.icon className="size-4" />}
{link.label}
{isExternal && <ExternalLinkIcon className="size-3" />}
</NavLink>
@@ -1,5 +1,4 @@
import type { Meta, StoryObj } from "@storybook/react";
import { mobileViewport } from "../../../stories/utils";
import { SidebarLayout } from "./SidebarLayout";
const meta = {
@@ -15,17 +14,10 @@ const meta = {
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
export const Variants: Story = {
args: {},
};
export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};
function Story() {
return (
<SidebarLayout
@@ -1,6 +1,23 @@
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarRail,
SidebarSeparator,
SidebarTrigger,
useSidebar,
} from "@/components/ui/sidebar";
import { cn } from "../../lib/utils";
import { MobileSidebar } from "./MobileSidebar";
import { Sidebar, type SidebarLink } from "./Sidebar";
import { NavLink } from "../ui/NavLink";
import { Separator } from "../ui/separator";
import { MobileSidebar, useActiveSidebarLink } from "./MobileSidebar";
import { CustomSidebar, type SidebarLink } from "./Sidebar";
import { AppFooter } from "./app-footer";
export function SidebarLayout(props: {
sidebarLinks: SidebarLink[];
@@ -17,7 +34,10 @@ export function SidebarLayout(props: {
props.className,
)}
>
<Sidebar links={sidebarLinks} className={props.desktopSidebarClassName} />
<CustomSidebar
links={sidebarLinks}
className={props.desktopSidebarClassName}
/>
<MobileSidebar
links={sidebarLinks}
triggerClassName={props.mobileSidebarClassName}
@@ -29,3 +49,117 @@ export function SidebarLayout(props: {
</div>
);
}
export function FullWidthSidebarLayout(props: {
contentSidebarLinks: SidebarLink[];
footerSidebarLinks?: SidebarLink[];
children: React.ReactNode;
className?: string;
}) {
const { contentSidebarLinks, children, footerSidebarLinks } = props;
return (
<div
className={cn(
"relative flex w-full flex-1 overflow-y-hidden",
props.className,
)}
>
{/* left - sidebar */}
<Sidebar collapsible="icon" side="left" className="pt-2">
<SidebarContent>
<SidebarGroup>
<SidebarGroupContent>
<RenderSidebarGroup
sidebarLinks={contentSidebarLinks}
groupName={undefined}
/>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
{footerSidebarLinks && (
<SidebarFooter className="pb-3">
<RenderSidebarGroup
sidebarLinks={footerSidebarLinks}
groupName={undefined}
/>
</SidebarFooter>
)}
<SidebarRail />
</Sidebar>
{/* right - content */}
<div className="flex h-full flex-grow flex-col overflow-y-auto">
<MobileSidebarTrigger
links={[...contentSidebarLinks, ...(footerSidebarLinks || [])]}
/>
<main className="z-0 flex min-w-0 grow flex-col max-sm:w-full">
{children}
</main>
<AppFooter containerClassName="max-w-7xl" />
</div>
</div>
);
}
function RenderSidebarGroup(props: {
sidebarLinks: SidebarLink[];
groupName: string | undefined;
}) {
const { sidebarLinks } = props;
const sidebar = useSidebar();
return (
<SidebarMenu className="gap-1.5">
{sidebarLinks.map((link) => {
if ("href" in link) {
return (
<SidebarMenuItem key={link.href}>
<SidebarMenuButton asChild>
<NavLink
href={link.href}
className="flex items-center gap-2 text-muted-foreground text-sm hover:bg-accent"
activeClassName="text-foreground bg-accent"
exactMatch={link.exactMatch}
tracking={link.tracking}
onClick={() => {
sidebar.setOpenMobile(false);
}}
>
{link.icon && <link.icon className="size-4" />}
<span>{link.label}</span>
</NavLink>
</SidebarMenuButton>
</SidebarMenuItem>
);
}
if ("separator" in link) {
return <SidebarSeparator className="my-1" />;
}
return (
<RenderSidebarGroup
sidebarLinks={link.links}
groupName={link.group}
key={link.group}
/>
);
})}
</SidebarMenu>
);
}
function MobileSidebarTrigger(props: { links: SidebarLink[] }) {
const activeLink = useActiveSidebarLink(props.links);
return (
<div className="flex items-center gap-3 border-b px-4 py-4 lg:hidden">
<SidebarTrigger className="size-4" />
<Separator orientation="vertical" className="h-4" />
{activeLink && <span className="text-sm">{activeLink.label}</span>}
</div>
);
}
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from "@storybook/react";
import { useState } from "react";
import { BadgeContainer, mobileViewport } from "../../../stories/utils";
import { BadgeContainer } from "../../../stories/utils";
import { SingleNetworkSelector } from "./NetworkSelectors";
const meta = {
@@ -16,20 +16,13 @@ const meta = {
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
export const Variants: Story = {
args: {},
};
export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};
function Story() {
return (
<div className="container flex max-w-[1000px] flex-col gap-8 lg:p-10">
<div className="container flex max-w-6xl flex-col gap-8 py-10">
<Variant label="No Chain ID selected by default" chainId={undefined} />
<Variant label="Polygon selected by default" chainId={137} />
<Variant
@@ -12,17 +12,54 @@ import Link from "next/link";
type AppFooterProps = {
className?: string;
containerClassName?: string;
};
const footerLinks = [
{
href: "/home",
label: "Home",
},
{
href: "https://blog.thirdweb.com",
label: "Blog",
},
{
href: "https://portal.thirdweb.com/changelog",
label: "Changelog",
},
{
href: "https://feedback.thirdweb.com/",
label: "Feedback",
},
{
href: "https://thirdweb.com/privacy-policy",
label: "Privacy Policy",
},
{
href: "https://thirdweb.com/terms",
label: "Terms of Service",
},
{
href: "https://thirdweb.com/chainlist",
label: "Chainlist",
},
];
export function AppFooter(props: AppFooterProps) {
return (
<footer
className={cn(
"w-full border-border border-t bg-card py-6 md:py-8",
"w-full border-border border-t py-6 md:py-8",
props.className,
)}
>
<div className="container flex flex-col gap-4 md:gap-6">
<div
className={cn(
"container flex flex-col gap-4 md:gap-6",
props.containerClassName,
)}
>
{/* top row */}
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div className="flex flex-row items-center gap-3">
@@ -74,57 +111,28 @@ export function AppFooter(props: AppFooterProps) {
</div>
{/* bottom row */}
<div className="grid grid-flow-col grid-cols-2 grid-rows-5 gap-2 md:flex md:flex-row md:justify-between">
<Link
className="px-[10px] py-[6px] text-muted-foreground text-sm hover:underline"
href="/home"
>
Home
</Link>
<Link
className="px-[10px] py-[6px] text-muted-foreground text-sm hover:underline"
href="https://blog.thirdweb.com"
target="_blank"
>
Blog
</Link>
<Link
className="px-[10px] py-[6px] text-muted-foreground text-sm hover:underline"
href="https://portal.thirdweb.com/changelog"
target="_blank"
>
Changelog
</Link>
<Link
className="px-[10px] py-[6px] text-muted-foreground text-sm hover:underline"
href="https://feedback.thirdweb.com/"
target="_blank"
>
Feedback
</Link>
<Link
className="px-[10px] py-[6px] text-muted-foreground text-sm hover:underline"
href="https://thirdweb.com/privacy-policy"
target="_blank"
>
Privacy Policy
</Link>
<Link
className="px-[10px] py-[6px] text-muted-foreground text-sm hover:underline"
href="https://thirdweb.com/terms"
target="_blank"
>
Terms of Service
</Link>
<Link
className="px-[10px] py-[6px] text-muted-foreground text-sm hover:underline"
href="https://thirdweb.com/chainlist"
target="_blank"
>
Chain List
</Link>
{footerLinks.map((link) => (
<FooterLink key={link.href} {...link} />
))}
</div>
</div>
</footer>
);
}
function FooterLink(props: {
href: string;
label: string;
prefetch?: boolean;
}) {
return (
<Link
href={props.href}
prefetch={false}
className="px-0 py-[6px] text-muted-foreground text-sm hover:underline"
target="_blank"
>
{props.label}
</Link>
);
}
@@ -4,6 +4,7 @@ import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
@@ -17,7 +18,7 @@ import {
} from "@/components/ui/chart";
import { formatDate } from "date-fns";
import { useMemo } from "react";
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts";
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts";
import {
EmptyChartState,
LoadingChartState,
@@ -30,10 +31,17 @@ type ThirdwebAreaChartProps<TConfig extends ChartConfig> = {
description?: string;
titleClassName?: string;
};
footer?: React.ReactNode;
customHeader?: React.ReactNode;
// chart config
config: TConfig;
data: Array<Record<keyof TConfig, number> & { time: number | string | Date }>;
showLegend?: boolean;
maxLimit?: number;
yAxis?: boolean;
xAxis?: {
sameDay?: boolean;
};
// chart className
chartClassName?: string;
@@ -60,6 +68,8 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
</CardHeader>
)}
{props.customHeader && props.customHeader}
<CardContent className={cn(!props.header && "pt-6")}>
<ChartContainer config={props.config} className={props.chartClassName}>
{props.isPending ? (
@@ -67,17 +77,33 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
) : props.data.length === 0 ? (
<EmptyChartState />
) : (
<AreaChart accessibilityLayer data={props.data}>
<AreaChart
accessibilityLayer
data={
props.maxLimit
? props.data.map((d) => ({
...d,
maxLimit: props.maxLimit,
}))
: props.data
}
>
<CartesianGrid vertical={false} />
{props.yAxis && <YAxis tickLine={false} axisLine={false} />}
<XAxis
dataKey="time"
tickLine={false}
axisLine={false}
tickMargin={20}
tickFormatter={(value) => formatDate(new Date(value), "MMM dd")}
tickFormatter={(value) =>
formatDate(
new Date(value),
props.xAxis?.sameDay ? "MMM dd, HH:mm" : "MMM dd",
)
}
/>
<ChartTooltip
cursor={false}
cursor={true}
content={
<ChartTooltipContent
hideLabel={
@@ -121,6 +147,16 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
stackId="a"
/>
))}
{props.maxLimit && (
<Area
type="monotone"
dataKey="maxLimit"
stroke="#ef4444"
strokeWidth={2}
strokeDasharray="5 5"
fill="none"
/>
)}
{props.showLegend && (
<ChartLegend
@@ -131,6 +167,9 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
)}
</ChartContainer>
</CardContent>
{props.footer && (
<CardFooter className="w-full">{props.footer}</CardFooter>
)}
</Card>
);
}
@@ -32,6 +32,7 @@ type ThirdwebBarChartProps<TConfig extends ChartConfig> = {
description?: string;
titleClassName?: string;
};
customHeader?: React.ReactNode;
// chart config
config: TConfig;
data: Array<Record<keyof TConfig, number> & { time: number | string | Date }>;
@@ -41,7 +42,9 @@ type ThirdwebBarChartProps<TConfig extends ChartConfig> = {
chartClassName?: string;
isPending: boolean;
toolTipLabelFormatter?: (label: string, payload: unknown) => React.ReactNode;
toolTipValueFormatter?: (value: unknown) => React.ReactNode;
hideLabel?: boolean;
emptyChartState?: React.ReactElement;
};
export function ThirdwebBarChart<TConfig extends ChartConfig>(
@@ -65,12 +68,14 @@ export function ThirdwebBarChart<TConfig extends ChartConfig>(
</CardHeader>
)}
{props.customHeader && props.customHeader}
<CardContent className={cn(!props.header && "pt-6")}>
<ChartContainer config={props.config} className={props.chartClassName}>
{props.isPending ? (
<LoadingChartState />
) : props.data.length === 0 ? (
<EmptyChartState />
<EmptyChartState>{props.emptyChartState}</EmptyChartState>
) : (
<BarChart accessibilityLayer data={props.data}>
<CartesianGrid vertical={false} />
@@ -88,6 +93,7 @@ export function ThirdwebBarChart<TConfig extends ChartConfig>(
props.hideLabel !== undefined ? props.hideLabel : true
}
labelFormatter={props.toolTipLabelFormatter}
valueFormatter={props.toolTipValueFormatter}
/>
}
/>
@@ -96,25 +102,15 @@ export function ThirdwebBarChart<TConfig extends ChartConfig>(
content={<ChartLegendContent className="pt-5" />}
/>
)}
{configKeys.map((key, idx) => (
{configKeys.map((key) => (
<Bar
key={key}
dataKey={key}
// if stacked then they should all be the same stackId
// if grouped then they should all be unique stackId (so the key works great)
stackId={variant === "stacked" ? "a" : key}
fill={`var(--color-${key})`}
// if stacked then we need to figure out the radius based on the index in the array
// if grouped then we can just use the same radius for all
radius={
variant === "stacked"
? idx === 0
? [0, 0, 4, 4]
: idx === configKeys.length - 1
? [4, 4, 0, 0]
: [0, 0, 0, 0]
: [4, 4, 4, 4]
}
fill={props.config[key]?.color}
radius={[4, 4, 4, 4]}
strokeWidth={1}
className="stroke-background"
/>
@@ -8,7 +8,7 @@ import {
} from "@/components/ui/select";
import type { Meta, StoryObj } from "@storybook/react";
import { useState } from "react";
import { BadgeContainer, mobileViewport } from "../../../stories/utils";
import { BadgeContainer } from "../../../stories/utils";
import { type CodeEnvironment, CodeSegment } from "./code-segment.client";
const meta = {
@@ -24,17 +24,10 @@ const meta = {
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
export const Variants: Story = {
args: {},
};
export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};
type Mode = "default" | "no-tabs" | "only-tabs";
function Story() {
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from "@storybook/react";
import { useMemo, useState } from "react";
import { BadgeContainer, mobileViewport } from "../../../stories/utils";
import { BadgeContainer } from "../../../stories/utils";
import { MultiSelect } from "./multi-select";
const meta = {
@@ -16,17 +16,10 @@ const meta = {
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
export const Variants: Story = {
args: {},
};
export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};
function createList(len: number) {
return Array.from({ length: len }, (_, i) => ({
value: `${i}`,
@@ -36,7 +29,7 @@ function createList(len: number) {
function Story() {
return (
<div className="mx-auto flex w-full max-w-[600px] flex-col gap-6 px-4 py-6">
<div className="container flex max-w-6xl flex-col gap-6 py-10">
<VariantTest defaultValues={[]} storyLabel="5 items" listLen={5} />
<VariantTest defaultValues={[]} storyLabel="5000 items" listLen={5000} />
<VariantTest
@@ -3,33 +3,36 @@ import type { Team } from "@/api/team";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { TrackedLinkTW } from "@/components/ui/tracked-link";
import { cn } from "@/lib/utils";
import { CheckIcon, CircleAlertIcon, CircleDollarSignIcon } from "lucide-react";
import { CheckIcon, CircleDollarSignIcon } from "lucide-react";
import Link from "next/link";
import type React from "react";
import { TEAM_PLANS } from "utils/pricing";
import { useTrack } from "../../../hooks/analytics/useTrack";
import { remainingDays } from "../../../utils/date-utils";
import type { GetBillingCheckoutUrlAction } from "../../actions/billing";
import type { ProductSKU } from "../../lib/billing";
import { CheckoutButton } from "../billing";
type ButtonProps = React.ComponentProps<typeof Button>;
const PRO_CONTACT_US_URL =
"https://meetings.hubspot.com/sales-thirdweb/thirdweb-pro";
type PricingCardCta = {
hint?: string;
title: string;
onClick?: () => void;
} & (
| {
type: "link";
href: string;
}
| {
type: "checkout";
}
);
type PricingCardProps = {
teamSlug: string;
billingPlan: Exclude<Team["billingPlan"], "free">;
cta?: {
hint?: string;
title: string;
tracking: {
category: string;
label?: string;
};
variant?: ButtonProps["variant"];
onClick?: () => void;
};
billingStatus: Team["billingStatus"];
billingPlan: keyof typeof TEAM_PLANS;
cta?: PricingCardCta;
ctaHint?: string;
highlighted?: boolean;
current?: boolean;
@@ -39,6 +42,7 @@ type PricingCardProps = {
export const PricingCard: React.FC<PricingCardProps> = ({
teamSlug,
billingStatus,
billingPlan,
cta,
highlighted = false,
@@ -49,13 +53,23 @@ export const PricingCard: React.FC<PricingCardProps> = ({
const plan = TEAM_PLANS[billingPlan];
const isCustomPrice = typeof plan.price === "string";
const trackEvent = useTrack();
const remainingTrialDays =
(activeTrialEndsAt ? remainingDays(activeTrialEndsAt) : 0) || 0;
const handleCTAClick = () => {
cta?.onClick?.();
trackEvent({
category: "account",
label: `${billingPlan}Plan`,
action: "click",
});
};
return (
<div
className={cn(
"z-10 flex w-full flex-col gap-6 rounded-xl border border-border bg-card p-4 md:p-6",
"z-10 flex w-full flex-col gap-4 rounded-xl border border-border bg-card p-4",
current && "border-blue-500",
highlighted && "border-active-border",
)}
@@ -71,13 +85,13 @@ export const PricingCard: React.FC<PricingCardProps> = ({
<div className="flex flex-col gap-5">
{/* Title + Desc */}
<div>
<div className="mb-2 flex flex-row items-center gap-2">
<div className="mb-1 flex flex-row items-center gap-3">
<h3 className="font-semibold text-2xl capitalize tracking-tight">
{plan.title}
</h3>
{current && <Badge className="capitalize">Current plan</Badge>}
</div>
<p className="max-w-[320px] text-muted-foreground">
<p className="max-w-[320px] text-muted-foreground text-sm">
{plan.description}
</p>
</div>
@@ -85,30 +99,13 @@ export const PricingCard: React.FC<PricingCardProps> = ({
{/* Price */}
<div className="flex flex-col gap-0.5">
<div className="flex items-center gap-2">
<span className="font-semibold text-3xl text-foreground tracking-tight">
<span className="font-semibold text-2xl text-foreground tracking-tight">
${plan.price}
</span>
{!isCustomPrice && (
<span className="text-muted-foreground">/ month</span>
)}
{billingPlan === "starter" && (
<ToolTipLabel
contentClassName="max-w-[320px]"
label="We will place a temporary hold of $25 to verify your card, this will be immediately released back to you after verification."
>
<Button
asChild
variant="ghost"
className="h-auto w-auto p-1 text-muted-foreground hover:text-foreground"
>
<div>
<CircleAlertIcon className="size-5 shrink-0" />
</div>
</Button>
</ToolTipLabel>
)}
</div>
{remainingTrialDays > 0 && (
@@ -124,7 +121,7 @@ export const PricingCard: React.FC<PricingCardProps> = ({
<div className="flex grow flex-col items-start gap-2 text-foreground">
{plan.subTitle && (
<p className="font-medium text-foreground">{plan.subTitle}</p>
<p className="font-medium text-foreground text-sm">{plan.subTitle}</p>
)}
{plan.features.map((f) => (
@@ -134,29 +131,31 @@ export const PricingCard: React.FC<PricingCardProps> = ({
{cta && (
<div className="flex flex-col gap-3">
{billingPlan !== "pro" ? (
{billingPlanToSkuMap[billingPlan] && cta.type === "checkout" && (
<CheckoutButton
billingStatus={billingStatus}
buttonProps={{
variant: cta.variant || "outline",
className: "gap-2",
onClick: cta.onClick,
variant: highlighted ? "default" : "outline",
className: highlighted ? undefined : "bg-background",
onClick: handleCTAClick,
}}
teamSlug={teamSlug}
sku={billingPlan === "starter" ? "plan:starter" : "plan:growth"}
sku={billingPlanToSkuMap[billingPlan]}
getBillingCheckoutUrl={getBillingCheckoutUrl}
>
{cta.title}
</CheckoutButton>
) : (
<Button variant={cta.variant || "outline"} asChild>
<TrackedLinkTW
href={PRO_CONTACT_US_URL}
label={cta.tracking?.label}
category={cta.tracking?.category}
target="_blank"
>
)}
{cta.type === "link" && (
<Button
variant={highlighted ? "default" : "outline"}
className={highlighted ? undefined : "bg-background"}
asChild
>
<Link href={cta.href} target="_blank" onClick={handleCTAClick}>
{cta.title}
</TrackedLinkTW>
</Link>
</Button>
)}
@@ -171,6 +170,19 @@ export const PricingCard: React.FC<PricingCardProps> = ({
);
};
const billingPlanToSkuMap: Record<Team["billingPlan"], ProductSKU | undefined> =
{
starter: "plan:starter",
growth: "plan:growth",
accelerate: "plan:accelerate",
scale: "plan:scale",
// we can't render checkout buttons for these plans:
pro: undefined,
free: undefined,
growth_legacy: undefined,
starter_legacy: undefined,
};
type FeatureItemProps = {
text: string | string[];
};
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from "@storybook/react";
import { useMemo, useState } from "react";
import { BadgeContainer, mobileViewport } from "../../../stories/utils";
import { BadgeContainer } from "../../../stories/utils";
import { SelectWithSearch } from "./select-with-search";
const meta = {
@@ -16,17 +16,10 @@ const meta = {
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
export const Variants: Story = {
args: {},
};
export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};
function createList(len: number) {
return Array.from({ length: len }, (_, i) => ({
value: `${i}`,
@@ -36,7 +29,7 @@ function createList(len: number) {
function Story() {
return (
<div className="mx-auto flex w-full max-w-[600px] flex-col gap-6 px-4 py-6">
<div className="container flex max-w-6xl flex-col gap-6 py-10">
<VariantTest storyLabel="5 items" listLen={5} />
<VariantTest storyLabel="5000 items" listLen={5000} />
<VariantTest
@@ -23,6 +23,7 @@ export function WalletAddress(props: {
address: string | undefined;
shortenAddress?: boolean;
className?: string;
iconClassName?: string;
}) {
const thirdwebClient = useThirdwebClient();
// default back to zero address if no address provided
@@ -78,6 +79,7 @@ export function WalletAddress(props: {
address={address}
profiles={profiles.data || []}
thirdwebClient={thirdwebClient}
iconClassName={props.iconClassName}
/>
)}
<span className="cursor-pointer font-mono">
@@ -168,6 +170,7 @@ function WalletAvatar(props: {
address: string;
profiles: SocialProfile[];
thirdwebClient: ThirdwebClient;
iconClassName?: string;
}) {
const avatar = useMemo(() => {
return props.profiles.find(
@@ -186,11 +189,20 @@ function WalletAvatar(props: {
: undefined;
return (
<div className="size-6 overflow-hidden rounded-full">
<div
className={cn("size-6 overflow-hidden rounded-full", props.iconClassName)}
>
{resolvedAvatarSrc ? (
<Img src={resolvedAvatarSrc} className="size-6 object-cover" />
<Img
src={resolvedAvatarSrc}
className={cn("size-6 object-cover", props.iconClassName)}
/>
) : (
<Blobbie address={props.address} size={24} />
<Blobbie
address={props.address}
size={24}
className={props.iconClassName}
/>
)}
</div>
);
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from "@storybook/react";
import { useState } from "react";
import { BadgeContainer, mobileViewport } from "../../stories/utils";
import { BadgeContainer } from "../../stories/utils";
import { PaginationButtons } from "./pagination-buttons";
const meta = {
@@ -16,17 +16,10 @@ const meta = {
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
export const Variants: Story = {
args: {},
};
export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};
function Story() {
return (
<div className="container flex max-w-[1000px] flex-col gap-8 py-10">
@@ -15,6 +15,7 @@ export type NavButtonProps = {
action: string;
label: string;
};
onClick?: () => void;
};
export function NavLink(props: React.PropsWithChildren<NavButtonProps>) {
@@ -30,7 +31,9 @@ export function NavLink(props: React.PropsWithChildren<NavButtonProps>) {
href={props.href}
className={cn(props.className, isActive && props.activeClassName)}
target={props.href.startsWith("http") ? "_blank" : undefined}
prefetch={false}
onClick={() => {
props.onClick?.();
if (props.tracking) {
track({
category: props.tracking.category,
@@ -96,7 +96,10 @@ const BreadcrumbEllipsis = ({
<span
role="presentation"
aria-hidden="true"
className={cn("flex h-9 w-9 items-center justify-center", className)}
className={cn(
"relative flex h-9 w-9 items-center justify-center",
className,
)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
@@ -6,9 +6,6 @@ import { BadgeContainer } from "../../../stories/utils";
const meta = {
title: "Shadcn/Buttons",
component: Component,
parameters: {
layout: "centered",
},
} satisfies Meta<typeof Component>;
export default meta;
+3 -1
View File
@@ -243,7 +243,9 @@ const ChartTooltipContent = React.forwardRef<
<div className="grid gap-1.5">
{nestLabel ? tooltipLabel : null}
<span className="text-muted-foreground">
{itemConfig?.label || item.name}
{item.name === "maxLimit"
? "Upper Limit"
: itemConfig?.label || item.name}
</span>
</div>
{item.value !== undefined && (
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react";
import { BadgeContainer, mobileViewport } from "stories/utils";
import { BadgeContainer } from "stories/utils";
import { CodeClient } from "./code.client";
const meta = {
@@ -11,17 +11,10 @@ const meta = {
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
export const Variants: Story = {
args: {},
};
export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};
const tsCode = `\
type User = {
name: string;
@@ -86,7 +79,7 @@ const bashCode = "pnpm i thirdweb";
function Component() {
return (
<div className="container flex max-w-[600px] flex-col gap-10 py-10">
<div className="container flex max-w-6xl flex-col gap-10 py-10">
<BadgeContainer label="ts">
<CodeClient code={tsCode} lang="ts" />
</BadgeContainer>
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react";
import { BadgeContainer, mobileViewport } from "stories/utils";
import { BadgeContainer } from "stories/utils";
import { PlainTextCodeBlock } from "./plaintext-code";
const meta = {
@@ -11,17 +11,10 @@ const meta = {
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
export const Variants: Story = {
args: {},
};
export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};
const jsCode = `\
import { getContract } from "thirdweb";
import { sepolia } from "thirdweb/chains";
@@ -39,7 +32,7 @@ const overflowText =
function Component() {
return (
<div className="container flex max-w-[600px] flex-col gap-10 py-10">
<div className="container flex max-w-6xl flex-col gap-10 py-10">
<BadgeContainer label="large chunk of code">
<PlainTextCodeBlock code={jsCode} />
</BadgeContainer>
@@ -94,7 +94,10 @@ const PaginationEllipsis = ({
}: React.ComponentProps<"span">) => (
<span
aria-hidden
className={cn("flex h-9 w-9 items-center justify-center", className)}
className={cn(
"relative flex h-9 w-9 items-center justify-center",
className,
)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
@@ -13,9 +13,6 @@ import { BadgeContainer } from "../../../stories/utils";
const meta = {
title: "Shadcn/Select",
component: Component,
parameters: {
layout: "centered",
},
} satisfies Meta<typeof Component>;
export default meta;
@@ -0,0 +1,773 @@
"use client";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
} from "@/components/ui/sheet";
import { Skeleton } from "@/components/ui/skeleton";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { useIsMobile } from "@/hooks/use-mobile";
import { cn } from "@/lib/utils";
import { Slot } from "@radix-ui/react-slot";
import { type VariantProps, cva } from "class-variance-authority";
import { PanelLeft } from "lucide-react";
import * as React from "react";
const SIDEBAR_COOKIE_NAME = "sidebar_state";
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
const SIDEBAR_WIDTH = "16rem";
const SIDEBAR_WIDTH_MOBILE = "18rem";
const SIDEBAR_WIDTH_ICON = "3rem";
const SIDEBAR_KEYBOARD_SHORTCUT = "b";
type SidebarContextProps = {
state: "expanded" | "collapsed";
open: boolean;
setOpen: (open: boolean) => void;
openMobile: boolean;
setOpenMobile: (open: boolean) => void;
isMobile: boolean;
toggleSidebar: () => void;
};
const SidebarContext = React.createContext<SidebarContextProps | null>(null);
function useSidebar() {
const context = React.useContext(SidebarContext);
if (!context) {
throw new Error("useSidebar must be used within a SidebarProvider.");
}
return context;
}
const SidebarProvider = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & {
defaultOpen?: boolean;
open?: boolean;
onOpenChange?: (open: boolean) => void;
}
>(
(
{
defaultOpen = true,
open: openProp,
onOpenChange: setOpenProp,
className,
style,
children,
...props
},
ref,
) => {
const isMobile = useIsMobile();
const [openMobile, setOpenMobile] = React.useState(false);
// This is the internal state of the sidebar.
// We use openProp and setOpenProp for control from outside the component.
const [_open, _setOpen] = React.useState(defaultOpen);
const open = openProp ?? _open;
const setOpen = React.useCallback(
(value: boolean | ((value: boolean) => boolean)) => {
const openState = typeof value === "function" ? value(open) : value;
if (setOpenProp) {
setOpenProp(openState);
} else {
_setOpen(openState);
}
// This sets the cookie to keep the sidebar state.
// eslint-disable-next-line react-compiler/react-compiler
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
},
[setOpenProp, open],
);
// Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => {
return isMobile
? setOpenMobile((open) => !open)
: setOpen((open) => !open);
}, [isMobile, setOpen]);
// Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (
event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
(event.metaKey || event.ctrlKey)
) {
event.preventDefault();
toggleSidebar();
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [toggleSidebar]);
// We add a state so that we can do data-state="expanded" or "collapsed".
// This makes it easier to style the sidebar with Tailwind classes.
const state = open ? "expanded" : "collapsed";
const contextValue = React.useMemo<SidebarContextProps>(
() => ({
state,
open,
setOpen,
isMobile,
openMobile,
setOpenMobile,
toggleSidebar,
}),
[state, open, setOpen, isMobile, openMobile, toggleSidebar],
);
return (
<SidebarContext.Provider value={contextValue}>
<TooltipProvider delayDuration={0}>
<div
style={
{
"--sidebar-width": SIDEBAR_WIDTH,
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
...style,
} as React.CSSProperties
}
className={cn(
"group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar",
className,
)}
ref={ref}
{...props}
>
{children}
</div>
</TooltipProvider>
</SidebarContext.Provider>
);
},
);
SidebarProvider.displayName = "SidebarProvider";
const Sidebar = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & {
side?: "left" | "right";
variant?: "sidebar" | "floating" | "inset";
collapsible?: "offcanvas" | "icon" | "none";
}
>(
(
{
side = "left",
variant = "sidebar",
collapsible = "offcanvas",
className,
children,
...props
},
ref,
) => {
const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
if (collapsible === "none") {
return (
<div
className={cn(
"flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground",
className,
)}
ref={ref}
{...props}
>
{children}
</div>
);
}
if (isMobile) {
return (
<Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
<SheetContent
data-sidebar="sidebar"
data-mobile="true"
className="w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
style={
{
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
} as React.CSSProperties
}
side={side}
>
<SheetHeader className="sr-only">
<SheetTitle>Sidebar</SheetTitle>
<SheetDescription>Displays the mobile sidebar.</SheetDescription>
</SheetHeader>
<div className="flex h-full w-full flex-col">{children}</div>
</SheetContent>
</Sheet>
);
}
return (
<div
ref={ref}
className="group peer relative hidden flex-shrink-0 text-sidebar-foreground md:block"
data-state={state}
data-collapsible={state === "collapsed" ? collapsible : ""}
data-variant={variant}
data-side={side}
>
{/* This is what handles the sidebar gap on desktop */}
<div
className={cn(
"relative w-[--sidebar-width] bg-transparent transition-[width] duration-200 ease-linear",
"group-data-[collapsible=offcanvas]:w-0",
"group-data-[side=right]:rotate-180",
variant === "floating" || variant === "inset"
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
: "group-data-[collapsible=icon]:w-[--sidebar-width-icon]",
)}
/>
<div
className={cn(
"absolute inset-y-0 z-10 hidden h-full w-[--sidebar-width] transition-[left,right,width] duration-200 ease-linear md:flex",
side === "left"
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
// Adjust the padding for floating and inset variants.
variant === "floating" || variant === "inset"
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]"
: "group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l",
className,
)}
{...props}
>
<div
data-sidebar="sidebar"
className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow"
>
{children}
</div>
</div>
</div>
);
},
);
Sidebar.displayName = "Sidebar";
const SidebarTrigger = React.forwardRef<
React.ElementRef<typeof Button>,
React.ComponentProps<typeof Button>
>(({ className, onClick, ...props }, ref) => {
const { toggleSidebar } = useSidebar();
return (
<Button
ref={ref}
data-sidebar="trigger"
variant="ghost"
size="icon"
className={cn("h-7 w-7", className)}
onClick={(event) => {
onClick?.(event);
toggleSidebar();
}}
{...props}
>
<PanelLeft />
<span className="sr-only">Toggle Sidebar</span>
</Button>
);
});
SidebarTrigger.displayName = "SidebarTrigger";
const SidebarRail = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button">
>(({ className, ...props }, ref) => {
const { toggleSidebar } = useSidebar();
return (
<button
ref={ref}
data-sidebar="rail"
aria-label="Toggle Sidebar"
tabIndex={-1}
onClick={toggleSidebar}
title="Toggle Sidebar"
className={cn(
"-translate-x-1/2 group-data-[side=left]:-right-4 absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=right]:left-0 sm:flex",
"[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize",
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
"group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:hover:bg-sidebar group-data-[collapsible=offcanvas]:after:left-full",
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
className,
)}
{...props}
/>
);
});
SidebarRail.displayName = "SidebarRail";
const SidebarInset = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"main">
>(({ className, ...props }, ref) => {
return (
<main
ref={ref}
className={cn(
"relative flex w-full flex-1 flex-col bg-background",
"md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
className,
)}
{...props}
/>
);
});
SidebarInset.displayName = "SidebarInset";
const SidebarInput = React.forwardRef<
React.ElementRef<typeof Input>,
React.ComponentProps<typeof Input>
>(({ className, ...props }, ref) => {
return (
<Input
ref={ref}
data-sidebar="input"
className={cn(
"h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring",
className,
)}
{...props}
/>
);
});
SidebarInput.displayName = "SidebarInput";
const SidebarHeader = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => {
return (
<div
ref={ref}
data-sidebar="header"
className={cn("flex flex-col gap-2 p-2", className)}
{...props}
/>
);
});
SidebarHeader.displayName = "SidebarHeader";
const SidebarFooter = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => {
return (
<div
ref={ref}
data-sidebar="footer"
className={cn("flex flex-col gap-2 p-2", className)}
{...props}
/>
);
});
SidebarFooter.displayName = "SidebarFooter";
const SidebarSeparator = React.forwardRef<
React.ElementRef<typeof Separator>,
React.ComponentProps<typeof Separator>
>(({ className, ...props }, ref) => {
return (
<Separator
ref={ref}
data-sidebar="separator"
className={cn("mx-2 w-auto bg-sidebar-border", className)}
{...props}
/>
);
});
SidebarSeparator.displayName = "SidebarSeparator";
const SidebarContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => {
return (
<div
ref={ref}
data-sidebar="content"
className={cn(
"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
className,
)}
{...props}
/>
);
});
SidebarContent.displayName = "SidebarContent";
const SidebarGroup = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => {
return (
<div
ref={ref}
data-sidebar="group"
className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
{...props}
/>
);
});
SidebarGroup.displayName = "SidebarGroup";
const SidebarGroupLabel = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "div";
return (
<Comp
ref={ref}
data-sidebar="group-label"
className={cn(
"flex h-8 shrink-0 items-center rounded-md px-2 font-medium text-sidebar-foreground/70 text-xs outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
className,
)}
{...props}
/>
);
});
SidebarGroupLabel.displayName = "SidebarGroupLabel";
const SidebarGroupAction = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button"> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
ref={ref}
data-sidebar="group-action"
className={cn(
"absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile.
"after:-inset-2 after:absolute after:md:hidden",
"group-data-[collapsible=icon]:hidden",
className,
)}
{...props}
/>
);
});
SidebarGroupAction.displayName = "SidebarGroupAction";
const SidebarGroupContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
<div
ref={ref}
data-sidebar="group-content"
className={cn("w-full text-sm", className)}
{...props}
/>
));
SidebarGroupContent.displayName = "SidebarGroupContent";
const SidebarMenu = React.forwardRef<
HTMLUListElement,
React.ComponentProps<"ul">
>(({ className, ...props }, ref) => (
<ul
ref={ref}
data-sidebar="menu"
className={cn("flex w-full min-w-0 flex-col gap-1", className)}
{...props}
/>
));
SidebarMenu.displayName = "SidebarMenu";
const SidebarMenuItem = React.forwardRef<
HTMLLIElement,
React.ComponentProps<"li">
>(({ className, ...props }, ref) => (
<li
ref={ref}
data-sidebar="menu-item"
className={cn("group/menu-item relative", className)}
{...props}
/>
));
SidebarMenuItem.displayName = "SidebarMenuItem";
const sidebarMenuButtonVariants = cva(
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
{
variants: {
variant: {
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
outline:
"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
},
size: {
default: "h-8 text-sm",
sm: "h-7 text-xs",
lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
const SidebarMenuButton = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button"> & {
asChild?: boolean;
isActive?: boolean;
tooltip?: string | React.ComponentProps<typeof TooltipContent>;
} & VariantProps<typeof sidebarMenuButtonVariants>
>(
(
{
asChild = false,
isActive = false,
variant = "default",
size = "default",
tooltip,
className,
...props
},
ref,
) => {
const Comp = asChild ? Slot : "button";
const { isMobile, state } = useSidebar();
const button = (
<Comp
ref={ref}
data-sidebar="menu-button"
data-size={size}
data-active={isActive}
className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
{...props}
/>
);
if (!tooltip) {
return button;
}
if (typeof tooltip === "string") {
tooltip = {
children: tooltip,
};
}
return (
<Tooltip>
<TooltipTrigger asChild>{button}</TooltipTrigger>
<TooltipContent
side="right"
align="center"
hidden={state !== "collapsed" || isMobile}
{...tooltip}
/>
</Tooltip>
);
},
);
SidebarMenuButton.displayName = "SidebarMenuButton";
const SidebarMenuAction = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button"> & {
asChild?: boolean;
showOnHover?: boolean;
}
>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
ref={ref}
data-sidebar="menu-action"
className={cn(
"absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile.
"after:-inset-2 after:absolute after:md:hidden",
"peer-data-[size=sm]/menu-button:top-1",
"peer-data-[size=default]/menu-button:top-1.5",
"peer-data-[size=lg]/menu-button:top-2.5",
"group-data-[collapsible=icon]:hidden",
showOnHover &&
"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
className,
)}
{...props}
/>
);
});
SidebarMenuAction.displayName = "SidebarMenuAction";
const SidebarMenuBadge = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
<div
ref={ref}
data-sidebar="menu-badge"
className={cn(
"pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 font-medium text-sidebar-foreground text-xs tabular-nums",
"peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
"peer-data-[size=sm]/menu-button:top-1",
"peer-data-[size=default]/menu-button:top-1.5",
"peer-data-[size=lg]/menu-button:top-2.5",
"group-data-[collapsible=icon]:hidden",
className,
)}
{...props}
/>
));
SidebarMenuBadge.displayName = "SidebarMenuBadge";
const SidebarMenuSkeleton = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & {
showIcon?: boolean;
}
>(({ className, showIcon = false, ...props }, ref) => {
// Random width between 50 to 90%.
const width = React.useMemo(() => {
return `${Math.floor(Math.random() * 40) + 50}%`;
}, []);
return (
<div
ref={ref}
data-sidebar="menu-skeleton"
className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)}
{...props}
>
{showIcon && (
<Skeleton
className="size-4 rounded-md"
data-sidebar="menu-skeleton-icon"
/>
)}
<Skeleton
className="h-4 max-w-[--skeleton-width] flex-1"
data-sidebar="menu-skeleton-text"
style={
{
"--skeleton-width": width,
} as React.CSSProperties
}
/>
</div>
);
});
SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton";
const SidebarMenuSub = React.forwardRef<
HTMLUListElement,
React.ComponentProps<"ul">
>(({ className, ...props }, ref) => (
<ul
ref={ref}
data-sidebar="menu-sub"
className={cn(
"mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-sidebar-border border-l px-2.5 py-0.5",
"group-data-[collapsible=icon]:hidden",
className,
)}
{...props}
/>
));
SidebarMenuSub.displayName = "SidebarMenuSub";
const SidebarMenuSubItem = React.forwardRef<
HTMLLIElement,
React.ComponentProps<"li">
>(({ ...props }, ref) => <li ref={ref} {...props} />);
SidebarMenuSubItem.displayName = "SidebarMenuSubItem";
const SidebarMenuSubButton = React.forwardRef<
HTMLAnchorElement,
React.ComponentProps<"a"> & {
asChild?: boolean;
size?: "sm" | "md";
isActive?: boolean;
}
>(({ asChild = false, size = "md", isActive, className, ...props }, ref) => {
const Comp = asChild ? Slot : "a";
return (
<Comp
ref={ref}
data-sidebar="menu-sub-button"
data-size={size}
data-active={isActive}
className={cn(
"-translate-x-px flex h-7 min-w-0 items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
size === "sm" && "text-xs",
size === "md" && "text-sm",
"group-data-[collapsible=icon]:hidden",
className,
)}
{...props}
/>
);
});
SidebarMenuSubButton.displayName = "SidebarMenuSubButton";
export {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupAction,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInput,
SidebarInset,
SidebarMenu,
SidebarMenuAction,
SidebarMenuBadge,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSkeleton,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarProvider,
SidebarRail,
SidebarSeparator,
SidebarTrigger,
useSidebar,
};
@@ -10,35 +10,25 @@ import {
} from "@/components/ui/table";
import type { Meta, StoryObj } from "@storybook/react";
import Link from "next/link";
import { BadgeContainer, mobileViewport } from "../../../stories/utils";
import { BadgeContainer } from "../../../stories/utils";
import { cn } from "../../lib/utils";
import { Badge } from "./badge";
const meta = {
title: "Shadcn/Table",
component: Component,
parameters: {
layout: "centered",
},
} satisfies Meta<typeof Component>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
export const Variants: Story = {
args: {},
};
export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};
function Component() {
return (
<div className="flex min-w-0 flex-col gap-6 px-4 py-6 lg:mx-auto lg:max-w-[1000px]">
<div className="container flex max-w-6xl flex-col gap-10 py-10">
<BadgeContainer label="Normal">
<TableDemo />
</BadgeContainer>
+32 -21
View File
@@ -7,6 +7,7 @@ import { usePathname } from "next/navigation";
import { useCallback, useRef, useState } from "react";
import { ScrollShadow } from "./ScrollShadow/ScrollShadow";
import { Button } from "./button";
import { ToolTipLabel } from "./tooltip";
export type TabLink = {
name: string;
@@ -20,6 +21,7 @@ export function TabLinks(props: {
className?: string;
tabContainerClassName?: string;
shadowColor?: string;
scrollableClassName?: string;
}) {
const { containerRef, lineRef, activeTabRef } =
useUnderline<HTMLAnchorElement>();
@@ -30,7 +32,7 @@ export function TabLinks(props: {
<div className="absolute right-0 bottom-0 left-0 h-[1px] bg-border" />
<ScrollShadow
scrollableClassName="pb-[8px] relative"
scrollableClassName={cn("pb-[8px] relative", props.scrollableClassName)}
shadowColor={props.shadowColor}
>
<div
@@ -81,6 +83,7 @@ export function TabButtons(props: {
isActive: boolean;
isDisabled?: boolean;
icon?: React.FC<{ className?: string }>;
toolTip?: string;
}[];
tabClassName?: string;
activeTabClassName?: string;
@@ -110,26 +113,31 @@ export function TabButtons(props: {
>
{props.tabs.map((tab, index) => {
return (
<Button
// biome-ignore lint/suspicious/noArrayIndexKey: tabs don't change order, so index is stable
key={index}
variant="ghost"
ref={tab.isActive ? activeTabRef : undefined}
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",
!tab.isActive &&
"text-muted-foreground hover:text-foreground",
tab.isDisabled && "cursor-not-allowed opacity-50",
props.tabClassName,
tab.isActive && props.activeTabClassName,
)}
onClick={!tab.isDisabled ? tab.onClick : undefined}
<ToolTipLabel
key={typeof tab.name === "string" ? tab.name : index}
label={tab.toolTip}
>
{tab.icon && (
<tab.icon className={cn("size-6", props.tabIconClassName)} />
)}
{tab.name}
</Button>
<Button
variant="ghost"
ref={tab.isActive ? activeTabRef : undefined}
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",
!tab.isActive &&
"text-muted-foreground hover:text-foreground",
tab.isDisabled && "cursor-not-allowed opacity-50",
props.tabClassName,
tab.isActive && props.activeTabClassName,
)}
onClick={!tab.isDisabled ? tab.onClick : undefined}
>
{tab.icon && (
<tab.icon
className={cn("size-6", props.tabIconClassName)}
/>
)}
{tab.name}
</Button>
</ToolTipLabel>
);
})}
</div>
@@ -159,9 +167,11 @@ function useUnderline<El extends HTMLElement>() {
const containerRect = containerRef.current.getBoundingClientRect();
const lineEl = lineRef.current;
const rect = activeTabEl.getBoundingClientRect();
const containerPaddingLeft =
containerRect.left - containerRef.current.offsetLeft;
lineEl.style.width = `${rect.width}px`;
lineEl.style.transform = `translateX(${
rect.left - containerRect.left
rect.left - containerPaddingLeft
}px)`;
setTimeout(() => {
lineEl.style.transition = "transform 0.3s, width 0.3s";
@@ -205,6 +215,7 @@ export function TabPathLinks(props: {
className?: string;
tabContainerClassName?: string;
shadowColor?: string;
scrollableClassName?: string;
}) {
const pathname = usePathname() || "";
const { links, ...restProps } = props;
+21
View File
@@ -0,0 +1,21 @@
import * as React from "react";
const MOBILE_BREAKPOINT = 768;
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
undefined,
);
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
};
mql.addEventListener("change", onChange);
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
return () => mql.removeEventListener("change", onChange);
}, []);
return !!isMobile;
}
+2
View File
@@ -3,6 +3,8 @@ export type ProductSKU =
| "plan:starter"
| "plan:growth"
| "plan:custom"
| "plan:accelerate"
| "plan:scale"
| "product:ecosystem_wallets"
| "product:engine_standard"
| "product:engine_premium"
@@ -15,8 +15,6 @@ import invariant from "tiny-invariant";
import type { EngineStatus } from "../../../app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/transactions-table";
import { engineKeys } from "../cache-keys";
export type EngineTier = "STARTER" | "PREMIUM" | "ENTERPRISE";
// Engine instances
export type EngineInstance = {
id: string;
@@ -24,6 +22,8 @@ export type EngineInstance = {
name: string;
url: string;
lastAccessedAt: string;
isCloudHosted: boolean;
isPlanEngine: boolean;
status:
| "active"
| "pending"
@@ -5,6 +5,7 @@ import invariant from "tiny-invariant";
export function useWalletNFTs(params: {
chainId: number;
walletAddress?: string;
isInsightSupported: boolean;
}) {
return useQuery({
queryKey: ["walletNfts", params.chainId, params.walletAddress],
@@ -13,6 +14,7 @@ export function useWalletNFTs(params: {
return getWalletNFTs({
chainId: params.chainId,
owner: params.walletAddress,
isInsightSupported: params.isInsightSupported,
});
},
enabled: !!params.walletAddress,
@@ -0,0 +1,25 @@
"use client";
import { getThirdwebClient } from "@/constants/thirdweb.server";
import { getSDKTheme } from "app/components/sdk-component-theme";
import { useV5DashboardChain } from "lib/v5-adapter";
import { useTheme } from "next-themes";
import { PayEmbed } from "thirdweb/react";
export function UniversalBridgeEmbed({ chainId }: { chainId?: number }) {
const { theme } = useTheme();
const chain = useV5DashboardChain(chainId || 1);
return (
<PayEmbed
client={getThirdwebClient()}
payOptions={{
mode: "fund_wallet",
prefillBuy: {
chain,
amount: "0.01",
},
}}
theme={getSDKTheme(theme === "light" ? "light" : "dark")}
/>
);
}
@@ -0,0 +1,35 @@
import type { Metadata } from "next";
import { UniversalBridgeEmbed } from "./components/client/UniversalBridgeEmbed";
const title = "Universal Bridge: Swap, Bridge, and On-Ramp";
const description =
"Swap, bridge, and on-ramp to any EVM chain with thirdweb's Universal Bridge.";
export const metadata: Metadata = {
title,
description,
openGraph: {
title,
description,
},
};
export default async function RoutesPage({
searchParams,
}: { searchParams: Record<string, string | string[]> }) {
const { chainId } = searchParams;
return (
<div className="relative mx-auto flex h-screen w-full flex-col items-center justify-center overflow-hidden border py-10">
<main className="container z-10 flex justify-center">
<UniversalBridgeEmbed chainId={chainId ? Number(chainId) : undefined} />
</main>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
alt=""
src="/assets/login/background.svg"
className="-bottom-12 -right-12 pointer-events-none absolute lg:right-0 lg:bottom-0"
/>
</div>
);
}
@@ -47,7 +47,7 @@ export const SearchInput: React.FC = () => {
<div className="group relative w-full">
<SearchIcon className="-translate-y-1/2 absolute top-[50%] left-3 size-4 text-muted-foreground" />
<Input
placeholder="Search by token address or chain ID"
placeholder="Search with anything!"
className="h-10 rounded-lg bg-card py-2 pl-9 lg:min-w-[300px]"
defaultValue={searchParams?.get("query") || ""}
onChange={(e) => handleSearch(e.target.value)}
@@ -7,6 +7,7 @@ import {
TableRow,
} from "@/components/ui/table";
import type { Address } from "thirdweb";
import { checksumAddress } from "thirdweb/utils";
import { getRoutes } from "../../../utils";
import { ChainlistPagination } from "../client/pagination";
import { RouteListCard } from "../server/routelist-card";
@@ -34,28 +35,83 @@ async function getRoutesToRender(params: SearchParams) {
originTokenAddress?: Address;
destinationChainId?: number;
destinationTokenAddress?: Address;
originTextQuery?: string;
destinationTextQuery?: string;
}> = {};
if (params.type === "origin" || typeof params.type === "undefined") {
if (params.query?.startsWith("0x")) {
filters.originTokenAddress = params.query as Address;
} else if (params.query) {
} else if (Number.isInteger(Number(params.query))) {
filters.originChainId = Number(params.query);
} else if (params.query) {
filters.originTextQuery = params.query;
}
} else if (params.type === "destination") {
if (params.query?.startsWith("0x")) {
filters.destinationTokenAddress = params.query as Address;
} else if (params.query) {
} else if (Number.isInteger(Number(params.query))) {
filters.destinationChainId = Number(params.query);
} else if (params.query) {
filters.destinationTextQuery = params.query;
}
}
// Temporary, will update this after the /routes endpoint
filters.limit = 50_000;
const routes = await getRoutes(filters);
let routes = await getRoutes({ limit: 100_000 });
const totalCount = routes.length;
if (filters.originChainId) {
routes = routes.filter(
(route) => route.originToken.chainId === filters.originChainId,
);
}
if (filters.originTokenAddress) {
const originTokenAddress = filters.originTokenAddress;
routes = routes.filter(
(route) =>
checksumAddress(route.originToken.address) ===
checksumAddress(originTokenAddress),
);
}
if (filters.destinationChainId) {
routes = routes.filter(
(route) => route.destinationToken.chainId === filters.destinationChainId,
);
}
if (filters.destinationTokenAddress) {
const destinationTokenAddress = filters.destinationTokenAddress;
routes = routes.filter(
(route) =>
checksumAddress(route.destinationToken.address) ===
checksumAddress(destinationTokenAddress),
);
}
if (filters.originTextQuery) {
const originTextQuery = filters.originTextQuery.toLowerCase();
routes = routes.filter((route) => {
return (
route.originToken.name.toLowerCase().includes(originTextQuery) ||
route.originToken.symbol.toLowerCase().includes(originTextQuery)
);
});
}
if (filters.destinationTextQuery) {
const destinationTextQuery = filters.destinationTextQuery.toLowerCase();
routes = routes.filter((route) => {
return (
route.destinationToken.name
.toLowerCase()
.includes(destinationTextQuery) ||
route.destinationToken.symbol
.toLowerCase()
.includes(destinationTextQuery)
);
});
}
return {
routesToRender: routes,
totalCount,
@@ -154,13 +210,21 @@ export async function RoutesData(props: {
out of{" "}
{filteredCount !== totalCount ? (
<>
<span className="text-accent-foreground">{filteredCount}</span>{" "}
<span className="text-accent-foreground">
{filteredCount.toLocaleString()}
</span>{" "}
routes that match filters. (Total:{" "}
<span className="text-accent-foreground">{totalCount}</span>)
<span className="text-accent-foreground">
{totalCount.toLocaleString()}
</span>
)
</>
) : (
<>
<span className="text-accent-foreground">{totalCount}</span> routes.
<span className="text-accent-foreground">
{totalCount.toLocaleString()}
</span>{" "}
routes.
</>
)}
</p>
@@ -2,43 +2,18 @@ import "server-only";
import { BRIDGE_URL, DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/env";
import { getAuthToken } from "app/api/lib/getAuthToken";
import type { Address } from "thirdweb";
import type { Route } from "./types/route";
export async function getRoutes({
limit,
offset,
originChainId,
originTokenAddress,
destinationChainId,
destinationTokenAddress,
}: {
limit?: number;
offset?: number;
originChainId?: number;
originTokenAddress?: Address;
destinationChainId?: number;
destinationTokenAddress?: Address;
} = {}) {
const url = new URL(`${BRIDGE_URL}/v1/routes`);
if (limit) {
url.searchParams.set("limit", limit.toString());
}
if (offset) {
url.searchParams.set("offset", offset.toString());
}
if (originChainId) {
url.searchParams.set("originChainId", originChainId.toString());
}
if (originTokenAddress) {
url.searchParams.set("originTokenAddress", originTokenAddress);
}
if (destinationChainId) {
url.searchParams.set("destinationChainId", destinationChainId.toString());
}
if (destinationTokenAddress) {
url.searchParams.set("destinationTokenAddress", destinationTokenAddress);
}
// It's faster to filter client side, doesn't seem to be a big performance boost to paginate/filter server side
const token = await getAuthToken();
const routesResponse = await fetch(url, {
headers: token
@@ -0,0 +1,46 @@
"use client";
import { useTrack } from "hooks/analytics/useTrack";
import { FileText } from "lucide-react";
import Link from "next/link";
import type { ChainMetadata } from "thirdweb/chains";
import { SectionTitle } from "../server/SectionTitle";
export default function NextSteps(props: { chain: ChainMetadata }) {
const { chain } = props;
const trackEvent = useTrack();
return (
<section>
<SectionTitle title="Next Steps" />
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3">
<div className="relative flex gap-3 rounded-lg border bg-card p-4 pr-8 transition-colors hover:border-active-border">
<FileText className="mt-0.5 size-5 shrink-0" />
<div>
<h3 className="mb-1.5 font-medium">
<Link
href={
"https://blog.thirdweb.com/supercharge-user-adoption-integrate-embedded-wallets-in-minutes/"
}
className="before:absolute before:inset-0"
target="_blank"
onClick={() =>
trackEvent({
category: "nextSteps",
action: "click-inapp",
label: "success",
chain_id: chain.chainId,
})
}
>
Create a login for {chain.name}
</Link>
</h3>
<p className="text-muted-foreground text-sm">
Supercharge User Adoption—Integrate In-App Wallets in Minutes
</p>
</div>
</div>
</div>
</section>
);
}
@@ -11,7 +11,7 @@ export function BuyFundsSection(props: { chain: ChainMetadata }) {
return (
<section>
<SectionTitle title="Buy Funds" />
<SectionTitle title="Bridge" />
<div className="flex justify-center rounded-lg border border-border bg-card px-4 py-10">
<div className="flex max-w-[520px] flex-col items-center ">
<div className="flex items-center">
@@ -28,21 +28,21 @@ export function BuyFundsSection(props: { chain: ChainMetadata }) {
<div className="h-6" />
<h2 className="px-4 text-center font-semibold text-lg tracking-tight">
Buy Funds on {sanitizedChainName} using thirdweb Pay
Bridge Funds to {sanitizedChainName}
</h2>
<div className="h-2" />
<p className="max-w-[520px] px-4 text-center text-muted-foreground text-sm">
Instantly onramp, bridge & swap into {sanitizedChainName} with any
token.
Get {props.chain.nativeCurrency.symbol} on {sanitizedChainName} with
fiat or any token on another chain.
</p>
<div className="h-8" />
<PayModalButton
chainId={props.chain.chainId}
label={`Buy Funds on ${sanitizedChainName}`}
label={`Bridge to ${sanitizedChainName}`}
/>
<div className="h-4" />
@@ -50,8 +50,9 @@ export function BuyFundsSection(props: { chain: ChainMetadata }) {
<Link
href="https://portal.thirdweb.com/connect/pay/overview"
className="inline-flex items-center gap-1.5 text-muted-foreground text-sm hover:text-foreground"
target="_blank"
>
Learn more about thirdweb Pay
Learn more about Universal Bridge
<ExternalLinkIcon className="size-3" />
</Link>
</div>
@@ -13,12 +13,18 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { getThirdwebClient } from "@/constants/thirdweb.server";
import { ChevronDownIcon, TicketCheckIcon } from "lucide-react";
import type { Metadata } from "next";
import Link from "next/link";
import { redirect } from "next/navigation";
import { mapV4ChainToV5Chain } from "../../../../../contexts/map-chains";
import { getAuthToken } from "../../../../api/lib/getAuthToken";
import { getRawAccount } from "../../../../account/settings/getAccount";
import {
getAuthToken,
getAuthTokenWalletAddress,
} from "../../../../api/lib/getAuthToken";
import { NebulaFloatingChatButton } from "../../../../nebula-app/(app)/components/FloatingChat/FloatingChat";
import { StarButton } from "../../components/client/star-button";
import { getChain, getChainMetadata } from "../../utils";
import { AddChainToWallet } from "./components/client/add-chain-to-wallet";
@@ -55,17 +61,58 @@ export default async function ChainPageLayout(props: {
}) {
const params = await props.params;
const { children } = props;
const chain = await getChain(params.chain_id);
const authToken = await getAuthToken();
const [chain, authToken, account, accountAddress] = await Promise.all([
getChain(params.chain_id),
getAuthToken(),
getRawAccount(),
getAuthTokenWalletAddress(),
]);
if (params.chain_id !== chain.slug) {
redirect(chain.slug);
}
const chainMetadata = await getChainMetadata(chain.chainId);
const client = getThirdwebClient(authToken ?? undefined);
const chainPromptPrefix = `\
You are assisting users exploring the chain ${chain.name} (Chain ID: ${chain.chainId}). Provide concise insights into the types of applications and activities prevalent on this chain, such as DeFi protocols, NFT marketplaces, or gaming platforms. Highlight notable projects or trends without delving into technical details like consensus mechanisms or gas fees.
Users may seek comparisons between ${chain.name} and other chains. Provide objective, succinct comparisons focusing on performance, fees, and ecosystem support. Refrain from transaction-specific advice unless requested.
Provide users with an understanding of the unique use cases and functionalities that ${chain.name} supports. Discuss how developers leverage this chain for specific applications, such as scalable dApps, low-cost transactions, or specialized token standards, focusing on practical implementations.
Users may be interested in utilizing thirdweb tools on ${chain.name}. Offer clear guidance on how thirdweb's SDKs, smart contract templates, and deployment tools integrate with this chain. Emphasize the functionalities enabled by thirdweb without discussing transaction execution unless prompted.
Avoid transaction-related actions to be executed by the user unless inquired about.
The following is the user's message:
`;
const examplePrompts: string[] = [
"What are users doing on this chain?",
"What are the most active contracts?",
"Why would I use this chain over others?",
"Can I deploy thirdweb contracts to this chain?",
];
if (chain.chainId !== 1) {
examplePrompts.push("Can I bridge assets from Ethereum to this chain?");
}
return (
<>
<NebulaFloatingChatButton
authToken={authToken ?? undefined}
account={account}
label="Ask AI about this chain"
client={client}
nebulaParams={{
messagePrefix: chainPromptPrefix,
chainIds: [chain.chainId],
wallet: accountAddress ?? undefined,
}}
examplePrompts={examplePrompts.map((prompt) => ({
title: prompt,
message: prompt,
}))}
/>
<div className="flex h-14 border-border border-b pl-7">
<Breadcrumb className="my-auto">
<BreadcrumbList>
@@ -1,6 +1,7 @@
import { CircleAlertIcon } from "lucide-react";
import { getRawAccount } from "../../../../account/settings/getAccount";
import { getChain, getChainMetadata } from "../../utils";
import NextSteps from "./components/client/NextSteps";
import { BuyFundsSection } from "./components/server/BuyFundsSection";
import { ChainOverviewSection } from "./components/server/ChainOverviewSection";
import { ClaimChainSection } from "./components/server/ClaimChainSection";
@@ -57,6 +58,8 @@ export default async function Page(props: {
{chain.services.filter((s) => s.enabled).length > 0 && (
<SupportedProductsSection services={chain.services} />
)}
{/*Next Steps */}
<NextSteps chain={chain} />
{/* Claim Chain */}
{!chainMetadata && <ClaimChainSection />}
@@ -13,7 +13,6 @@ import { ListerOnly } from "@3rdweb-sdk/react/components/roles/lister-only";
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
import { isAlchemySupported } from "lib/wallet/nfts/alchemy";
import { isMoralisSupported } from "lib/wallet/nfts/moralis";
import { useSimplehashSupport } from "lib/wallet/nfts/simpleHash";
import { PlusIcon } from "lucide-react";
import { useState } from "react";
import type { ThirdwebContract } from "thirdweb";
@@ -25,6 +24,7 @@ interface CreateListingButtonProps {
createText?: string;
type?: "direct-listings" | "english-auctions";
twAccount: Account | undefined;
isInsightSupported: boolean;
}
const LISTING_MODES = ["Select NFT", "Manual"] as const;
@@ -34,6 +34,7 @@ export const CreateListingButton: React.FC<CreateListingButtonProps> = ({
type,
contract,
twAccount,
isInsightSupported,
...restButtonProps
}) => {
const address = useActiveAccount()?.address;
@@ -41,13 +42,12 @@ export const CreateListingButton: React.FC<CreateListingButtonProps> = ({
const [listingMode, setListingMode] =
useState<(typeof LISTING_MODES)[number]>("Select NFT");
const simplehashQuery = useSimplehashSupport(contract.chain.id);
const isSupportedChain =
contract.chain.id &&
(simplehashQuery.data ||
(isInsightSupported ||
isAlchemySupported(contract.chain.id) ||
isMoralisSupported(contract.chain.id));
return (
<ListerOnly contract={contract}>
<Sheet open={open} onOpenChange={setOpen}>
@@ -83,6 +83,7 @@ export const CreateListingButton: React.FC<CreateListingButtonProps> = ({
actionText={createText}
setOpen={setOpen}
mode={listingMode === "Select NFT" ? "automatic" : "manual"}
isInsightSupported={isInsightSupported}
/>
</div>
</>
@@ -95,6 +96,7 @@ export const CreateListingButton: React.FC<CreateListingButtonProps> = ({
actionText={createText}
setOpen={setOpen}
mode="manual"
isInsightSupported={isInsightSupported}
/>
</div>
)}
@@ -21,7 +21,6 @@ import { useAllChainsData } from "hooks/chains/allChains";
import { useTxNotifications } from "hooks/useTxNotifications";
import { isAlchemySupported } from "lib/wallet/nfts/alchemy";
import { isMoralisSupported } from "lib/wallet/nfts/moralis";
import { useSimplehashSupport } from "lib/wallet/nfts/simpleHash";
import type { WalletNFT } from "lib/wallet/nfts/types";
import { CircleAlertIcon, InfoIcon } from "lucide-react";
import Link from "next/link";
@@ -87,6 +86,7 @@ type CreateListingsFormProps = {
mode: "automatic" | "manual";
type?: "direct-listings" | "english-auctions";
twAccount: Account | undefined;
isInsightSupported: boolean;
};
const auctionTimes = [
@@ -106,16 +106,17 @@ export const CreateListingsForm: React.FC<CreateListingsFormProps> = ({
setOpen,
twAccount,
mode,
isInsightSupported,
}) => {
const trackEvent = useTrack();
const chainId = contract.chain.id;
const { idToChain } = useAllChainsData();
const network = idToChain.get(chainId);
const [isFormLoading, setIsFormLoading] = useState(false);
const simplehashQuery = useSimplehashSupport(contract.chain.id);
const isSupportedChain =
chainId &&
(!!simplehashQuery.data ||
(isInsightSupported ||
isAlchemySupported(chainId) ||
isMoralisSupported(chainId));
@@ -124,7 +125,9 @@ export const CreateListingsForm: React.FC<CreateListingsFormProps> = ({
const { data: walletNFTs, isPending: isWalletNFTsLoading } = useWalletNFTs({
chainId,
walletAddress: account?.address,
isInsightSupported,
});
const sendAndConfirmTx = useSendAndConfirmTransaction();
const listingNotifications = useTxNotifications(
"NFT listed Successfully",
@@ -202,6 +205,8 @@ export const CreateListingsForm: React.FC<CreateListingsFormProps> = ({
owner: nft.owner,
type: "ERC721",
tokenURI: nft.tokenURI,
chainId: nft.chainId,
tokenAddress: nft.tokenAddress,
};
}
return {
@@ -213,6 +218,8 @@ export const CreateListingsForm: React.FC<CreateListingsFormProps> = ({
owner: nft.owner,
type: "ERC1155",
tokenURI: nft.tokenURI,
chainId: nft.chainId,
tokenAddress: nft.tokenAddress,
};
}) as WalletNFT[];
}, [ownedNFTs, form]);
@@ -29,6 +29,7 @@ export function ContractDirectListingsPageClient(props: {
<ContractDirectListingsPage
contract={props.contract}
twAccount={props.twAccount}
isInsightSupported={metadataQuery.data.isInsightSupported}
/>
);
}
@@ -8,17 +8,21 @@ import { DirectListingsTable } from "./components/table";
interface ContractDirectListingsPageProps {
contract: ThirdwebContract;
twAccount: Account | undefined;
isInsightSupported: boolean;
}
export const ContractDirectListingsPage: React.FC<
ContractDirectListingsPageProps
> = ({ contract, twAccount }) => {
> = ({ contract, twAccount, isInsightSupported }) => {
return (
<div className="flex flex-col gap-6">
<div className="flex flex-row items-center justify-between">
<p className="text-lg">Contract Listings</p>
<h2 className="font-semibold text-2xl tracking-tight">
Direct Listings
</h2>
<div className="flex flex-row gap-4">
<CreateListingButton
isInsightSupported={isInsightSupported}
contract={contract}
type="direct-listings"
createText="Create Direct Listing"
@@ -29,15 +29,18 @@ export default async function Page(props: {
);
}
const { isDirectListingSupported } = await getContractPageMetadata(
info.contract,
);
const { isDirectListingSupported, isInsightSupported } =
await getContractPageMetadata(info.contract);
if (!isDirectListingSupported) {
redirect(`/${params.chain_id}/${params.contractAddress}`);
}
return (
<ContractDirectListingsPage contract={info.contract} twAccount={account} />
<ContractDirectListingsPage
contract={info.contract}
twAccount={account}
isInsightSupported={isInsightSupported}
/>
);
}
@@ -29,6 +29,7 @@ export function ContractEnglishAuctionsPageClient(props: {
<ContractEnglishAuctionsPage
contract={props.contract}
twAccount={props.twAccount}
isInsightSupported={metadataQuery.data.isInsightSupported}
/>
);
}
@@ -8,21 +8,25 @@ import { EnglishAuctionsTable } from "./components/table";
interface ContractEnglishAuctionsProps {
contract: ThirdwebContract;
twAccount: Account | undefined;
isInsightSupported: boolean;
}
export const ContractEnglishAuctionsPage: React.FC<
ContractEnglishAuctionsProps
> = ({ contract, twAccount }) => {
> = ({ contract, twAccount, isInsightSupported }) => {
return (
<div className="flex flex-col gap-6">
<div className="flex flex-row items-center justify-between">
<p className="text-lg">Contract Auctions</p>
<h2 className="font-semibold text-2xl tracking-tight">
English Auctions
</h2>
<div className="flex flex-row gap-4">
<CreateListingButton
contract={contract}
type="english-auctions"
createText="Create English Auction"
twAccount={twAccount}
isInsightSupported={isInsightSupported}
/>
</div>
</div>
@@ -30,9 +30,8 @@ export default async function Page(props: {
);
}
const { isEnglishAuctionSupported } = await getContractPageMetadata(
info.contract,
);
const { isEnglishAuctionSupported, isInsightSupported } =
await getContractPageMetadata(info.contract);
if (!isEnglishAuctionSupported) {
redirect(`/${params.chain_id}/${params.contractAddress}`);
@@ -42,6 +41,7 @@ export default async function Page(props: {
<ContractEnglishAuctionsPage
contract={info.contract}
twAccount={twAccount}
isInsightSupported={isInsightSupported}
/>
);
}
@@ -1,14 +1,7 @@
import {
Flex,
GridItem,
SimpleGrid,
Skeleton,
SkeletonText,
useBreakpointValue,
} from "@chakra-ui/react";
import { SkeletonContainer } from "@/components/ui/skeleton";
import { TrackedLinkTW } from "@/components/ui/tracked-link";
import { useMemo } from "react";
import { type NFT, ZERO_ADDRESS } from "thirdweb";
import { Card, TrackedLink, type TrackedLinkProps } from "tw-components";
import { NFTMediaWithEmptyState } from "tw-components/nft-media";
type NFTWithContract = NFT & { contractAddress: string; chainId: number };
@@ -20,18 +13,20 @@ const dummyMetadata: (idx: number) => NFTWithContract = (idx) => ({
tokenURI: `1-0x123-${idx}`,
metadata: {
name: "Loading...",
description: "lorem ipsum loading sit amet",
description:
"lorem ipsum loading sit amet consectetur adipisicing elit. Quisquam, quos.",
id: BigInt(idx || 0),
uri: `1-0x123-${idx}`,
},
owner: `0x_fake_${idx}`,
type: "ERC721",
supply: 1n,
tokenAddress: ZERO_ADDRESS,
});
interface NFTCardsProps {
nfts: Array<NFTWithContract>;
trackingCategory: TrackedLinkProps["category"];
trackingCategory: string;
isPending: boolean;
allNfts?: boolean;
}
@@ -42,61 +37,82 @@ export const NFTCards: React.FC<NFTCardsProps> = ({
isPending,
allNfts,
}) => {
const isMobile = useBreakpointValue({ base: true, md: false });
const dummyData = useMemo(() => {
return Array.from({
length: allNfts ? nfts.length : isMobile ? 2 : 3,
length: allNfts ? nfts.length : 3,
}).map((_, idx) => dummyMetadata(idx));
}, [nfts.length, isMobile, allNfts]);
}, [nfts.length, allNfts]);
return (
<SimpleGrid
gap={{ base: 3, md: 6 }}
columns={allNfts ? { base: 2, md: 4 } : { base: 2, md: 3 }}
>
<div className="grid grid-cols-1 gap-4 lg:grid-cols-3">
{(isPending ? dummyData : nfts).map((token) => {
const tokenId = token.id.toString();
return (
<GridItem
<div
key={`${token.chainId}_${token.contractAddress}_${tokenId}`}
as={TrackedLink}
category={trackingCategory}
href={`/${token.chainId}/${token.contractAddress}/nfts/${tokenId}`}
_hover={{ opacity: 0.75, textDecoration: "none" }}
className="hover:-translate-y-0.5 relative flex h-full cursor-pointer flex-col overflow-hidden rounded-lg bg-background duration-200 hover:scale-[1.01]"
>
<Card p={0} h="full">
<div className="relative aspect-square w-full overflow-hidden rounded-xl">
<Skeleton isLoaded={!isPending}>
<NFTMediaWithEmptyState
metadata={token.metadata}
requireInteraction
width="100%"
height="100%"
/>
</Skeleton>
</div>
<Flex p={4} pb={3} gap={1} direction="column">
<Skeleton w={!isPending ? "100%" : "50%"} isLoaded={!isPending}>
<h2 className="font-semibold tracking-tight">
{token.metadata.name}
</h2>
</Skeleton>
<SkeletonText isLoaded={!isPending}>
<p className="line-clamp-3 text-muted-foreground text-sm">
{token.metadata.description ? (
token.metadata.description
) : (
<i>No description</i>
)}
</p>
</SkeletonText>
</Flex>
</Card>
</GridItem>
{/* border */}
<div className="absolute inset-0 rounded-lg border border-border" />
{/* image */}
<div className="relative aspect-square w-full overflow-hidden">
<SkeletonContainer
skeletonData={token.metadata}
loadedData={isPending ? undefined : token.metadata}
className="h-full w-full rounded-lg"
render={(v) => {
return (
<NFTMediaWithEmptyState
metadata={v}
requireInteraction
width="100%"
height="100%"
/>
);
}}
/>
</div>
<div className="p-4">
{/* title */}
<SkeletonContainer
skeletonData={token.metadata}
loadedData={isPending ? undefined : token.metadata}
className="mb-2"
render={(v) => {
return (
<h2 className="font-semibold tracking-tight">
<TrackedLinkTW
category={trackingCategory}
label="view_nft"
href={`/${token.chainId}/${token.contractAddress}/nfts/${tokenId}`}
className="before:absolute before:inset-0"
>
{v.name}
</TrackedLinkTW>
</h2>
);
}}
/>
{/* Description */}
<SkeletonContainer
skeletonData={token.metadata}
loadedData={isPending ? undefined : token.metadata}
render={(v) => {
return (
<p className="line-clamp-3 text-muted-foreground text-sm">
{v.description ? v.description : <i>No description</i>}
</p>
);
}}
/>
</div>
</div>
);
})}
</SimpleGrid>
</div>
);
};
@@ -55,7 +55,7 @@ type Options =
};
export async function getClaimPhasesInLegacyFormat(
options: BaseTransactionOptions<Options>,
options: BaseTransactionOptions<Options> & { isMultiPhase: boolean },
): Promise<CombinedClaimCondition[]> {
const conditions = await (async () => {
switch (options.type) {
@@ -64,7 +64,10 @@ export async function getClaimPhasesInLegacyFormat(
case "erc721":
return ERC721Ext.getClaimConditions(options);
case "erc1155":
return ERC1155Ext.getClaimConditions(options);
return ERC1155Ext.getClaimConditions({
...options,
singlePhaseDrop: !options.isMultiPhase,
});
}
})();
@@ -126,7 +129,7 @@ export async function getClaimPhasesInLegacyFormat(
type PhaseInput = z.input<typeof LegacyClaimConditionInputSchema>;
export function setClaimPhasesTx(
baseOptions: BaseTransactionOptions<Options>,
baseOptions: BaseTransactionOptions<Options> & { isSinglePhase?: boolean },
rawPhases: PhaseInput[],
) {
const phases = rawPhases.map((phase) => {
@@ -172,6 +175,7 @@ export function setClaimPhasesTx(
contract: baseOptions.contract,
phases,
tokenId: baseOptions.tokenId,
singlePhaseDrop: baseOptions.isSinglePhase,
});
}
}
@@ -222,11 +222,15 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
const claimConditionsQuery = useReadContract(getClaimPhasesInLegacyFormat, {
contract,
isMultiPhase,
...(isErc20
? { type: "erc20", decimals: tokenDecimals.data }
: isErc721
? { type: "erc721" }
: { type: "erc1155", tokenId: BigInt(tokenId || 0) }),
: {
type: "erc1155",
tokenId: BigInt(tokenId || 0),
}),
});
const transformedQueryData = useMemo(() => {
@@ -370,6 +374,7 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
const tx = setClaimPhasesTx(
{
contract,
isSinglePhase: !isMultiPhase,
...(isErc20
? { type: "erc20", decimals: tokenDecimals.data }
: isErc721
@@ -635,6 +640,7 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
isErc20={isErc20}
contract={contract}
tokenId={tokenId}
isMultiphase={isMultiPhase}
/>
)}
</div>
@@ -10,6 +10,7 @@ interface ClaimConditionsProps {
isColumn?: true;
isERC20: boolean;
twAccount: Account | undefined;
isMultiphase: boolean;
}
export const ClaimConditions: React.FC<ClaimConditionsProps> = ({
contract,
@@ -17,6 +18,7 @@ export const ClaimConditions: React.FC<ClaimConditionsProps> = ({
isColumn,
isERC20,
twAccount,
isMultiphase,
}) => {
return (
<div className="flex w-full flex-col gap-6">
@@ -37,8 +39,7 @@ export const ClaimConditions: React.FC<ClaimConditionsProps> = ({
contract={contract}
tokenId={tokenId}
isColumn={isColumn}
// always multi phase!
isMultiPhase={true}
isMultiPhase={isMultiphase}
/>
</div>
);
@@ -18,6 +18,7 @@ interface ResetClaimEligibilityProps {
contract: ThirdwebContract;
tokenId?: string;
twAccount: Account | undefined;
isMultiphase: boolean;
}
export const ResetClaimEligibility: React.FC<ResetClaimEligibilityProps> = ({
@@ -25,6 +26,7 @@ export const ResetClaimEligibility: React.FC<ResetClaimEligibilityProps> = ({
tokenId,
isErc20,
twAccount,
isMultiphase,
}) => {
const trackEvent = useTrack();
@@ -55,6 +57,7 @@ export const ResetClaimEligibility: React.FC<ResetClaimEligibilityProps> = ({
return ERC1155Ext.resetClaimEligibility({
contract,
tokenId: BigInt(tokenId),
singlePhaseDrop: !isMultiphase,
});
}
// assume erc 721
@@ -36,33 +36,34 @@ export function ContractPageLayout(props: {
return (
<ChakraProviderSetup>
<SidebarLayout sidebarLinks={sidebarLinks}>
<div className="border-border border-b pb-8">
<div className="flex flex-col gap-4 ">
<div className="flex flex-col justify-between gap-4 md:flex-row">
<ContractMetadata
contract={contract}
chain={chainMetadata}
contractMetadata={dashboardContractMetadata}
externalLinks={externalLinks}
/>
<PrimaryDashboardButton
contractAddress={contract.address}
chain={contract.chain}
contractInfo={{
chain: chainMetadata,
chainSlug: chainMetadata.slug,
contractAddress: contract.address,
}}
teamsAndProjects={teamsAndProjects}
client={client}
/>
</div>
<DeprecatedAlert chain={chainMetadata} />
<div className="border-border border-b py-8">
<div className="container flex flex-col gap-4">
<div className="flex flex-col justify-between gap-4 md:flex-row">
<ContractMetadata
contract={contract}
chain={chainMetadata}
contractMetadata={dashboardContractMetadata}
externalLinks={externalLinks}
/>
<PrimaryDashboardButton
contractAddress={contract.address}
chain={contract.chain}
contractInfo={{
chain: chainMetadata,
chainSlug: chainMetadata.slug,
contractAddress: contract.address,
}}
teamsAndProjects={teamsAndProjects}
client={client}
/>
</div>
<DeprecatedAlert chain={chainMetadata} />
</div>
<div className="h-8" />
<div className="pb-10">{props.children}</div>
</div>
<SidebarLayout sidebarLinks={sidebarLinks}>
{props.children}
<div className="h-20" />
</SidebarLayout>
</ChakraProviderSetup>
);
@@ -3,7 +3,7 @@
import { CopyAddressButton } from "@/components/ui/CopyAddressButton";
import { Button } from "@/components/ui/button";
import { getThirdwebClient } from "@/constants/thirdweb.server";
import { ChainIcon } from "components/icons/ChainIcon";
import { ChainIconClient } from "components/icons/ChainIcon";
import { ExternalLinkIcon } from "lucide-react";
import Link from "next/link";
import type { ChainMetadata } from "thirdweb/chains";
@@ -66,7 +66,7 @@ export const MetadataHeader: React.FC<MetadataHeaderProps> = ({
</div>
)}
<div className="flex grow flex-col gap-4">
<div className="flex grow flex-col gap-3">
{/* Title */}
{!data ? (
<h1 className="font-semibold text-2xl tracking-tight lg:text-3xl">
@@ -75,7 +75,7 @@ export const MetadataHeader: React.FC<MetadataHeaderProps> = ({
) : (
<div className="flex flex-col gap-3 lg:flex-row lg:items-center">
{data?.name && (
<h1 className="line-clamp-2 font-semibold text-2xl tracking-tight lg:text-3xl">
<h1 className="line-clamp-2 font-bold text-2xl tracking-tight lg:text-3xl">
{data.name}
</h1>
)}
@@ -85,7 +85,7 @@ export const MetadataHeader: React.FC<MetadataHeaderProps> = ({
href={`/${chain.slug}`}
className="flex w-fit shrink-0 items-center gap-2 rounded-3xl border border-border bg-card px-2.5 py-1.5 hover:bg-accent"
>
<ChainIcon ipfsSrc={chain.icon?.url} className="size-4" />
<ChainIconClient ipfsSrc={chain.icon?.url} className="size-4" />
{cleanedChainName && (
<span className="text-xs">{cleanedChainName}</span>
)}
@@ -89,7 +89,7 @@ function AddToProjectButton(props: {
return (
<Dialog>
<DialogTrigger asChild>
<Button className="gap-2" variant="outline">
<Button className="gap-2 bg-card" variant="outline">
<PlusIcon className="size-4" />
Add To Project
</Button>
@@ -21,7 +21,7 @@ import {
} from "./detectedFeatures/permissions";
import { supportedERCs } from "./detectedFeatures/supportedERCs";
import { type EmbedTypeToShow, getEmbedTypeToShow } from "./getEmbedTypeToShow";
import { isAnalyticsSupportedForChain } from "./isAnalyticsSupportedForChain";
import { isInsightSupportedForChain } from "./isAnalyticsSupportedForChain";
export type ContractPageMetadata = {
supportedERCs: {
@@ -36,7 +36,7 @@ export type ContractPageMetadata = {
isPermissionsEnumerableSupported: boolean;
isModularCore: boolean;
embedType: EmbedTypeToShow;
isAnalyticsSupported: boolean;
isInsightSupported: boolean;
isSplitSupported: boolean;
isERC721ClaimConditionsSupported: boolean;
isERC20ClaimConditionsSupported: boolean;
@@ -47,7 +47,7 @@ export type ContractPageMetadata = {
};
export async function getContractPageMetadata(contract: ThirdwebContract) {
return getContractPageMetadataSetup(contract, isAnalyticsSupportedForChain);
return getContractPageMetadataSetup(contract, isInsightSupportedForChain);
}
export async function getContractPageMetadataSetup(
@@ -56,7 +56,7 @@ export async function getContractPageMetadataSetup(
): Promise<ContractPageMetadata> {
const [
functionSelectorsResult,
isAnalyticsSupportedResult,
isInsightSupportedResult,
contractTypeResult,
] = await Promise.allSettled([
resolveFunctionSelectors(contract),
@@ -69,9 +69,9 @@ export async function getContractPageMetadataSetup(
? functionSelectorsResult.value
: [];
const isAnalyticsSupported =
isAnalyticsSupportedResult.status === "fulfilled"
? isAnalyticsSupportedResult.value
const isInsightSupported =
isInsightSupportedResult.status === "fulfilled"
? isInsightSupportedResult.value
: true;
const contractType =
@@ -86,7 +86,7 @@ export async function getContractPageMetadataSetup(
isPermissionsEnumerableSupported(functionSelectors),
isModularCore: isModularCoreContract(functionSelectors),
embedType: getEmbedTypeToShow(functionSelectors),
isAnalyticsSupported: isAnalyticsSupported,
isInsightSupported: isInsightSupported,
isSplitSupported: contractType === "Split",
isVoteContract: contractType === "VoteERC20",
isERC721ClaimConditionsSupported:
@@ -47,7 +47,7 @@ export function getContractPageSidebarLinks(data: {
{
label: "Analytics",
href: `${layoutPrefix}/analytics`,
hide: !data.metadata.isAnalyticsSupported,
hide: !data.metadata.isInsightSupported,
exactMatch: true,
},
{
@@ -4,7 +4,7 @@ import { getVercelEnv } from "lib/vercel-utils";
const thirdwebDomain =
getVercelEnv() !== "production" ? "thirdweb-dev" : "thirdweb";
export async function isAnalyticsSupportedForChain(
export async function isInsightSupportedForChain(
chainId: number,
): Promise<boolean> {
try {
@@ -32,6 +32,7 @@ export function AccountPageClient(props: {
contract={props.contract}
chainMetadata={props.chainMetadata}
twAccount={props.twAccount}
isInsightSupported={metadataQuery.data.isInsightSupported}
/>
);
}
@@ -12,12 +12,14 @@ interface AccountPageProps {
contract: ThirdwebContract;
chainMetadata: ChainMetadata;
twAccount: Account | undefined;
isInsightSupported: boolean;
}
export const AccountPage: React.FC<AccountPageProps> = ({
contract,
chainMetadata,
twAccount,
isInsightSupported,
}) => {
const symbol = chainMetadata.nativeCurrency.symbol || "Native Token";
@@ -43,7 +45,7 @@ export const AccountPage: React.FC<AccountPageProps> = ({
<div className="flex flex-row items-center justify-between">
<Heading size="title.sm">NFTs owned</Heading>
</div>
<NftsOwned contract={contract} />
<NftsOwned contract={contract} isInsightSupported={isInsightSupported} />
</div>
);
};
@@ -6,12 +6,17 @@ import { NFTCards } from "../../_components/NFTCards";
interface NftsOwnedProps {
contract: ThirdwebContract;
isInsightSupported: boolean;
}
export const NftsOwned: React.FC<NftsOwnedProps> = ({ contract }) => {
export const NftsOwned: React.FC<NftsOwnedProps> = ({
contract,
isInsightSupported,
}) => {
const { data: walletNFTs, isPending: isWalletNFTsLoading } = useWalletNFTs({
chainId: contract.chain.id,
walletAddress: contract.address,
isInsightSupported: isInsightSupported,
});
const nfts = walletNFTs?.result || [];
@@ -28,6 +33,7 @@ export const NftsOwned: React.FC<NftsOwnedProps> = ({ contract }) => {
type: nft.type,
contractAddress: nft.contractAddress,
chainId: contract.chain.id,
tokenAddress: nft.tokenAddress,
}))}
allNfts
isPending={isWalletNFTsLoading}
@@ -33,7 +33,8 @@ export default async function Page(props: {
);
}
const { isAccount } = await getContractPageMetadata(contract);
const { isAccount, isInsightSupported } =
await getContractPageMetadata(contract);
if (!isAccount) {
redirect(`/${params.chain_id}/${params.contractAddress}`);
@@ -44,6 +45,7 @@ export default async function Page(props: {
contract={contract}
chainMetadata={chainMetadata}
twAccount={account}
isInsightSupported={isInsightSupported}
/>
);
}
@@ -21,7 +21,7 @@ export function ContractAnalyticsPageClient(props: {
return <ErrorPage />;
}
if (!metadataQuery.data.isAnalyticsSupported) {
if (!metadataQuery.data.isInsightSupported) {
return <RedirectToContractOverview contract={props.contract} />;
}
@@ -1,6 +1,6 @@
"use client";
import { ThirdwebBarChart } from "@/components/blocks/charts/bar-chart";
import { Skeleton, Stat, StatLabel, StatNumber } from "@chakra-ui/react";
import { SkeletonContainer } from "@/components/ui/skeleton";
import type { UseQueryResult } from "@tanstack/react-query";
import {
type AnalyticsQueryParams,
@@ -14,9 +14,9 @@ import {
useTotalContractTransactionAnalytics,
useTotalContractUniqueWallets,
} from "data/analytics/hooks";
import { Suspense, useMemo, useState } from "react";
import { formatDate } from "date-fns";
import { useMemo, useState } from "react";
import type { ThirdwebContract } from "thirdweb";
import { Card } from "tw-components";
import {
DateRangeSelector,
type Range,
@@ -121,6 +121,15 @@ type ChartProps = {
startDate: Date;
endDate: Date;
};
function toolTipLabelFormatter(_v: string, item: unknown) {
if (Array.isArray(item)) {
const time = item[0].payload.time as number;
return formatDate(new Date(time), "MMM d, yyyy");
}
return undefined;
}
function UniqueWalletsChart(props: ChartProps) {
const analyticsQuery = useContractUniqueWalletAnalytics(props);
@@ -128,6 +137,7 @@ function UniqueWalletsChart(props: ChartProps) {
<ThirdwebBarChart
header={{
title: "Unique Wallets",
titleClassName: "mb-0.5 text-xl",
description:
"The number of unique wallet addresses that have sent a transaction to this contract.",
}}
@@ -140,6 +150,8 @@ function UniqueWalletsChart(props: ChartProps) {
},
}}
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
hideLabel={false}
toolTipLabelFormatter={toolTipLabelFormatter}
/>
);
}
@@ -156,6 +168,7 @@ function TotalTransactionsChart(props: ChartProps) {
<ThirdwebBarChart
header={{
title: "Total Transactions",
titleClassName: "mb-0.5 text-xl",
description:
"The number of transactions that have been sent to this contract.",
}}
@@ -168,6 +181,8 @@ function TotalTransactionsChart(props: ChartProps) {
},
}}
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
hideLabel={false}
toolTipLabelFormatter={toolTipLabelFormatter}
/>
);
}
@@ -179,6 +194,7 @@ function TotalEventsChart(props: ChartProps) {
<ThirdwebBarChart
header={{
title: "Total Events",
titleClassName: "mb-0.5 text-xl",
description:
"The number of on-chain events that have been emitted from this contract.",
}}
@@ -191,6 +207,8 @@ function TotalEventsChart(props: ChartProps) {
},
}}
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
hideLabel={false}
toolTipLabelFormatter={toolTipLabelFormatter}
/>
);
}
@@ -227,6 +245,7 @@ function FunctionBreakdownChart(
<ThirdwebBarChart
header={{
title: "Function Breakdown",
titleClassName: "mb-0.5 text-xl",
description:
"The breakdown of calls to each write function from transactions.",
}}
@@ -247,6 +266,8 @@ function FunctionBreakdownChart(
)}
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
showLegend
hideLabel={false}
toolTipLabelFormatter={toolTipLabelFormatter}
/>
);
}
@@ -283,6 +304,7 @@ function EventBreakdownChart(
<ThirdwebBarChart
header={{
title: "Event Breakdown",
titleClassName: "mb-0.5 text-xl",
description: "The breakdown of events emitted by this contract.",
}}
data={mappedQueryData || []}
@@ -302,6 +324,8 @@ function EventBreakdownChart(
)}
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
showLegend
hideLabel={false}
toolTipLabelFormatter={toolTipLabelFormatter}
/>
);
}
@@ -320,25 +344,12 @@ const AnalyticsStat: React.FC<AnalyticsStatProps> = ({
useTotal,
}) => {
return (
<Suspense fallback={<AnalyticsSkeleton label={label} />}>
<AnalyticsData
chainId={chainId}
contractAddress={contractAddress}
useTotal={useTotal}
label={label}
/>
</Suspense>
);
};
const AnalyticsSkeleton: React.FC<{ label: string }> = ({ label }) => {
return (
<Card as={Stat} className="bg-card">
<StatLabel mb={{ base: 1, md: 0 }}>{label}</StatLabel>
<Skeleton isLoaded={false}>
<StatNumber>{0}</StatNumber>
</Skeleton>
</Card>
<AnalyticsData
chainId={chainId}
contractAddress={contractAddress}
useTotal={useTotal}
label={label}
/>
);
};
@@ -355,20 +366,23 @@ const AnalyticsData: React.FC<AnalyticsStatProps> = ({
chainId,
});
const data = useMemo(() => {
if (!totalQuery.data) {
return 0;
}
return totalQuery.data.count;
}, [totalQuery.data]);
return (
<Card as={Stat} className="bg-card">
<StatLabel mb={{ base: 1, md: 0 }}>{label}</StatLabel>
<Skeleton isLoaded={totalQuery.isFetched}>
<StatNumber>{data.toLocaleString()}</StatNumber>
</Skeleton>
</Card>
);
return <AnalyticsStatUI label={label} data={totalQuery.data?.count} />;
};
function AnalyticsStatUI(props: {
label: string;
data: number | undefined;
}) {
return (
<dl className="rounded-lg border bg-card p-4">
<dt className="font-semibold">{props.label}</dt>
<SkeletonContainer
skeletonData={10000}
loadedData={props.data}
render={(v) => {
return <dd className="font-normal text-xl">{v.toLocaleString()}</dd>;
}}
/>
</dl>
);
}
@@ -34,9 +34,9 @@ export default async function Page(props: {
);
}
const { isAnalyticsSupported } = await getContractPageMetadata(info.contract);
const { isInsightSupported } = await getContractPageMetadata(info.contract);
if (!isAnalyticsSupported) {
if (!isInsightSupported) {
redirect(`/${params.chain_id}/${params.contractAddress}`);
}
@@ -36,6 +36,7 @@ export function ClaimConditionsClient(props: {
contract={props.contract}
isERC20={supportedERCs.isERC20}
twAccount={props.twAccount}
isMultiphase={true}
/>
);
}
@@ -41,6 +41,7 @@ export default async function Page(props: {
contract={contract}
isERC20={supportedERCs.isERC20}
twAccount={account}
isMultiphase={true}
/>
);
}
@@ -2,7 +2,6 @@ import { fetchPublishedContractsFromDeploy } from "components/contract-component
import { notFound } from "next/navigation";
import {
type ContractOptions,
eth_blockNumber,
eth_getTransactionByHash,
eth_getTransactionReceipt,
getContractEvents,
@@ -39,14 +38,17 @@ export default async function Page(props: {
chain_id: string;
}>;
}) {
const params = await props.params;
const info = await getContractPageParamsInfo(params);
const ProxyDeployedEvent = prepareEvent({
signature:
"event ProxyDeployedV2(address indexed implementation, address indexed proxy, address indexed deployer, bytes32 inputSalt, bytes data, bytes extraData)",
filters: {
proxy: params.contractAddress.toLowerCase(),
},
});
const params = await props.params;
const info = await getContractPageParamsInfo(params);
if (!info) {
notFound();
}
@@ -111,16 +113,9 @@ export default async function Page(props: {
let creationBlockNumber: bigint | undefined;
if (twCloneFactoryContract) {
const latestBlockNumber = await eth_blockNumber(
getRpcClient({
client: contract.client,
chain: contract.chain,
}),
);
const events = await getContractEvents({
contract: twCloneFactoryContract,
events: [ProxyDeployedEvent],
blockRange: latestBlockNumber < 100000n ? latestBlockNumber : 100000n,
});
const event = events.find(
(e) =>
@@ -208,7 +203,6 @@ export default async function Page(props: {
const events = await getContractEvents({
contract: contract,
events: [moduleEvent],
blockRange: 123456n,
});
const filteredEvents = events.filter(
@@ -1,6 +1,6 @@
import { SelectWithSearch } from "@/components/blocks/select-with-search";
import { Badge } from "@/components/ui/badge";
import { ChainIcon } from "components/icons/ChainIcon";
import { ChainIconClient } from "components/icons/ChainIcon";
import { useAllChainsData } from "hooks/chains/allChains";
import { useCallback, useMemo } from "react";
@@ -45,7 +45,7 @@ export function SingleNetworkSelector(props: {
return (
<div className="flex justify-between gap-4">
<span className="flex grow gap-2 truncate text-left">
<ChainIcon
<ChainIconClient
className="size-5"
ipfsSrc={chain.icon?.url}
loading="lazy"
@@ -9,6 +9,12 @@ import { isAddress, isContractDeployed } from "thirdweb/utils";
import type { MinimalTeamsAndProjects } from "../../../../../components/contract-components/contract-deploy-form/add-to-project-card";
import { resolveFunctionSelectors } from "../../../../../lib/selectors";
import { shortenIfAddress } from "../../../../../utils/usedapp-external";
import { getRawAccount } from "../../../../account/settings/getAccount";
import {
getAuthToken,
getAuthTokenWalletAddress,
} from "../../../../api/lib/getAuthToken";
import { NebulaFloatingChatButton } from "../../../../nebula-app/(app)/components/FloatingChat/FloatingChat";
import { ConfigureCustomChain } from "./_layout/ConfigureCustomChain";
import { getContractMetadataHeaderData } from "./_layout/contract-metadata";
import { ContractPageLayout } from "./_layout/contract-page-layout";
@@ -42,6 +48,12 @@ export default async function Layout(props: {
notFound();
}
const [authToken, account, accountAddress] = await Promise.all([
getAuthToken(),
getRawAccount(),
getAuthTokenWalletAddress(),
]);
const client = getThirdwebClient();
const teamsAndProjects = await getTeamsAndProjectsIfLoggedIn();
@@ -75,6 +87,25 @@ export default async function Layout(props: {
const { contractMetadata, externalLinks } =
await getContractMetadataHeaderData(contract);
const contractAddress = info.contract.address;
const chainName = info.chainMetadata.name;
const chainId = info.contract.chain.id;
const contractPromptPrefix = `A user is viewing the contract address ${contractAddress} on ${chainName} (Chain ID: ${chainId}). Provide a concise summary of this contract's functionalities, such as token minting, staking, or governance mechanisms. Focus on what the contract enables users to do, avoiding transaction execution details unless requested.
Users may be interested in how to interact with the contract. Outline common interaction patterns, such as claiming rewards, participating in governance, or transferring assets. Emphasize the contract's capabilities without guiding through transaction processes unless asked.
Provide insights into how the contract is being used. Share information on user engagement, transaction volumes, or integration with other dApps, focusing on the contract's role within the broader ecosystem.
Users may be considering integrating the contract into their applications. Discuss how this contract's functionalities can be leveraged within different types of dApps, highlighting potential use cases and benefits.
The following is the user's message:`;
const examplePrompts: string[] = [
"What does this contract do?",
"What permissions or roles exist in this contract?",
"Which functions are used the most?",
"Has this contract been used recently?",
"Who are the largest holders/users of this?",
];
return (
<ContractPageLayout
chainMetadata={chainMetadata}
@@ -85,6 +116,21 @@ export default async function Layout(props: {
teamsAndProjects={teamsAndProjects}
client={client}
>
<NebulaFloatingChatButton
authToken={authToken ?? undefined}
account={account}
label="Ask AI about this contract"
client={client}
nebulaParams={{
messagePrefix: contractPromptPrefix,
chainIds: [info.contract.chain.id],
wallet: accountAddress ?? undefined,
}}
examplePrompts={examplePrompts.map((prompt) => ({
title: prompt,
message: prompt,
}))}
/>
{props.children}
</ContractPageLayout>
);
@@ -3,14 +3,11 @@ import { getThirdwebClient } from "@/constants/thirdweb.server";
import type { Meta, StoryObj } from "@storybook/react";
import { useMutation } from "@tanstack/react-query";
import { useState } from "react";
import { Toaster, toast } from "sonner";
import { BadgeContainer, mobileViewport } from "stories/utils";
import { toast } from "sonner";
import { BadgeContainer } from "stories/utils";
import { ZERO_ADDRESS } from "thirdweb";
import { ConnectButton, ThirdwebProvider } from "thirdweb/react";
import {
ErrorProvider,
type TransactionError,
} from "../../../../../../../contexts/error-handler";
import type { TransactionError } from "../../../../../../../contexts/error-handler";
import { accountStub } from "../../../../../../../stories/stubs";
import {
BatchMetadataModuleUI,
@@ -21,7 +18,6 @@ const meta = {
title: "Modules/BatchMetadata",
component: Component,
parameters: {
layout: "centered",
nextjs: {
appDirectory: true,
},
@@ -31,17 +27,10 @@ const meta = {
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
export const Variants: Story = {
args: {},
};
export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};
function Component() {
const [isOwner, setIsOwner] = useState(true);
@@ -80,38 +69,35 @@ function Component() {
return (
<ThirdwebProvider>
<ErrorProvider>
<div className="container flex max-w-[1150px] flex-col gap-10 py-10">
<div>
<ConnectButton client={getThirdwebClient()} />
</div>
<div className="flex items-center gap-5">
<CheckboxWithLabel
value={isOwner}
onChange={setIsOwner}
id="isOwner"
label="Is Owner"
/>
</div>
<BadgeContainer label="Default">
<BatchMetadataModuleUI
twAccount={accountStub()}
contractInfo={contractInfo}
moduleAddress="0x0000000000000000000000000000000000000000"
uploadMetadata={uploadMetadataStub}
uninstallButton={{
onClick: async () => removeMutation.mutateAsync(),
isPending: removeMutation.isPending,
}}
isOwnerAccount={isOwner}
contractChainId={1}
/>
</BadgeContainer>
<Toaster richColors />
<div className="container flex max-w-6xl flex-col gap-10 py-10">
<div>
<ConnectButton client={getThirdwebClient()} />
</div>
</ErrorProvider>
<div className="flex items-center gap-5">
<CheckboxWithLabel
value={isOwner}
onChange={setIsOwner}
id="isOwner"
label="Is Owner"
/>
</div>
<BadgeContainer label="Default">
<BatchMetadataModuleUI
twAccount={accountStub()}
contractInfo={contractInfo}
moduleAddress="0x0000000000000000000000000000000000000000"
uploadMetadata={uploadMetadataStub}
uninstallButton={{
onClick: async () => removeMutation.mutateAsync(),
isPending: removeMutation.isPending,
}}
isOwnerAccount={isOwner}
contractChainId={1}
/>
</BadgeContainer>
</div>
</ThirdwebProvider>
);
}
@@ -11,8 +11,7 @@ import type { Meta, StoryObj } from "@storybook/react";
import { useMutation } from "@tanstack/react-query";
import { subDays } from "date-fns";
import { useState } from "react";
import { Toaster, toast } from "sonner";
import { mobileViewport } from "stories/utils";
import { toast } from "sonner";
import { NATIVE_TOKEN_ADDRESS, ZERO_ADDRESS } from "thirdweb";
import { ConnectButton, ThirdwebProvider } from "thirdweb/react";
import { checksumAddress } from "thirdweb/utils";
@@ -29,7 +28,6 @@ const meta = {
title: "Modules/Claimable",
component: Component,
parameters: {
layout: "centered",
nextjs: {
appDirectory: true,
},
@@ -39,17 +37,10 @@ const meta = {
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
export const Variants: Story = {
args: {},
};
export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};
const testAddress1 = "0x1F846F6DAE38E1C88D71EAA191760B15f38B7A37";
const claimCondition = {
@@ -67,7 +58,7 @@ const claimCondition = {
function Component() {
const [isOwner, setIsOwner] = useState(true);
const [name, setName] = useState("ClaimableERC721");
const [name, setName] = useState("MintableERC721");
const [isClaimConditionLoading, setIsClaimConditionLoading] = useState(false);
const [isPrimarySaleRecipientLoading, setIsPrimarySaleRecipientLoading] =
useState(false);
@@ -110,7 +101,7 @@ function Component() {
return (
<ThirdwebProvider>
<div className="container flex max-w-[1150px] flex-col gap-10 py-10">
<div className="container flex max-w-6xl flex-col gap-10 py-10">
<div>
<ConnectButton client={getThirdwebClient()} />
</div>
@@ -196,8 +187,6 @@ function Component() {
isValidTokenId={true}
noClaimConditionSet={noClaimConditionSet}
/>
<Toaster richColors />
</div>
</ThirdwebProvider>
);
@@ -11,8 +11,8 @@ import { getThirdwebClient } from "@/constants/thirdweb.server";
import type { Meta, StoryObj } from "@storybook/react";
import { useMutation } from "@tanstack/react-query";
import { useState } from "react";
import { Toaster, toast } from "sonner";
import { BadgeContainer, mobileViewport } from "stories/utils";
import { toast } from "sonner";
import { BadgeContainer } from "stories/utils";
import { ConnectButton, ThirdwebProvider } from "thirdweb/react";
import { accountStub } from "../../../../../../../stories/stubs";
import {
@@ -25,7 +25,6 @@ const meta = {
title: "Modules/Mintable",
component: Component,
parameters: {
layout: "centered",
nextjs: {
appDirectory: true,
},
@@ -35,17 +34,10 @@ const meta = {
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
export const Variants: Story = {
args: {},
};
export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};
const testAddress1 = "0x1F846F6DAE38E1C88D71EAA191760B15f38B7A37";
function Component() {
@@ -84,7 +76,7 @@ function Component() {
return (
<ThirdwebProvider>
<div className="container flex max-w-[1150px] flex-col gap-10 py-10">
<div className="container flex max-w-6xl flex-col gap-10 py-10">
<div>
<ConnectButton client={getThirdwebClient()} />
</div>
@@ -175,8 +167,6 @@ function Component() {
contractChainId={1}
/>
</BadgeContainer>
<Toaster richColors />
</div>
</ThirdwebProvider>
);
@@ -2,33 +2,22 @@ import { Checkbox } from "@/components/ui/checkbox";
import type { Meta, StoryObj } from "@storybook/react";
import { useMutation } from "@tanstack/react-query";
import { useState } from "react";
import { Toaster } from "sonner";
import { BadgeContainer, mobileViewport } from "stories/utils";
import { BadgeContainer } from "stories/utils";
import { ThirdwebProvider } from "thirdweb/react";
import { ModuleCardUI } from "./module-card";
const meta = {
title: "Modules/ModuleCard",
component: Component,
parameters: {
layout: "centered",
},
} satisfies Meta<typeof Component>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
export const Variants: Story = {
args: {},
};
export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};
function Component() {
const [isOwner, setIsOwner] = useState(true);
@@ -120,8 +109,6 @@ function Component() {
</div>
</ModuleCardUI>
</BadgeContainer>
<Toaster richColors />
</div>
</ThirdwebProvider>
);
@@ -3,8 +3,8 @@ import { getThirdwebClient } from "@/constants/thirdweb.server";
import type { Meta, StoryObj } from "@storybook/react";
import { useMutation } from "@tanstack/react-query";
import { useState } from "react";
import { Toaster, toast } from "sonner";
import { BadgeContainer, mobileViewport } from "stories/utils";
import { toast } from "sonner";
import { BadgeContainer } from "stories/utils";
import { ConnectButton, ThirdwebProvider } from "thirdweb/react";
import { accountStub } from "../../../../../../../stories/stubs";
import {
@@ -16,7 +16,6 @@ const meta = {
title: "Modules/OpenEditionMetadata",
component: Component,
parameters: {
layout: "centered",
nextjs: {
appDirectory: true,
},
@@ -26,17 +25,10 @@ const meta = {
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
export const Variants: Story = {
args: {},
};
export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};
function Component() {
const [isOwner, setIsOwner] = useState(true);
async function setSharedMetadataStub(values: SetSharedMetadataFormValues) {
@@ -98,8 +90,6 @@ function Component() {
twAccount={accountStub()}
/>
</BadgeContainer>
<Toaster richColors />
</div>
</ThirdwebProvider>
);
@@ -3,8 +3,8 @@ import { getThirdwebClient } from "@/constants/thirdweb.server";
import type { Meta, StoryObj } from "@storybook/react";
import { useMutation } from "@tanstack/react-query";
import { useState } from "react";
import { Toaster, toast } from "sonner";
import { BadgeContainer, mobileViewport } from "stories/utils";
import { toast } from "sonner";
import { BadgeContainer } from "stories/utils";
import { ConnectButton, ThirdwebProvider } from "thirdweb/react";
import { accountStub } from "../../../../../../../stories/stubs";
import {
@@ -18,7 +18,6 @@ const meta = {
title: "Modules/Royalty",
component: Component,
parameters: {
layout: "centered",
nextjs: {
appDirectory: true,
},
@@ -28,17 +27,10 @@ const meta = {
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
export const Variants: Story = {
args: {},
};
export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};
const _testAddress1 = "0x1F846F6DAE38E1C88D71EAA191760B15f38B7A37";
function Component() {
@@ -79,7 +71,7 @@ function Component() {
return (
<ThirdwebProvider>
<div className="container flex max-w-[1150px] flex-col gap-10 py-10">
<div className="container flex max-w-6xl flex-col gap-10 py-10">
<div>
<ConnectButton client={getThirdwebClient()} />
</div>
@@ -168,8 +160,6 @@ function Component() {
twAccount={twAccount}
/>
</BadgeContainer>
<Toaster richColors />
</div>
</ThirdwebProvider>
);
@@ -3,8 +3,8 @@ import { getThirdwebClient } from "@/constants/thirdweb.server";
import type { Meta, StoryObj } from "@storybook/react";
import { useMutation } from "@tanstack/react-query";
import { useState } from "react";
import { Toaster, toast } from "sonner";
import { BadgeContainer, mobileViewport } from "stories/utils";
import { toast } from "sonner";
import { BadgeContainer } from "stories/utils";
import { ConnectButton, ThirdwebProvider } from "thirdweb/react";
import { accountStub } from "../../../../../../../stories/stubs";
import {
@@ -16,7 +16,6 @@ const meta = {
title: "Modules/Transferable",
component: Component,
parameters: {
layout: "centered",
nextjs: {
appDirectory: true,
},
@@ -26,17 +25,10 @@ const meta = {
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
export const Variants: Story = {
args: {},
};
export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};
const testAddress1 = "0x1F846F6DAE38E1C88D71EAA191760B15f38B7A37";
function Component() {
@@ -65,7 +57,7 @@ function Component() {
return (
<ThirdwebProvider>
<div className="container flex max-w-[1150px] flex-col gap-10 py-10">
<div className="container flex max-w-6xl flex-col gap-10 py-10">
<div className="flex gap-2">
<Checkbox
id="terms1"
@@ -139,8 +131,6 @@ function Component() {
twAccount={accountStub()}
/>
</BadgeContainer>
<Toaster richColors />
</div>
</ThirdwebProvider>
);

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