Compare commits

..
Author SHA1 Message Date
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
93532ecbdc Version Packages (#7932)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-28 15:14:50 -07:00
Joaquim VergesandGitHub f3ad6efcf8 [Package] @thirdweb-dev/ai-sdk-provider (#7922) 2025-08-28 15:08:23 -07:00
MananTank 67616c22b4 [MNY-127] Dashboard: Remove unnecessary server action for fetching token list (#7940)
<!--

## 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 token management in the universal bridge API, including the introduction of a new `TokenMetadata` type and the relocation of the `getUniversalBridgeTokens` function to a new file. It also updates import paths for consistency.

### Detailed summary
- Added `TokenMetadata` type in `apps/dashboard/src/@/api/universal-bridge/types.ts`.
- Updated import path for `getUniversalBridgeTokens` in `apps/dashboard/src/@/hooks/tokens.ts` and `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/links/components/CreatePaymentLinkButton.client.tsx`.
- Created `getUniversalBridgeTokens` function in `apps/dashboard/src/@/api/universal-bridge/token-list.ts`.
- Removed old `getUniversalBridgeTokens` function and `TokenMetadata` type from `apps/dashboard/src/@/api/universal-bridge/tokens.ts`.

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

<!-- end pr-codex -->
2025-08-28 21:37:31 +00:00
MananTank 51f7418d1f [MNY-128] Dashboard: UI tweaks in token creation flow (#7939)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on improving the user interface and error handling in the token and NFT launch processes, as well as refining the layout and component interactions in the distribution section.

### Detailed summary
- Removed `isPending` state checks from token and NFT launch components.
- Updated button layouts to improve user experience in `launch-token.tsx` and `launch-nft.tsx`.
- Changed error message text in `token-distribution.tsx`.
- Added `errorMessage` prop to `StepCard` for better error display.
- Adjusted layout and styling for improved responsiveness and alignment.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

- New Features
  - Step headers can now display error messages.
  - Distribution chart moved into the main form flow for clearer visibility.
  - Post-launch actions updated: show “View NFT Collection” or “View Coin” when available; show “Cancel” before completion.

- Refactor
  - Simplified bottom action areas and removed pending-based button disabling for a more predictable flow.

- Style
  - Improved spacing, alignment, and responsiveness in forms.
  - Updated error text to “Token allocation exceeds total supply” for clearer guidance.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-28 21:26:54 +00:00
MananTank dc3df4bc35 [MNY-11] Dashboard: Add ERC20 token selector for starting price in token creation flow (#7933)
<!--

## 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 token creation process by modifying the `onLaunchSuccess` function to accept `CreateAssetFormValues`, updating the way initial tick values are calculated, and improving the token selection interface.

### Detailed summary
- Updated `onLaunchSuccess` in `launch-token.tsx` and `create-token-page.client.tsx` to accept `formValues` and `contractAddress`.
- Modified `getInitialTickValue` to include `tokenAddress`, `chain`, and `client` parameters.
- Added token selection functionality in the `PoolConfig` component using `TokenSelector`.
- Adjusted form validation and pricing logic for token creation.
- Enhanced UI layout in `PoolConfig` for better responsiveness and usability.

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

<!-- end pr-codex -->

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

- New Features
  - Token selector added to choose paired currency (native or ERC‑20) and a field to specify/select token address for ERC‑20 pools.
  - Bridging on launch is now performed only for ERC‑20 asset pool launches.

- Bug Fixes
  - Starting-price handling improved to account for token decimals and precompute/validate launch pricing.

- Style
  - Responsive layout and spacing refinements for pricing and allocation inputs.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-28 22:40:11 +05:30
MananTank 0853872d8d Dashboard: Fix WalletAddress ens image size (#7935)
<!--

## 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 adjusting the size of certain UI components in the `RecentTransfers` and `WalletAddress` sections, ensuring a more consistent design across the dashboard.

### Detailed summary
- In `RecentTransfers.tsx`, removed the `iconClassName` prop from a component.
- In `wallet-address.tsx`, changed the size of a `div` from `size-6` to `size-5`.
- Updated the `CircleSlashIcon` size from `size-6` to `size-5`.
- Added a `fallback` prop to the `Img` component in `WalletAvatar`.
- Changed the `size` of the `Blobbie` component in `WalletAvatar` from `24` to `20`.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

- New Features
  - Added automatic address-based avatar fallback when a wallet/avatar image isn’t available.

- Style
  - Reduced sizes for the invalid-address badge and zero-address indicator for better visual balance.
  - Adjusted fallback avatar size for improved consistency.
  - Standardized icon sizing in Recent Transfers by removing a custom icon size override.
  - Overall, wallet/address visuals are cleaner and more consistent across the dashboard.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-28 12:14:10 +00:00
gregandGitHub 44e6e115d9 [SDK] Feature: Allow proceeding past first checkout widget page with no wallet (#7937) 2025-08-27 21:16:01 -07:00
gregandGitHub 76cfecb305 [Dashboard] Feature: Update payment success copy on payment page (#7936) 2025-08-27 21:03:12 -07:00
MananTank f33d8f16e2 packages/ui: Add markdown-renderer (#7928)
<!--

## 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 consolidating the `MarkdownRenderer` component across different applications, enhancing code reusability and organization by utilizing shared components from the `@workspace/ui` package. It also updates dependencies and modifies some configurations.

### Detailed summary
- Deleted `plaintext-code.tsx` from `apps/portal/src/components/code/`.
- Updated `knip.json` files to include new dependencies: `react-children-utilities`, `react-markdown`, and `remark-gfm`.
- Simplified export of `CodeClient` in `code.client.tsx`.
- Added `InlineCode` and `UnderlineLink` components in `packages/ui`.
- Refactored `MarkdownRenderer` in `apps/dashboard` to use shared components from `@workspace/ui`.
- Removed redundant imports in `MarkdownRenderer` files across different applications.
- Updated `pnpm-lock.yaml` with new package versions.
- Adjusted class names in `MarkdownRenderer` for consistent styling.

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

<!-- end pr-codex -->
2025-08-27 23:18:35 +00:00
MananTank 5c08c7c17d packages/ui: Add code components (#7927)
<!--

## 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 code related to the `CodeClient` component, improving its structure and imports, while also updating several components to use a new centralized location for code utilities. It removes deprecated files and enhances code organization across the project.

### Detailed summary
- Deleted unused files related to clipboard and code rendering.
- Refactored `CodeClient` to use a centralized import from `@workspace/ui/components/code/code.client`.
- Updated various components to import `CodeClient` and other utilities from the new centralized location.
- Added new `CodeServer` component for server-side rendering of code.
- Improved loading states and error handling in code components.
- Updated component imports across the project to maintain consistency.
- Enhanced `PlainTextCodeBlock` functionality with new props for better customization.
- Removed redundant code and improved overall project structure.

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

<!-- end pr-codex -->
2025-08-27 23:18:35 +00:00
MananTank e20b1dd883 packages/ui: Add breadcrumb, tooltip, table (#7926)
<!--

## 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 adding the `@radix-ui/react-tooltip` package and refactoring the tooltip and breadcrumb components across multiple applications to enhance UI functionality and maintainability.

### Detailed summary
- Added `@radix-ui/react-tooltip` dependency in `package.json`.
- Updated `knip.json` files to include `@radix-ui/react-tooltip` in ignored dependencies.
- Refactored tooltip components in `packages/ui/src/components/tooltip.tsx` to utilize `@radix-ui/react-tooltip`.
- Created new `Breadcrumb` components in `packages/ui/src/components/breadcrumb.tsx`.
- Updated table components in `packages/ui/src/components/table.tsx` to streamline exports and structure.
- Removed redundant tooltip component definitions from multiple applications and replaced them with imports from `@workspace/ui/components/tooltip`.

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

<!-- end pr-codex -->
2025-08-27 23:18:35 +00:00
MananTank 55a4728766 packages/ui: Add Spinner, Img (#7925)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on refactoring the `Spinner` component across multiple applications by centralizing its import path and removing redundant files. It also introduces a new spinner style in the Tailwind configuration.

### Detailed summary
- Deleted multiple `Spinner.tsx` and `Spinner.module.css` files from various directories.
- Updated imports of `Spinner` to a centralized path: `@workspace/ui/components/spinner`.
- Added new spinner animation styles in `tailwind.config.ts`.
- Updated the `Spinner` component to use Tailwind classes for styling instead of CSS modules.
- Adjusted stories for `Spinner` to reflect the new structure and styles.

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

<!-- end pr-codex -->
2025-08-27 23:18:34 +00:00
MananTank c5ef55e4f6 packages/ui: Add scroll-shadow (#7924)
<!--

## 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 `ScrollShadow` component by consolidating its usage across various applications and removing redundant files. It also enhances the component's styling and functionality.

### Detailed summary
- Deleted multiple `ScrollShadow` component files from various apps.
- Created a new `ScrollShadow` component in `packages/ui/src/components/scroll-shadow.tsx`.
- Updated import paths for `ScrollShadow` in multiple files across the applications.
- Simplified styling by removing CSS module imports and integrating styles directly within the component.
- Added new CSS properties for shadows as inline styles.

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

<!-- end pr-codex -->
2025-08-27 23:18:34 +00:00
MananTank afd1150984 packages/ui: Add storybook, Add Badge, Input (#7923)
<!--

## 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 several updates focused on enhancing the UI components, improving Storybook integration, and updating dependencies. It also refactors some components for better modularity and performance.

### Detailed summary
- Added `class-variance-authority` and updated `tailwindcss-animate` in `knip.json`.
- Updated `tailwind.config.ts` to include `.storybook` directory in content.
- Refactored imports for `Button` and `Input` components.
- Created `Badge` component and its variants in `badge.tsx`.
- Updated Storybook configuration in `.storybook/main.ts` and `preview.tsx`.
- Refactored `Input` components in multiple locations to use a shared implementation.
- Added `Toaster` setup in Storybook preview for better notifications.
- Updated dependencies in `package.json` and `pnpm-lock.yaml`.

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

<!-- end pr-codex -->

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

- **New Features**
  - Shared Badge and Input components added to the UI package and made available to apps.
  - Themed Storybook for the UI library with mobile viewports, notifications, data-fetching context, and an in-app theme toggle.

- **Refactor**
  - Apps now re-export Badge/Input from the shared UI package; story utilities and preview/layout centralized.

- **Chores**
  - Storybook script and dependencies added, Tailwind content updated, and import paths standardized.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-27 23:18:33 +00:00
gregandGitHub f79672ebbf [Dashboard]: Fix: Remove in-app wallet connection option on payment page (#7934) 2025-08-27 16:00:13 -07:00
MananTank 3a289e80d1 Dashboard: Fix token not added to UB on launch success (#7931)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on improving the handling of the `contractAddress` state in the `LaunchTokenStatus` component by using a `ref` to avoid stale closures and ensure the latest address is used during execution.

### Detailed summary
- Added `useCallback` and `useRef` imports.
- Replaced the state setter for `contractAddress` with a callback to update a `ref`.
- Updated the condition to check `contractAddressRef.current` instead of `contractAddress` when launching.

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

<!-- end pr-codex -->

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

- Bug Fixes
  - Improved reliability of the token launch flow so the final confirmation consistently uses the latest deployed contract address.
  - Prevents rare cases where an outdated address could be shown or used after deployment, reducing intermittent post-launch errors.
  - Fix applies to ERC20 and Drop ERC20 deployments; no visible UI changes, just more consistent launches and confirmations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-27 18:54:33 +00:00
Jonas DanielsandGitHub e9efebaec4 Always show Engine in sidebar regardless of instances (#7929) 2025-08-27 11:53:25 -07:00
Jonas DanielsandGitHub d4a44f5b5f Add reason code type to team capabilities response (#7921) 2025-08-27 11:46:02 -07:00
MananTank e651d0ae91 Dashboard: Update Project header style (#7920)
<!--

## 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 updating UI components across various files in the application. It includes adjustments to icon sizes, component styles, and the introduction of new icons, enhancing the overall user interface and experience.

### Detailed summary
- Changed `PlusIcon` size from `size-4` to `size-3.5` in multiple buttons.
- Replaced `SettingsIcon` with `Settings2Icon`.
- Added `DatabaseIcon`, `WebhookIcon`, `TokenIcon`, `ArrowDownToLineIcon`, and others to various headers.
- Updated button styles to `bg-card` and changed variants to `outline`.
- Modified header layouts to include new icons and adjusted spacing.
- Removed actions from several `ProjectPage` components and replaced them with links.
- Adjusted descriptions and added new links in the `ContractsPage`, `PaymentsPage`, and other components.

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

<!-- end pr-codex -->

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

- New Features
  - Header icons added across many pages; Settings now appears as a dedicated header control where provided.
  - Single link renders as a direct action button; multiple links grouped under a labeled "Resources" menu with chevron.

- Refactor
  - Project header redesigned to an icon-driven layout; mobile-specific branching removed and actions reorganized.

- Style
  - Button variants, spacing, iconography, and dropdown alignment adjusted (smaller icons, new arrow/chevron visuals, reduced item padding).
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-26 22:35:44 +00:00
Joaquim VergesandGitHub 34239ea2cb [Docs] Rename Chat API to Blockchain LLM and add transaction execution docs (#7904) 2025-08-26 03:49:59 -07:00
MananTank 8828df0a58 Dashboard: Fix project links footer not sticked to the bottom on short pages (#7909)
<!--

## 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 `ProjectPage` component by modifying the class names of certain `div` elements to improve styling and spacing.

### Detailed summary
- Changed the class name of the main `div` from `pb-20` to `grow`.
- Added a new `div` with class `h-20` to provide spacing.
- Updated the class name of the footer `div` from `mt-20` to `mt-auto` for better positioning.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

- **Style**
  - Updated project page layout for more consistent spacing and alignment.
  - Standardized vertical spacing and removed conditional bottom padding to ensure predictable layout across views.
  - Footer now aligns to the bottom with automatic spacing, improving visual balance on longer pages.
  - Added a vertical spacer to separate main content from the footer for clearer hierarchy and readability.
  - No changes to public APIs or functionality; visual/spacing adjustments only.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-26 00:59:42 +00:00
MananTank c6150be299 Dashboard: Create Payment link dialog fixes/ui tweaks (#7908)
<!--

## 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 `CreatePaymentLinkButton` component to enhance its dialog interface and improve the overall user experience. It replaces the `LinkIcon` with a `PlusIcon`, refines form elements, and cleans up unused code.

### Detailed summary
- Replaced `LinkIcon` with `PlusIcon`.
- Updated dialog structure and content.
- Changed `RequiredFormLabel` to `FormLabel` for consistency.
- Improved styling by adding `bg-card` class to input fields.
- Removed commented-out code related to image upload functionality.
- Enhanced form layout and submission handling.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

- **Refactor**
  - Streamlined the payment link creation flow with a clearer dialog and improved form handling for a smoother experience.

- **Style**
  - Updated to a plus icon, added a header with title and description, standardized labels, themed inputs, and introduced a bottom action bar.

- **Bug Fixes**
  - Chain and token changes now properly reset related fields.
  - Improved validation for recipient address and token selection.
  - Added progress indicator during creation.
  - Success toast and automatic refresh ensure newly created payment links appear immediately.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-25 20:37:52 +00:00
MananTank 27f50a863c [BLD-143] Dashboard: Fix distribute funds in split contract (#7907)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on improving the `useSplitDistributeFunds` hook and the `DistributeButton` component by refining the distribution logic and enhancing error handling.

### Detailed summary
- Replaced direct comparison of `currency.name` with a check using `getAddress(currency.tokenAddress)` against `getAddress(NATIVE_TOKEN_ADDRESS)`.
- Added `await` for the promise in the `useSplitDistributeFunds` function.
- Renamed the mutation variable in `DistributeButton` from `mutation` to `distributeFundsMutation`.
- Updated the `distributeFunds` function to use `distributeFundsMutation.mutateAsync()`.
- Adjusted the `isPending` prop in `TransactionButton` to reference `distributeFundsMutation.isPending`.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

- Bug Fixes
  - Correctly detects and handles the native token during fund distribution, ensuring accurate routing and transaction counts.
  - Ensures each distribution transaction completes before the next begins, reducing partial or overlapping operations.

- Refactor
  - Streamlined distribution flow and loading state handling for a more consistent experience.
  - Removed toast-based error pop-ups during distribution; status is reflected via the ongoing operation state.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-25 20:09:45 +00:00
MananTank 157f4b9a19 Dashboard: Chain Infra pages UI improvements (#7906)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on improving the layout and user experience in the `Deploy Infrastructure` section of the application. It refines component styles, enhances breadcrumb navigation, and updates form elements for better accessibility and consistency.

### Detailed summary
- Adjusted spacing in the `InfraServiceCard` component.
- Enhanced typography and layout in various sections.
- Added breadcrumb navigation for better user context.
- Updated the `DeployInfrastructureForm` styles for consistency.
- Improved chain selection interface with better guidance.
- Refined the subscription summary layout.
- Enhanced accessibility features across components.

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

<!-- end pr-codex -->
2025-08-25 17:21:38 +00:00
MananTank fbe1aa2e53 Dashboard: Various UI tweaks in project pages (#7905)
<!--

## 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 refining the UI components and improving the layout across various pages in the dashboard application. It includes adjustments to button styles, layout properties, and content formatting for better user experience.

### Detailed summary
- Updated button styles for consistency (size and variant).
- Adjusted layout properties for various components (e.g., `max-w-7xl`, padding).
- Enhanced descriptions with line breaks for clarity.
- Modified component structures for better alignment and spacing.
- Removed unnecessary links from `webhooks` and `contracts` sections.
- Improved the `ProjectPageHeader` and `ProjectPageFooter` components.
- Revised `LinkGroup` to utilize dynamic icon rendering.
- Enhanced `EmptyStateCard` and `TotalSponsoredCard` layouts.

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

<!-- end pr-codex -->

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

- New Features
  - Headers now accept rich content (React nodes) enabling responsive line breaks.

- Style
  - Standardized small "secondary" bordered pill buttons across many actions and dialogs.
  - Updated header typography, spacing, avatar sizing, and max-width constraints for a more prominent layout.
  - Icons adjusted for size/color; token cards now stack vertically; dialog widths constrained; footer container width tightened.

- Bug Fixes / Refactor
  - Replaced visual separators with CSS borders and simplified DOM structure.
  - Removed certain header doc links from Webhooks and Payments pages.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-25 17:09:36 +00:00
bdcbe0e25b Add ai usage section to dashboard (#7902)
Co-authored-by: Cursor Agent <[email protected]>
2025-08-24 19:47:56 -07:00
Joaquim VergesandGitHub 30e217870d [Docs] Update API domains (#7903) 2025-08-24 19:18:24 -07:00
Jonas DanielsandGitHub 44fdfeef1a Rename "Payment Links" to "Payments" in UI text (#7900) 2025-08-22 21:18:07 -07:00
0xFirekeeper 4cda30a87a Clarify authentication flows in wallet API docs (#7899)
Updated the documentation to distinguish between two-step authentication for email, phone, passkey, and SIWE, and single-step flows for guest, custom, and social authentication. Added details and endpoint references for each flow to improve clarity for API users.

<!-- start pr-codex -->

---

## PR-Codex overview
This PR updates the documentation for the authentication process in the `wallets` page, clarifying the steps for different authentication methods and introducing a new section on social authentication.

### Detailed summary
- Clarified the two-step authentication process for email, phone, passkey, or SIWE.
- Added information on skipping the first step for guest or custom authentication.
- Updated the description for initiating authentication to specify SIWE.
- Introduced a new section on social authentication, detailing a single-step flow.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

* **Documentation**
  * Clarified API Authentication flows: two-step for email, phone, passkey, or SIWE; skip-first-step option for guest or custom auth.
  * Updated headings and examples to reflect the revised two-step flow (initiate to get a challenge, then complete).
  * Added a Social Authentication section describing a single-step OAuth flow via a dedicated endpoint, with links to provider details and code samples.
  * Reorganized content so Get Wallet Information follows the updated authentication flows for better readability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-23 00:37:20 +00:00
MananTank 98fab93e8b [BLD-166] Dashboard: Show Manage Contract button if user has used dashboard (#7898)
<!--

## 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 constant `HAS_USED_DASHBOARD` to track user engagement with the dashboard. It updates several components to include the `isDashboardUser` prop, allowing for conditional rendering based on whether the user has previously accessed the dashboard.

### Detailed summary
- Added `HAS_USED_DASHBOARD` constant in `cookie.ts`.
- Updated `NFTPublicPageLayout` to accept `isDashboardUser` prop.
- Modified `SaveLastVisitedTeamPage` to set `HAS_USED_DASHBOARD` cookie.
- Integrated `isDashboardUser` in `NFTPublicPage` and `ERC20PublicPage`.
- Updated `ContractHeaderUI` to conditionally render elements based on `isDashboardUser`.
- Enhanced storybook stories to include `isDashboardUser` prop for testing.

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

<!-- end pr-codex -->

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

* **New Features**
  * Dashboard users are now detected via a persistent flag so public ERC20 and NFT pages can tailor the header UI and surface “Manage Contract” controls when appropriate.
  * Stories updated to cover dashboard vs non-dashboard header states.

* **Bug Fixes**
  * More reliable dashboard-user detection ensures the “Manage Contract” controls display consistently on public contract pages.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-22 21:27:48 +00:00
MananTank 6caba380b2 [MNY-115] Dashboard: Fix token creation flow not working for custom chains (#7896)
<!--

## 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 custom hook `useGetV5DashboardChain` and modifies existing components to utilize this hook for chain management, improving code clarity and reducing redundancy.

### Detailed summary
- Added `useGetV5DashboardChain` hook in `v5-adapter.ts`.
- Updated `LaunchNFT` component to use `useV5DashboardChain`.
- Refactored `CreateTokenAssetPageUI` to use `useV5DashboardChain`.
- Refactored `CreateNFTPage` to use `getChain` from `useGetV5DashboardChain`.
- Refactored `CreateTokenAssetPage` to use `getChain` from `useGetV5DashboardChain`.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

- Refactor
  - Unified chain selection across NFT creation, NFT launch, and token creation flows using a single, consistent resolver.
  - Modernized internals for better compatibility with the latest chain data.

- Bug Fixes
  - Improved reliability of chain selection during deployments and validations, reducing chances of selecting the wrong network.
  - More consistent behavior when switching chains in forms and launch steps.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-21 19:26:44 +00:00
EimanandGitHub 68a07ed106 Portal Feature: changes to ai/mcp page to add buttons for downloading (#7890) 2025-08-21 11:49:17 -07:00
Jonas DanielsandGitHub b075ff55a1 Remove "dedicated" from engine URLs (#7897) 2025-08-21 09:28:25 -07:00
Jonas DanielsandGitHub 2d12249719 Remove "dedicated" from Engine URLs (#7894) 2025-08-20 21:35:47 -07:00
Jonas DanielsandGitHub 301b835b60 Remove RecentPaymentsSection and reduce payment history page size (#7893) 2025-08-20 20:33:51 -07:00
Jonas DanielsandGitHub a02b55744b unified project layout (#7888) 2025-08-20 18:38:12 -07:00
gregandGitHub 8b6deadc35 [Dashboard] Feature: Adds paired address for expedited token route finding (#7892) 2025-08-20 15:56:08 -07:00
494 changed files with 8879 additions and 11943 deletions
+6 -9
View File
@@ -1,14 +1,13 @@
import type { Preview } from "@storybook/nextjs";
import "../src/global.css";
import "@workspace/ui/global.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Button } from "@workspace/ui/components/button";
import { MoonIcon, SunIcon } from "lucide-react";
import { ThemeProvider, useTheme } from "next-themes";
import { Inter as interFont } from "next/font/google";
// biome-ignore lint/style/useImportType: <explanation>
import React from "react";
import { useEffect } from "react";
import { ThemeProvider, useTheme } from "next-themes";
// biome-ignore lint/style/useImportType: ok
import React, { useEffect } from "react";
import { Toaster } from "sonner";
import { Button } from "../src/@/components/ui/button";
const queryClient = new QueryClient();
@@ -69,9 +68,7 @@ const preview: Preview = {
export default preview;
function StoryLayout(props: {
children: React.ReactNode;
}) {
function StoryLayout(props: { children: React.ReactNode }) {
const { setTheme, theme } = useTheme();
useEffect(() => {
+6 -1
View File
@@ -14,7 +14,12 @@
"@types/color",
"fast-xml-parser",
"@workspace/ui",
"tailwindcss-animate"
"tailwindcss-animate",
"@radix-ui/react-tooltip",
"shiki",
"react-children-utilities",
"react-markdown",
"remark-gfm"
],
"next": true,
"project": ["src/**"]
+1 -3
View File
@@ -19,7 +19,6 @@
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.5",
"@radix-ui/react-tooltip": "1.2.7",
"@scalar/api-reference-react": "0.7.25",
"@sentry/nextjs": "9.34.0",
"@shazow/whatsabi": "0.22.2",
"@tanstack/react-query": "5.81.5",
@@ -47,7 +46,6 @@
"papaparse": "^5.5.3",
"pluralize": "^8.0.0",
"posthog-js": "1.256.1",
"posthog-node": "^5.4.0",
"prettier": "3.6.2",
"react": "19.1.0",
"react-children-utilities": "^2.10.0",
@@ -62,7 +60,7 @@
"remark-gfm": "4.0.1",
"responsive-rsc": "0.0.7",
"server-only": "^0.0.1",
"shiki": "1.27.0",
"shiki": "3.12.0",
"sonner": "2.0.6",
"spdx-correct": "^3.2.0",
"stripe": "17.7.0",
@@ -1,57 +0,0 @@
import "server-only";
import { unstable_cache } from "next/cache";
import { PostHog } from "posthog-node";
let _posthogClient: PostHog | null = null;
function getPostHogServer(): PostHog | null {
if (!_posthogClient && process.env.NEXT_PUBLIC_POSTHOG_KEY) {
_posthogClient = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
host: "https://us.i.posthog.com",
});
}
return _posthogClient;
}
/**
* Check if a feature flag is enabled for a specific user
* @param flagKey - The feature flag key
* @param userEmail - The user's email address for filtering
*/
export const isFeatureFlagEnabled = unstable_cache(
async (params: {
flagKey: string;
accountId: string;
email: string | undefined;
}): Promise<boolean> => {
const posthogClient = getPostHogServer();
if (!posthogClient) {
console.warn("Posthog client not set");
return true;
}
const { flagKey, accountId, email } = params;
try {
if (posthogClient && accountId) {
const isEnabled = await posthogClient.isFeatureEnabled(
flagKey,
accountId,
{
personProperties: email ? { email } : undefined,
},
);
if (isEnabled !== undefined) {
return isEnabled;
}
}
} catch (error) {
console.error(`Error checking feature flag ${flagKey}:`, error);
}
return false;
},
["is-feature-flag-enabled"],
{
revalidate: 3600, // 1 hour
},
);
+38 -54
View File
@@ -4,6 +4,7 @@ import { unstable_cache } from "next/cache";
import { ANALYTICS_SERVICE_URL } from "@/constants/server-envs";
import { normalizeTime } from "@/lib/time";
import type {
AIUsageStats,
AnalyticsQueryParams,
EcosystemWalletStats,
EngineCloudStats,
@@ -14,7 +15,6 @@ import type {
UserOpStats,
WalletStats,
WebhookLatencyStats,
WebhookRequestStats,
WebhookSummaryStats,
} from "@/types/analytics";
import { getChains } from "./chain";
@@ -205,6 +205,41 @@ export function getInAppWalletUsage(
return cached_getInAppWalletUsage(normalizedParams(params), authToken);
}
const cached_getAiUsage = unstable_cache(
async (
params: AnalyticsQueryParams,
authToken: string,
): Promise<AIUsageStats[]> => {
const searchParams = buildSearchParams(params);
const res = await fetchAnalytics({
authToken,
url: `v2/nebula/usage?${searchParams.toString()}`,
init: {
method: "GET",
},
});
if (res?.status !== 200) {
const reason = await res?.text();
console.error(
`Failed to fetch AI usage, ${res?.status} - ${res.statusText} - ${reason}`,
);
return [];
}
const json = await res.json();
return json.data as AIUsageStats[];
},
["getAiUsage"],
{
revalidate: 60 * 60, // 1 hour
},
);
export function getAiUsage(params: AnalyticsQueryParams, authToken: string) {
return cached_getAiUsage(normalizedParams(params), authToken);
}
const cached_getUserOpUsage = unstable_cache(
async (
params: AnalyticsQueryParams,
@@ -660,7 +695,7 @@ export function getEngineCloudMethodUsage(
return cached_getEngineCloudMethodUsage(normalizedParams(params), authToken);
}
const cached_getWebhookSummary = unstable_cache(
const _cached_getWebhookSummary = unstable_cache(
async (
params: AnalyticsQueryParams & { webhookId: string },
authToken: string,
@@ -688,51 +723,7 @@ const cached_getWebhookSummary = unstable_cache(
},
);
export function getWebhookSummary(
params: AnalyticsQueryParams & { webhookId: string },
authToken: string,
) {
return cached_getWebhookSummary(normalizedParams(params), authToken);
}
const cached_getWebhookRequests = unstable_cache(
async (
params: AnalyticsQueryParams & { webhookId?: string },
authToken: string,
): Promise<{ data: WebhookRequestStats[] } | { error: string }> => {
const searchParams = buildSearchParams(params);
if (params.webhookId) {
searchParams.append("webhookId", params.webhookId);
}
const res = await fetchAnalytics({
authToken,
url: `v2/webhook/requests?${searchParams.toString()}`,
init: {
method: "GET",
},
});
if (!res.ok) {
const reason = await res.text();
return { error: reason };
}
return (await res.json()) as { data: WebhookRequestStats[] };
},
["getWebhookRequests"],
{
revalidate: 60 * 60, // 1 hour
},
);
export function getWebhookRequests(
params: AnalyticsQueryParams & { webhookId?: string },
authToken: string,
) {
return cached_getWebhookRequests(normalizedParams(params), authToken);
}
const cached_getWebhookLatency = unstable_cache(
const _cached_getWebhookLatency = unstable_cache(
async (
params: AnalyticsQueryParams & { webhookId?: string },
authToken: string,
@@ -762,13 +753,6 @@ const cached_getWebhookLatency = unstable_cache(
},
);
export function getWebhookLatency(
params: AnalyticsQueryParams & { webhookId?: string },
authToken: string,
) {
return cached_getWebhookLatency(normalizedParams(params), authToken);
}
const cached_getInsightChainUsage = unstable_cache(
async (
params: AnalyticsQueryParams,
@@ -1,362 +0,0 @@
"use server";
import { getAuthToken } from "@/api/auth-token";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
export interface WebhookConfig {
id: string;
teamId: string;
projectId: string;
destinationUrl: string;
description: string;
pausedAt: string | null;
webhookSecret: string;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
topics: {
id: string;
serviceName: string;
description: string;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
filters: object | null;
}[];
}
type WebhookConfigsResponse =
| {
data: WebhookConfig[];
status: "success";
}
| {
body: string;
reason: string;
status: "error";
};
interface CreateWebhookConfigRequest {
topicIdsWithFilters: { id: string; filters: object | null }[];
destinationUrl: string;
description: string;
isPaused?: boolean;
}
type CreateWebhookConfigResponse =
| {
data: WebhookConfig;
status: "success";
}
| {
body: string;
reason: string;
status: "error";
};
export interface Topic {
id: string;
serviceName: string;
description: string;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
}
type TopicsResponse =
| {
data: Topic[];
status: "success";
}
| {
body: string;
reason: string;
status: "error";
};
interface UpdateWebhookConfigRequest {
destinationUrl?: string;
topicIdsWithFilters?: { id: string; filters: object | null }[];
description?: string;
isPaused?: boolean;
}
type UpdateWebhookConfigResponse =
| {
data: WebhookConfig;
status: "success";
}
| {
body: string;
reason: string;
status: "error";
};
type DeleteWebhookConfigResponse =
| {
data: WebhookConfig;
status: "success";
}
| {
body: string;
reason: string;
status: "error";
};
export async function getWebhookConfigs(props: {
teamIdOrSlug: string;
projectIdOrSlug: string;
}): Promise<WebhookConfigsResponse> {
const authToken = await getAuthToken();
if (!authToken) {
return {
body: "Authentication required",
reason: "no_auth_token",
status: "error",
};
}
const response = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${props.teamIdOrSlug}/projects/${props.projectIdOrSlug}/webhook-configs`,
{
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
},
method: "GET",
},
);
if (!response.ok) {
const body = await response.text();
return {
body,
reason: "unknown",
status: "error",
};
}
const result = await response.json();
return {
data: result.data,
status: "success",
};
}
export async function createWebhookConfig(props: {
teamIdOrSlug: string;
projectIdOrSlug: string;
config: CreateWebhookConfigRequest;
}): Promise<CreateWebhookConfigResponse> {
const authToken = await getAuthToken();
if (!authToken) {
return {
body: "Authentication required",
reason: "no_auth_token",
status: "error",
};
}
const response = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${props.teamIdOrSlug}/projects/${props.projectIdOrSlug}/webhook-configs`,
{
body: JSON.stringify(props.config),
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
},
method: "POST",
},
);
if (!response.ok) {
const body = await response.text();
return {
body,
reason: "unknown",
status: "error",
};
}
const result = await response.json();
return {
data: result.data,
status: "success",
};
}
export async function getAvailableTopics(): Promise<TopicsResponse> {
const authToken = await getAuthToken();
if (!authToken) {
return {
body: "Authentication required",
reason: "no_auth_token",
status: "error",
};
}
const response = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/webhook-topics`,
{
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
},
method: "GET",
},
);
if (!response.ok) {
const body = await response.text();
return {
body,
reason: "unknown",
status: "error",
};
}
const result = await response.json();
return {
data: result.data,
status: "success",
};
}
export async function updateWebhookConfig(props: {
teamIdOrSlug: string;
projectIdOrSlug: string;
webhookConfigId: string;
config: UpdateWebhookConfigRequest;
}): Promise<UpdateWebhookConfigResponse> {
const authToken = await getAuthToken();
if (!authToken) {
return {
body: "Authentication required",
reason: "no_auth_token",
status: "error",
};
}
const response = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${props.teamIdOrSlug}/projects/${props.projectIdOrSlug}/webhook-configs/${props.webhookConfigId}`,
{
body: JSON.stringify(props.config),
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
},
method: "PATCH",
},
);
if (!response.ok) {
const body = await response.text();
return {
body,
reason: "unknown",
status: "error",
};
}
const result = await response.json();
return {
data: result.data,
status: "success",
};
}
export async function deleteWebhookConfig(props: {
teamIdOrSlug: string;
projectIdOrSlug: string;
webhookConfigId: string;
}): Promise<DeleteWebhookConfigResponse> {
const authToken = await getAuthToken();
if (!authToken) {
return {
body: "Authentication required",
reason: "no_auth_token",
status: "error",
};
}
const response = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${props.teamIdOrSlug}/projects/${props.projectIdOrSlug}/webhook-configs/${props.webhookConfigId}`,
{
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
},
method: "DELETE",
},
);
if (!response.ok) {
const body = await response.text();
return {
body,
reason: "unknown",
status: "error",
};
}
const result = await response.json();
return {
data: result.data,
status: "success",
};
}
type TestDestinationUrlResponse =
| {
result: {
httpStatusCode: number;
httpResponseBody: string;
};
status: "success";
}
| {
body: string;
reason: string;
status: "error";
};
export async function testDestinationUrl(props: {
teamIdOrSlug: string;
projectIdOrSlug: string;
destinationUrl: string;
}): Promise<TestDestinationUrlResponse> {
const authToken = await getAuthToken();
if (!authToken) {
return {
body: "Authentication required",
reason: "no_auth_token",
status: "error",
};
}
const resp = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${props.teamIdOrSlug}/projects/${props.projectIdOrSlug}/webhook-configs/test-destination-url`,
{
body: JSON.stringify({ destinationUrl: props.destinationUrl }),
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
},
method: "POST",
},
);
if (!resp.ok) {
return {
body: await resp.text(),
reason: "unknown",
status: "error",
};
}
return await resp.json();
}
@@ -0,0 +1,35 @@
import { NEXT_PUBLIC_DASHBOARD_CLIENT_ID } from "@/constants/public-envs";
import { UB_BASE_URL } from "./constants";
import type { TokenMetadata } from "./types";
export async function getUniversalBridgeTokens(props: {
chainId?: number;
address?: string;
}) {
const url = new URL(`${UB_BASE_URL}/v1/tokens`);
if (props.chainId) {
url.searchParams.append("chainId", String(props.chainId));
}
if (props.address) {
url.searchParams.append("tokenAddress", props.address);
}
url.searchParams.append("limit", "1000");
url.searchParams.append("includePrices", "false");
const res = await fetch(url.toString(), {
headers: {
"Content-Type": "application/json",
"x-client-id": NEXT_PUBLIC_DASHBOARD_CLIENT_ID,
} as Record<string, string>,
method: "GET",
});
if (!res.ok) {
const text = await res.text();
throw new Error(text);
}
const json = await res.json();
return json.data as Array<TokenMetadata>;
}
@@ -1,48 +1,8 @@
"use server";
import type { ProjectResponse } from "@thirdweb-dev/service-utils";
import { getAuthToken } from "@/api/auth-token";
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/server-envs";
import { UB_BASE_URL } from "./constants";
export type TokenMetadata = {
name: string;
symbol: string;
address: string;
decimals: number;
chainId: number;
iconUri?: string;
};
export async function getUniversalBridgeTokens(props: {
chainId?: number;
address?: string;
}) {
const url = new URL(`${UB_BASE_URL}/v1/tokens`);
if (props.chainId) {
url.searchParams.append("chainId", String(props.chainId));
}
if (props.address) {
url.searchParams.append("tokenAddress", props.address);
}
url.searchParams.append("limit", "1000");
const res = await fetch(url.toString(), {
headers: {
"Content-Type": "application/json",
"x-secret-key": DASHBOARD_THIRDWEB_SECRET_KEY,
} as Record<string, string>,
method: "GET",
});
if (!res.ok) {
const text = await res.text();
throw new Error(text);
}
const json = await res.json();
return json.data as Array<TokenMetadata>;
}
import type { TokenMetadata } from "./types";
export async function addUniversalBridgeTokenRoute(props: {
chainId: number;
@@ -0,0 +1,8 @@
export type TokenMetadata = {
name: string;
symbol: string;
address: string;
decimals: number;
chainId: number;
iconUri?: string;
};
@@ -22,7 +22,7 @@ export function ResponsiveTimeFilters(props: { defaultRange: DurationId }) {
});
return (
<div className="flex justify-end gap-3 flex-col lg:flex-row">
<div className="flex gap-3 flex-row">
<DateRangeSelector
className="rounded-full"
range={range}
@@ -7,7 +7,7 @@ import { toast } from "sonner";
import { reSubscribePlan } from "@/actions/billing";
import type { Team } from "@/api/team/get-team";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { pollWithTimeout } from "@/utils/pollWithTimeout";
import { tryCatch } from "@/utils/try-catch";
@@ -10,7 +10,7 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { cn } from "../../lib/utils";
import { DynamicHeight } from "../ui/DynamicHeight";
@@ -3,7 +3,7 @@ import { DownloadIcon } from "lucide-react";
import Papa from "papaparse";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { cn } from "../../lib/utils";
export function ExportToCSVButton(props: {
+1 -85
View File
@@ -1,87 +1,3 @@
/* eslint-disable @next/next/no-img-element */
"use client";
import { useRef, useState } from "react";
import { cn } from "@/lib/utils";
import { useIsomorphicLayoutEffect } from "../../lib/useIsomorphicLayoutEffect";
type imgElementProps = React.DetailedHTMLProps<
React.ImgHTMLAttributes<HTMLImageElement>,
HTMLImageElement
> & {
skeleton?: React.ReactNode;
fallback?: React.ReactNode;
src: string | undefined;
containerClassName?: string;
};
export function Img(props: imgElementProps) {
const [_status, setStatus] = useState<"pending" | "fallback" | "loaded">(
"pending",
);
const status =
props.src === undefined
? "pending"
: props.src === ""
? "fallback"
: _status;
const { className, fallback, skeleton, containerClassName, ...restProps } =
props;
const defaultSkeleton = <div className="animate-pulse bg-accent" />;
const defaultFallback = <div className="bg-accent" />;
const imgRef = useRef<HTMLImageElement>(null);
useIsomorphicLayoutEffect(() => {
const imgEl = imgRef.current;
if (!imgEl) {
return;
}
if (imgEl.complete) {
setStatus("loaded");
} else {
function handleLoad() {
setStatus("loaded");
}
imgEl.addEventListener("load", handleLoad);
return () => {
imgEl.removeEventListener("load", handleLoad);
};
}
}, []);
return (
<div className={cn("relative shrink-0", containerClassName)}>
<img
{...restProps}
// avoid setting empty src string to prevent request to the entire page
alt={restProps.alt || ""}
className={cn(
"fade-in-0 object-cover transition-opacity duration-300",
className,
)}
decoding="async"
onError={() => {
setStatus("fallback");
}}
ref={imgRef}
src={restProps.src || undefined}
style={{
opacity: status === "loaded" ? 1 : 0,
...restProps.style,
}}
/>
{status !== "loaded" && (
<div
className={cn(
"fade-in-0 absolute inset-0 overflow-hidden transition-opacity duration-300 [&>*]:h-full [&>*]:w-full",
className,
)}
style={restProps.style}
>
{status === "pending" && (skeleton || defaultSkeleton)}
{status === "fallback" && (fallback || defaultFallback)}
</div>
)}
</div>
);
}
export { Img } from "@workspace/ui/components/img";
@@ -1,6 +1,6 @@
import type React from "react";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { cn } from "@/lib/utils";
export function RouteDiscoveryCard(
@@ -1,6 +1,6 @@
import type React from "react";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { cn } from "@/lib/utils";
export function SettingsCard(
@@ -19,7 +19,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { Separator } from "@/components/ui/separator";
import {
Table,
@@ -5,16 +5,16 @@ import {
type ThirdwebClient,
} from "thirdweb";
import { shortenAddress } from "thirdweb/utils";
import type { TokenMetadata } from "@/api/universal-bridge/tokens";
import type { TokenMetadata } from "@/api/universal-bridge/types";
import { Img } from "@/components/blocks/Img";
import { SelectWithSearch } from "@/components/blocks/select-with-search";
import { Badge } from "@/components/ui/badge";
import { Spinner } from "@/components/ui/Spinner";
import { fallbackChainIcon } from "@/constants/chain";
import { useAllChainsData } from "@/hooks/chains/allChains";
import { useTokensData } from "@/hooks/tokens";
import { cn } from "@/lib/utils";
import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler";
import { Spinner } from "../ui/Spinner/Spinner";
type Option = { label: string; value: string };
@@ -1,6 +1,6 @@
import { useMemo } from "react";
import { CopyTextButton } from "@/components/ui/CopyTextButton";
import { ScrollShadow } from "@/components/ui/ScrollShadow/ScrollShadow";
import { ScrollShadow } from "@/components/ui/ScrollShadow";
export function UnexpectedValueErrorMessage(props: {
value: unknown;
@@ -91,7 +91,7 @@ export function FullWidthSidebarLayout(props: {
<main className="flex min-w-0 grow flex-col max-sm:w-full">
{children}
</main>
<AppFooter containerClassName="max-w-7xl" />
<AppFooter />
</div>
</div>
);
@@ -1,225 +1 @@
import { onlyText } from "react-children-utilities";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { CodeClient } from "@/components/ui/code/code.client";
import { PlainTextCodeBlock } from "@/components/ui/code/plaintext-code";
import { InlineCode } from "@/components/ui/inline-code";
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { UnderlineLink } from "@/components/ui/UnderlineLink";
import { cn } from "@/lib/utils";
export const MarkdownRenderer: React.FC<{
markdownText: string;
className?: string;
code?: {
disableCodeHighlight?: boolean;
ignoreFormattingErrors?: boolean;
className?: string;
};
inlineCode?: {
className?: string;
};
p?: {
className?: string;
};
li?: {
className?: string;
};
skipHtml?: boolean;
}> = (markdownProps) => {
const { markdownText, className, code } = markdownProps;
const commonHeadingClassName = "mb-2 leading-5 font-semibold tracking-tight";
return (
<div className={className}>
<ReactMarkdown
components={{
a: (props) => (
<UnderlineLink
href={props.href ?? "#"}
rel="noopener noreferrer"
target="_blank"
{...cleanedProps(props)}
className="mt-4"
/>
),
code: ({ ...props }) => {
const codeStr = onlyText(props.children);
if (props?.className || codeStr.length > 100) {
if (code?.disableCodeHighlight || !props.className) {
return (
<div className="my-4">
{/* @ts-expect-error - TODO: fix this */}
<PlainTextCodeBlock
{...cleanedProps(props)}
className={markdownProps.code?.className}
code={onlyText(props.children).trim()}
/>
</div>
);
}
const language = props.className.replace("language-", "");
return (
<div className="my-4">
<CodeClient
// @ts-expect-error - TODO: fix this
lang={language}
{...cleanedProps(props)}
className={markdownProps.code?.className}
code={onlyText(props.children).trim()}
ignoreFormattingErrors={code?.ignoreFormattingErrors}
/>
</div>
);
}
return (
<InlineCode
className={markdownProps.inlineCode?.className}
code={onlyText(props.children).trim()}
/>
);
},
h1: (props) => (
<h2
className={cn(
commonHeadingClassName,
"mb-3 border-dashed border-b pb-2 text-xl md:text-2xl",
)}
{...cleanedProps(props)}
/>
),
h2: (props) => (
<h3
{...cleanedProps(props)}
className={cn(
commonHeadingClassName,
"mt-8 mb-3 border-dashed border-b pb-2 text-lg md:text-xl",
)}
/>
),
h3: (props) => (
<h4
{...cleanedProps(props)}
className={cn(
commonHeadingClassName,
"mt-4 text-base md:text-lg",
)}
/>
),
h4: (props) => (
<h5
{...cleanedProps(props)}
className={cn(commonHeadingClassName, "mt-4 text-lg")}
/>
),
h5: (props) => (
<h6
{...cleanedProps(props)}
className={cn(commonHeadingClassName, "mt-4 text-lg")}
/>
),
h6: (props) => (
<p
{...cleanedProps(props)}
className={cn(commonHeadingClassName, "mt-4 text-lg")}
/>
),
hr: (props) => (
<hr {...cleanedProps(props)} className="my-5 bg-border" />
),
li: ({ children: c, ...props }) => (
<li
className={cn(
"text-muted-foreground leading-relaxed [&>p]:m-0",
markdownProps.li?.className,
)}
{...cleanedProps(props)}
>
{c}
</li>
),
ol: (props) => (
<ol
className="mb-4 list-outside list-decimal pl-4 space-y-2 [&>li]:first:mt-2"
{...cleanedProps(props)}
/>
),
p: (props) => (
<p
className={cn(
"mb-3 text-muted-foreground leading-7",
markdownProps.p?.className,
)}
{...cleanedProps(props)}
/>
),
strong(props) {
return <strong className="font-medium" {...cleanedProps(props)} />;
},
table: (props) => (
<div className="mb-6">
<TableContainer>
<Table {...cleanedProps(props)} />
</TableContainer>
</div>
),
tbody: (props) => <TableBody {...cleanedProps(props)} />,
td: (props) => (
<TableCell {...cleanedProps(props)} className="text-left" />
),
th: ({ children: c, ...props }) => (
<TableHead
{...cleanedProps(props)}
className="text-left text-muted-foreground"
>
{c}
</TableHead>
),
thead: (props) => <TableHeader {...cleanedProps(props)} />,
tr: (props) => <TableRow {...cleanedProps(props)} />,
ul: (props) => {
return (
<ul
className="mb-4 list-outside list-disc pl-4 space-y-2 [&>li]:first:mt-2"
{...cleanedProps(props)}
/>
);
},
}}
remarkPlugins={[remarkGfm]}
skipHtml={markdownProps.skipHtml}
>
{markdownText}
</ReactMarkdown>
</div>
);
};
function cleanedProps<T extends object & { node?: unknown }>(
props: T,
): Omit<T, "node"> {
// biome-ignore lint/correctness/noUnusedVariables: EXPECTED
const { node, ...rest } = props;
return rest;
}
export { MarkdownRenderer } from "@workspace/ui/components/markdown-renderer";
@@ -10,7 +10,7 @@ import {
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { ScrollShadow } from "@/components/ui/ScrollShadow/ScrollShadow";
import { ScrollShadow } from "@/components/ui/ScrollShadow";
import { Separator } from "@/components/ui/separator";
import { useShowMore } from "@/hooks/useShowMore";
import { cn } from "@/lib/utils";
@@ -7,8 +7,8 @@ import {
RefreshCwIcon,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { DynamicHeight } from "../../ui/DynamicHeight";
import { Spinner } from "../../ui/Spinner/Spinner";
import { DynamicHeight } from "@/components/ui/DynamicHeight";
import { Spinner } from "@/components/ui/Spinner";
export type MultiStepState<T extends string> = {
id: T;
@@ -0,0 +1,119 @@
"use client";
import { Button } from "@workspace/ui/components/button";
import {
BookTextIcon,
BoxIcon,
ChevronDownIcon,
CodeIcon,
WebhookIcon,
} from "lucide-react";
import Link from "next/link";
import { useMemo } from "react";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
type LinkType = "api" | "docs" | "playground" | "webhooks";
const linkTypeToLabel: Record<LinkType, string> = {
api: "API Reference",
docs: "Documentation",
playground: "Playground",
webhooks: "Webhooks",
};
const linkTypeToOrder: Record<LinkType, number> = {
docs: 0,
playground: 1,
api: 3,
webhooks: 4,
};
const linkTypeToIcon: Record<LinkType, React.FC<{ className?: string }>> = {
api: CodeIcon,
docs: BookTextIcon,
playground: BoxIcon,
webhooks: WebhookIcon,
};
function orderLinks(links: ActionLink[]) {
return links.slice().sort((a, b) => {
const aIndex = linkTypeToOrder[a.type];
const bIndex = linkTypeToOrder[b.type];
return aIndex - bIndex;
});
}
export type ActionLink = {
type: LinkType;
href: string;
};
export function LinkGroup(props: { links: ActionLink[] }) {
const orderedLinks = useMemo(() => orderLinks(props.links), [props.links]);
if (orderedLinks.length === 1 && orderedLinks[0]) {
const link = orderedLinks[0];
const Icon = linkTypeToIcon[link.type];
return (
<Link
href={link.href}
target={link.href.startsWith("http") ? "_blank" : undefined}
>
<Button
variant="outline"
size="sm"
className="rounded-full border gap-2 bg-card"
>
<Icon className="size-3.5 text-muted-foreground" />
{linkTypeToLabel[link.type]}
</Button>
</Link>
);
}
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="sm"
className="rounded-full border gap-2 bg-card [&[data-state=open]>svg]:rotate-180"
>
Resources
<ChevronDownIcon className="size-4 transition-transform duration-200 text-muted-foreground" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="gap-1 flex flex-col w-48 rounded-xl"
sideOffset={10}
>
{orderedLinks.map((link) => {
const isExternal = link.href.startsWith("http");
const Icon = linkTypeToIcon[link.type];
return (
<DropdownMenuItem
key={link.type}
asChild
className="flex flex-row items-center gap-2 cursor-pointer py-1.5"
>
<Link
href={link.href}
target={isExternal ? "_blank" : undefined}
rel={isExternal ? "noopener noreferrer" : undefined}
>
<Icon className="size-4 text-muted-foreground" />
{linkTypeToLabel[link.type]}
</Link>
</DropdownMenuItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
);
}
@@ -0,0 +1,14 @@
import {
type FooterCardProps,
FooterLinksSection,
} from "../../../../app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/footer/FooterLinksSection";
export type ProjectPageFooterProps = FooterCardProps;
export function ProjectPageFooter(props: ProjectPageFooterProps) {
return (
<footer className="container max-w-7xl">
<FooterLinksSection {...props} />
</footer>
);
}
@@ -0,0 +1,155 @@
import { Button } from "@workspace/ui/components/button";
import { ArrowUpRightIcon, Settings2Icon } from "lucide-react";
import Link from "next/link";
import type { ThirdwebClient } from "thirdweb";
import { cn } from "@/lib/utils";
import { type ActionLink, LinkGroup } from "./header/link-group";
type Action =
| ({
label: string;
href: string;
} & (
| {
external: true;
icon?: never;
}
| {
icon?: React.ReactNode;
external?: false;
}
))
| {
component: React.ReactNode;
};
function Action(props: { action: Action; variant?: "default" | "secondary" }) {
const action = props.action;
return "component" in action ? (
action.component
) : (
<Button
asChild
className={cn(
"rounded-full",
props.variant === "secondary" && "border border-border",
)}
size="sm"
variant={props.variant}
>
<Link
href={action.href}
target={action.external ? "_blank" : undefined}
rel={action.external ? "noopener noreferrer" : undefined}
className="flex flex-row items-center gap-2"
>
{action.icon}
{action.label}
{action.external && <ArrowUpRightIcon className="size-4" />}
</Link>
</Button>
);
}
export type ProjectPageHeaderProps = {
client: ThirdwebClient;
title: string;
description?: React.ReactNode;
imageUrl?: string | null;
icon: React.FC<{ className?: string }>;
isProjectIcon?: boolean;
actions: {
primary: Action;
secondary?: Action;
} | null;
links?: ActionLink[];
settings?: {
href: string;
};
// TODO: add task card component
task?: never;
};
export function ProjectPageHeader(props: ProjectPageHeaderProps) {
return (
<header className="container max-w-7xl py-6 relative">
{/* top row */}
<div className="flex justify-between items-start mb-4">
{/* left - icon */}
<div className="flex">
{props.isProjectIcon ? (
<props.icon />
) : (
<div className="border rounded-full p-2.5 bg-card">
<props.icon className="size-5 text-muted-foreground" />
</div>
)}
</div>
{/* right - buttons */}
<div className="flex items-center gap-3">
<div className="flex items-center gap-3">
{props.links && props.links.length > 0 && (
<LinkGroup links={props.links} />
)}
{props.settings && (
<Link href={props.settings.href}>
<Button
variant="outline"
size="sm"
className="rounded-full gap-2 bg-card"
>
<Settings2Icon className="size-4 text-muted-foreground" />
Settings
</Button>
</Link>
)}
</div>
{/* hide on mobile */}
{props.actions && (
<div className="hidden lg:flex items-center gap-3">
{props.actions.secondary && (
<Action action={props.actions.secondary} variant="secondary" />
)}
{props.actions.primary && (
<Action action={props.actions.primary} />
)}
</div>
)}
</div>
</div>
<div className="space-y-4">
{/* mid row */}
<div className="space-y-1 max-w-3xl">
<h2 className="text-3xl font-semibold tracking-tight">
{props.title}
</h2>
{/* description */}
<p className="text-sm text-muted-foreground line-clamp-3 md:line-clamp-2">
{props.description}
</p>
</div>
{/* bottom row - hidden on desktop */}
{props.actions && (
<div className="flex items-center gap-3 lg:hidden">
{props.actions?.primary && (
<Action action={props.actions.primary} />
)}
{props.actions?.secondary && (
<Action action={props.actions.secondary} variant="secondary" />
)}
</div>
)}
</div>
</header>
);
}
@@ -0,0 +1,44 @@
import { cn } from "@/lib/utils";
import { type TabPathLink, TabPathLinks } from "../../ui/tabs";
import {
ProjectPageFooter,
type ProjectPageFooterProps,
} from "./project-page-footer";
import {
ProjectPageHeader,
type ProjectPageHeaderProps,
} from "./project-page-header";
type ProjectPageProps = {
header: ProjectPageHeaderProps;
footer?: ProjectPageFooterProps;
/**
* @deprecated only for legacy pages where we still need internal tabs for the moment, currently:
* - /webhooks
*/
tabs?: TabPathLink[];
};
export function ProjectPage(props: React.PropsWithChildren<ProjectPageProps>) {
return (
<div className="flex flex-col grow">
<div className={cn(!props.tabs && "border-b")}>
<ProjectPageHeader {...props.header} />
{props.tabs && (
<TabPathLinks
scrollableClassName="container max-w-7xl"
links={props.tabs}
/>
)}
</div>
<main className="container max-w-7xl pt-6">{props.children}</main>
<div className="h-20" />
{props.footer && (
<div className="border-t mt-auto">
<ProjectPageFooter {...props.footer} />
</div>
)}
</div>
);
}
@@ -10,7 +10,7 @@ import {
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { ScrollShadow } from "@/components/ui/ScrollShadow/ScrollShadow";
import { ScrollShadow } from "@/components/ui/ScrollShadow";
import { useShowMore } from "@/hooks/useShowMore";
import { cn } from "@/lib/utils";
@@ -1,6 +1,6 @@
"use client";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { cn } from "@/lib/utils";
export function GenericLoadingPage({ className }: { className?: string }) {
@@ -48,7 +48,7 @@ export function WalletAddress(props: {
return (
<ToolTipLabel hoverable label={address}>
<span className="flex items-center gap-2 underline-offset-4 hover:underline">
<div className="flex size-6 items-center justify-center rounded-full border bg-background">
<div className="flex size-5 items-center justify-center rounded-full border bg-background">
<XIcon className="size-4 text-muted-foreground" />
</div>
Invalid Address
@@ -62,7 +62,7 @@ export function WalletAddress(props: {
return (
<div className="flex items-center gap-2 py-2">
<CircleSlashIcon
className={cn("size-6 text-muted-foreground/70", props.iconClassName)}
className={cn("size-5 text-muted-foreground/70", props.iconClassName)}
/>
<span
className={cn("cursor-pointer font-mono text-sm", props.className)}
@@ -208,6 +208,13 @@ function WalletAvatar(props: {
<Img
className={cn("size-5 object-cover", props.iconClassName)}
src={resolvedAvatarSrc}
fallback={
<Blobbie
address={props.address}
className={props.iconClassName}
size={20}
/>
}
/>
) : props.fallbackIcon ? (
props.fallbackIcon
@@ -215,7 +222,7 @@ function WalletAvatar(props: {
<Blobbie
address={props.address}
className={props.iconClassName}
size={24}
size={20}
/>
)}
</div>
@@ -15,7 +15,7 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { ScrollShadow } from "@/components/ui/ScrollShadow/ScrollShadow";
import { ScrollShadow } from "@/components/ui/ScrollShadow";
import { cn } from "@/lib/utils";
import { ThirdwebMiniLogo } from "../../../app/(app)/components/ThirdwebMiniLogo";
import { SupportTicketForm } from "../../../app/(app)/team/[team_slug]/(team)/~/support/_components/SupportTicketForm";
@@ -413,15 +413,15 @@ function StyledMarkdownRenderer(props: {
}) {
return (
<MarkdownRenderer
className="[&>*:first-child]:mt-0 [&>*:first-child]:border-none [&>*:first-child]:pb-0 [&>*:last-child]:mb-0"
className="text-sm text-foreground [&>*:first-child]:mt-0 [&>*:first-child]:border-none [&>*:first-child]:pb-0 [&>*:last-child]:mb-0 leading-relaxed"
code={{
className: "bg-card",
ignoreFormattingErrors: true,
}}
inlineCode={{ className: "border-none" }}
li={{ className: "text-foreground leading-relaxed" }}
markdownText={props.text}
p={{
className: props.type === "assistant" ? "" : "leading-normal",
className: "text-foreground leading-relaxed",
}}
skipHtml
/>
@@ -22,8 +22,8 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { ScrollShadow } from "@/components/ui/ScrollShadow/ScrollShadow";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { ScrollShadow } from "@/components/ui/ScrollShadow";
import { Spinner } from "@/components/ui/Spinner";
import { ChainIconClient } from "@/icons/ChainIcon";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { fetchTopContracts, type TrendingContract } from "@/lib/search";
@@ -17,7 +17,7 @@ import {
DropdownMenuContent,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { SkeletonContainer } from "@/components/ui/skeleton";
import {
Table,
@@ -31,7 +31,7 @@ import { Button } from "@/components/ui/button";
import { CodeClient } from "@/components/ui/code/code.client";
import { PlainTextCodeBlock } from "@/components/ui/code/plaintext-code";
import { Input } from "@/components/ui/input";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { Skeleton } from "@/components/ui/skeleton";
import { ToolTipLabel } from "@/components/ui/tooltip";
@@ -27,7 +27,7 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { useChainSlug } from "@/hooks/chains/chainSlug";
import { useAddContractToProject } from "@/hooks/project-contracts";
import { useDashboardRouter } from "@/lib/DashboardRouter";
@@ -4,7 +4,7 @@ import { SearchIcon, XIcon } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import {
Select,
SelectContent,
@@ -10,7 +10,7 @@ import type { WalletUser } from "thirdweb/wallets";
import { TWTable } from "@/components/blocks/TWTable";
import { WalletAddress } from "@/components/blocks/wallet-address";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import {
ToolTipLabel,
Tooltip,
@@ -2,7 +2,7 @@ import { SettingsIcon } from "lucide-react";
import type { ThirdwebClient } from "thirdweb";
import type { UseNetworkSwitcherModalOptions } from "thirdweb/react";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { OPSponsoredChains } from "@/constants/chains";
import { useAllChainsData } from "@/hooks/chains/allChains";
import { ChainIconClient } from "@/icons/ChainIcon";
@@ -7,7 +7,7 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { ScrollShadow } from "@/components/ui/ScrollShadow/ScrollShadow";
import { ScrollShadow } from "@/components/ui/ScrollShadow";
import { useAllChainsData } from "@/hooks/chains/allChains";
import { ChainIconClient } from "@/icons/ChainIcon";
import { useStore } from "@/lib/reactive";
@@ -5,7 +5,7 @@ import { toast } from "sonner";
import type { ThirdwebClient } from "thirdweb";
import { FileInput } from "@/components/blocks/FileInput";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { useDashboardStorageUpload } from "@/hooks/useDashboardStorageUpload";
export const IconUpload: React.FC<{
@@ -32,7 +32,7 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { Textarea } from "@/components/ui/textarea";
import { createProjectClient } from "@/hooks/useApi";
import { useDashboardRouter } from "@/lib/DashboardRouter";
@@ -5,7 +5,7 @@ import { useEffect, useMemo, useState } from "react";
import { useActiveAccount } from "thirdweb/react";
import { isAddress, isValidENSName } from "thirdweb/utils";
import { Input } from "@/components/ui/input";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { useEns } from "@/hooks/contract-hooks";
import type { SolidityInputProps } from ".";
import { validateAddress } from "./helpers";
@@ -41,7 +41,7 @@ import {
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { LOCAL_NODE_PKEY } from "@/constants/misc";
import { useAllChainsData } from "@/hooks/chains/allChains";
import { useV5DashboardChain } from "@/hooks/chains/v5-adapter";
@@ -0,0 +1,3 @@
"use client";
export { ScrollShadow } from "@workspace/ui/components/scroll-shadow";
@@ -1,36 +0,0 @@
.scrollShadowY {
position: absolute;
left: 0;
width: 100%;
height: 32px;
pointer-events: none;
}
.scrollShadowX {
position: absolute;
top: 0;
width: 32px;
height: 100%;
pointer-events: none;
}
.scrollShadowTop {
top: 0;
background: linear-gradient(to bottom, var(--shadow), transparent);
opacity: 0;
}
.scrollShadowBottom {
bottom: 0;
background: linear-gradient(to top, var(--shadow), transparent);
}
.scrollShadowLeft {
left: 0;
background: linear-gradient(to right, var(--shadow), transparent);
}
.scrollShadowRight {
right: 0;
background: linear-gradient(to left, var(--shadow), transparent);
}
@@ -1,151 +0,0 @@
"use client";
import { useRef } from "react";
import { useIsomorphicLayoutEffect } from "@/lib/useIsomorphicLayoutEffect";
import { cn } from "@/lib/utils";
import styles from "./ScrollShadow.module.css";
export function ScrollShadow(props: {
children: React.ReactNode;
className?: string;
scrollableClassName?: string;
disableTopShadow?: boolean;
shadowColor?: string;
shadowClassName?: string;
}) {
const scrollableEl = useRef<HTMLDivElement>(null);
const shadowTopEl = useRef<HTMLDivElement>(null);
const shadowBottomEl = useRef<HTMLDivElement>(null);
const shadowLeftEl = useRef<HTMLDivElement>(null);
const shadowRightEl = useRef<HTMLDivElement>(null);
const wrapperEl = useRef<HTMLDivElement>(null);
useIsomorphicLayoutEffect(() => {
const content = scrollableEl.current;
const shadowTop = shadowTopEl.current;
const shadowBottom = shadowBottomEl.current;
const wrapper = wrapperEl.current;
const shadowLeft = shadowLeftEl.current;
const shadowRight = shadowRightEl.current;
if (!content || !shadowTop || !shadowBottom || !wrapper) {
return;
}
function handleScroll() {
if (
!content ||
!shadowTop ||
!shadowBottom ||
!shadowLeft ||
!shadowRight ||
!wrapper
) {
return;
}
const contentScrollHeight = content.scrollHeight - wrapper.offsetHeight;
const contentScrollWidth = content.scrollWidth - wrapper.offsetWidth;
if (contentScrollHeight > 10) {
const currentScroll = content.scrollTop / contentScrollHeight;
shadowTop.style.opacity = `${currentScroll}`;
shadowBottom.style.opacity = `${1 - currentScroll}`;
} else {
shadowTop.style.opacity = "0";
shadowBottom.style.opacity = "0";
}
if (contentScrollWidth > 10) {
const currentScrollX = content.scrollLeft / contentScrollWidth;
shadowLeft.style.opacity = `${currentScrollX}`;
shadowRight.style.opacity = `${1 - currentScrollX}`;
} else {
shadowLeft.style.opacity = "0";
shadowRight.style.opacity = "0";
}
}
requestAnimationFrame(() => {
requestAnimationFrame(() => {
handleScroll();
});
});
content.addEventListener("scroll", handleScroll);
const resizeObserver = new ResizeObserver(() => {
handleScroll();
});
resizeObserver.observe(content);
return () => {
content.removeEventListener("scroll", handleScroll);
resizeObserver.disconnect();
};
}, []);
return (
<div
className={cn(props.className, "relative overflow-hidden")}
ref={wrapperEl}
style={
{
"--shadow": props.shadowColor || "hsl(var(--muted))",
} as React.CSSProperties
}
>
<div
className={cn(
styles.scrollShadowTop,
styles.scrollShadowY,
props.shadowClassName,
)}
ref={shadowTopEl}
style={{
display: props.disableTopShadow ? "none" : "block",
opacity: "0",
}}
/>
<div
className={cn(
styles.scrollShadowBottom,
styles.scrollShadowY,
props.shadowClassName,
)}
ref={shadowBottomEl}
style={{
opacity: "0",
}}
/>
<div
className={cn(
styles.scrollShadowLeft,
styles.scrollShadowX,
props.shadowClassName,
)}
ref={shadowLeftEl}
style={{
opacity: "0",
}}
/>
<div
className={cn(
styles.scrollShadowRight,
styles.scrollShadowX,
props.shadowClassName,
)}
ref={shadowRightEl}
style={{
opacity: "0",
}}
/>
<div
className={cn("no-scrollbar overflow-auto", props.scrollableClassName)}
data-scrollable
ref={scrollableEl}
>
{props.children}
</div>
</div>
);
}
@@ -0,0 +1 @@
export { Spinner } from "@workspace/ui/components/spinner";
@@ -1,37 +0,0 @@
.loader {
border-radius: 50%;
position: relative;
animation: rotate 2s linear infinite;
}
.loader circle {
content: "";
box-sizing: border-box;
position: absolute;
inset: 0px;
border-radius: 50%;
border: 4px solid #fff;
stroke-linecap: round;
animation: dash 1.5s ease-in-out infinite;
}
@keyframes rotate {
100% {
transform: rotate(360deg);
}
}
@keyframes dash {
0% {
stroke-dasharray: 1, 150;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -35;
}
100% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -124;
}
}
@@ -1,21 +0,0 @@
import { cn } from "@/lib/utils";
import style from "./Spinner.module.css";
export function Spinner(props: { className?: string }) {
return (
<svg
className={cn(style.loader, props.className || "size-4")}
viewBox="0 0 50 50"
>
<title>loading</title>
<circle
cx="25"
cy="25"
fill="none"
r="20"
stroke="currentColor"
strokeWidth="4"
/>
</svg>
);
}
@@ -1,16 +1 @@
import Link from "next/link";
import { cn } from "../../lib/utils";
type LinkProps = React.ComponentProps<typeof Link>;
export function UnderlineLink(props: LinkProps) {
return (
<Link
{...props}
className={cn(
"underline decoration-muted-foreground/50 decoration-dotted underline-offset-[5px] hover:text-foreground hover:decoration-foreground hover:decoration-solid",
props.className,
)}
/>
);
}
export { UnderlineLink } from "@workspace/ui/components/UnderlineLink";
+2 -39
View File
@@ -1,39 +1,2 @@
import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@/lib/utils";
const badgeVariants = cva(
"inline-flex items-center rounded-full border border-border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 leading-4",
{
defaultVariants: {
variant: "default",
},
variants: {
variant: {
default: "border-transparent bg-primary/20 text-primary",
destructive:
"border-transparent dark:bg-red-950 dark:text-red-400 bg-red-500/20 text-red-800",
outline: "text-foreground",
secondary:
"border-transparent bg-accent text-accent-foreground hover:bg-accent/80",
success:
"border-transparent dark:bg-green-950/50 dark:text-green-400 bg-green-200 text-green-950",
warning:
"border-transparent dark:bg-yellow-600/20 dark:text-yellow-500 bg-yellow-500/20 text-yellow-900",
},
},
},
);
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
);
}
export { Badge, badgeVariants };
export type { BadgeProps } from "@workspace/ui/components/badge";
export { Badge, badgeVariants } from "@workspace/ui/components/badge";
@@ -1,119 +1,9 @@
import { Slot } from "@radix-ui/react-slot";
import { ChevronRightIcon, MoreHorizontalIcon } from "lucide-react";
import * as React from "react";
import { cn } from "@/lib/utils";
const Breadcrumb = React.forwardRef<
HTMLElement,
React.ComponentPropsWithoutRef<"nav"> & {
separator?: React.ReactNode;
}
>(({ ...props }, ref) => <nav aria-label="breadcrumb" ref={ref} {...props} />);
Breadcrumb.displayName = "Breadcrumb";
const BreadcrumbList = React.forwardRef<
HTMLOListElement,
React.ComponentPropsWithoutRef<"ol">
>(({ className, ...props }, ref) => (
<ol
className={cn(
"flex flex-wrap items-center gap-1.5 break-words text-muted-foreground text-sm sm:gap-2.5",
className,
)}
ref={ref}
{...props}
/>
));
BreadcrumbList.displayName = "BreadcrumbList";
const BreadcrumbItem = React.forwardRef<
HTMLLIElement,
React.ComponentPropsWithoutRef<"li">
>(({ className, ...props }, ref) => (
<li
className={cn("inline-flex items-center gap-1.5", className)}
ref={ref}
{...props}
/>
));
BreadcrumbItem.displayName = "BreadcrumbItem";
const BreadcrumbLink = React.forwardRef<
HTMLAnchorElement,
React.ComponentPropsWithoutRef<"a"> & {
asChild?: boolean;
}
>(({ asChild, className, ...props }, ref) => {
const Comp = asChild ? Slot : "a";
return (
<Comp
className={cn("transition-colors hover:text-foreground", className)}
ref={ref}
{...props}
/>
);
});
BreadcrumbLink.displayName = "BreadcrumbLink";
const BreadcrumbPage = React.forwardRef<
HTMLSpanElement,
React.ComponentPropsWithoutRef<"span">
>(({ className, ...props }, ref) => (
// biome-ignore lint/a11y/useFocusableInteractive: required
<span
aria-current="page"
aria-disabled="true"
className={cn("font-normal text-foreground", className)}
ref={ref}
role="link"
{...props}
/>
));
BreadcrumbPage.displayName = "BreadcrumbPage";
const BreadcrumbSeparator = ({
children,
className,
...props
}: React.ComponentProps<"li">) => (
<li
aria-hidden="true"
className={cn("[&>svg]:size-3.5", className)}
role="presentation"
{...props}
>
{children ?? <ChevronRightIcon />}
</li>
);
BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
const BreadcrumbEllipsis = ({
className,
...props
}: React.ComponentProps<"span">) => (
<span
aria-hidden="true"
className={cn(
"relative flex h-9 w-9 items-center justify-center",
className,
)}
role="presentation"
{...props}
>
<MoreHorizontalIcon className="h-4 w-4" />
<span className="sr-only">More</span>
</span>
);
BreadcrumbEllipsis.displayName = "BreadcrumbEllipsis";
export {
Breadcrumb,
BreadcrumbList,
BreadcrumbEllipsis,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbEllipsis,
};
} from "@workspace/ui/components/breadcrumb";
@@ -1,71 +1,3 @@
"use client";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import type { BundledLanguage } from "shiki";
import { getCodeHtml } from "./getCodeHtml";
import { PlainTextCodeBlock } from "./plaintext-code";
import { RenderCode } from "./RenderCode";
export type CodeProps = {
code: string;
lang: BundledLanguage;
className?: string;
scrollableClassName?: string;
keepPreviousDataOnCodeChange?: boolean;
copyButtonClassName?: string;
scrollableContainerClassName?: string;
shadowColor?: string;
ignoreFormattingErrors?: boolean;
onCopy?: (code: string) => void;
};
export const CodeClient: React.FC<CodeProps> = ({
code,
lang,
className,
scrollableClassName,
keepPreviousDataOnCodeChange = false,
copyButtonClassName,
ignoreFormattingErrors,
scrollableContainerClassName,
shadowColor,
onCopy,
}) => {
const codeQuery = useQuery({
placeholderData: keepPreviousDataOnCodeChange
? keepPreviousData
: undefined,
queryFn: () =>
getCodeHtml(code, lang, {
ignoreFormattingErrors: ignoreFormattingErrors,
}),
queryKey: ["html", code],
retry: false,
});
if (!codeQuery.data) {
return (
<PlainTextCodeBlock
className={className}
code={code}
copyButtonClassName={copyButtonClassName}
onCopy={onCopy}
scrollableClassName={scrollableClassName}
scrollableContainerClassName={scrollableContainerClassName}
shadowColor={shadowColor}
/>
);
}
return (
<RenderCode
className={className}
code={codeQuery.data.formattedCode}
copyButtonClassName={copyButtonClassName}
html={codeQuery.data.html}
onCopy={onCopy}
scrollableClassName={scrollableClassName}
scrollableContainerClassName={scrollableContainerClassName}
shadowColor={shadowColor}
/>
);
};
export { CodeClient } from "@workspace/ui/components/code/code.client";
@@ -1,22 +1,4 @@
import type { BundledLanguage } from "shiki";
import { getCodeHtml } from "./getCodeHtml";
import { RenderCode } from "./RenderCode";
export type CodeProps = {
code: string;
lang: BundledLanguage;
className?: string;
ignoreFormattingErrors?: boolean;
};
export const CodeServer: React.FC<CodeProps> = async ({
code,
lang,
className,
ignoreFormattingErrors,
}) => {
const { html, formattedCode } = await getCodeHtml(code, lang, {
ignoreFormattingErrors,
});
return <RenderCode className={className} code={formattedCode} html={html} />;
};
export {
type CodeProps,
CodeServer,
} from "@workspace/ui/components/code/code.server";
@@ -1,29 +1 @@
import { cn } from "@/lib/utils";
import { CodeBlockContainer } from "./CodeBlockContainer";
export function PlainTextCodeBlock(props: {
code: string;
copyButtonClassName?: string;
className?: string;
scrollableClassName?: string;
codeClassName?: string;
scrollableContainerClassName?: string;
shadowColor?: string;
onCopy?: (code: string) => void;
}) {
return (
<CodeBlockContainer
className={props.className}
codeToCopy={props.code}
copyButtonClassName={props.copyButtonClassName}
onCopy={props.onCopy}
scrollableClassName={props.scrollableClassName}
scrollableContainerClassName={props.scrollableContainerClassName}
shadowColor={props.shadowColor}
>
<code className={cn("block whitespace-pre", props.codeClassName)}>
{props.code}
</code>
</CodeBlockContainer>
);
}
export { PlainTextCodeBlock } from "@workspace/ui/components/code/plaintext-code";
+1 -25
View File
@@ -1,25 +1 @@
import * as React from "react";
import { cn } from "@/lib/utils";
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background selection:bg-inverted selection:text-inverted-foreground file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground placeholder:text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className,
)}
ref={ref}
type={type}
{...props}
/>
);
},
);
Input.displayName = "Input";
export { Input };
export { Input } from "@workspace/ui/components/input";
+5 -150
View File
@@ -1,156 +1,11 @@
import * as React from "react";
import { cn } from "@/lib/utils";
import { ScrollShadow } from "./ScrollShadow/ScrollShadow";
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<table
className={cn(
"w-full caption-bottom border-collapse align-top text-sm lining-nums tabular-nums",
className,
)}
ref={ref}
{...props}
/>
));
Table.displayName = "Table";
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead
className={cn("relative border-b bg-background", className)}
ref={ref}
{...props}
/>
));
TableHeader.displayName = "TableHeader";
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
className={cn("[&_tr:last-child]:border-0", className)}
ref={ref}
{...props}
/>
));
TableBody.displayName = "TableBody";
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
className={cn(
"border-border border-t bg-card font-medium [&>tr]:last:border-b-0",
className,
)}
ref={ref}
{...props}
/>
));
TableFooter.displayName = "TableFooter";
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement> & {
/**
* Contain the absolutely position elements inside the row with position:relative + transform:translate(0)
* transform:translate(0) is required because position:relative on tr element does not work on webkit
*/
linkBox?: boolean;
}
>(({ className, linkBox, ...props }, ref) => (
<tr
className={cn(
"border-border border-b last:border-0 data-[state=selected]:bg-muted",
linkBox && "relative translate-x-0 translate-y-0",
className,
)}
ref={ref}
{...props}
/>
));
TableRow.displayName = "TableRow";
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
className={cn(
"px-6 py-4 text-left align-middle font-medium text-muted-foreground text-xs uppercase tracking-wider [&:has([role=checkbox])]:pr-0",
className,
)}
ref={ref}
{...props}
/>
));
TableHead.displayName = "TableHead";
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
className={cn(
"px-6 py-4 text-start align-middle text-sm [&:has([role=checkbox])]:pr-0",
className,
)}
ref={ref}
{...props}
/>
));
TableCell.displayName = "TableCell";
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
className={cn("py-4 text-muted-foreground text-sm", className)}
ref={ref}
{...props}
/>
));
TableCaption.displayName = "TableCaption";
function TableContainer(props: {
children: React.ReactNode;
className?: string;
scrollableContainerClassName?: string;
}) {
return (
<ScrollShadow
className={cn(
"relative whitespace-nowrap rounded-lg border border-border bg-card",
props.className,
)}
disableTopShadow
scrollableClassName={props.scrollableContainerClassName}
shadowClassName="z-30"
shadowColor="hsl(var(--muted))"
>
{props.children}
</ScrollShadow>
);
}
export {
Table,
TableHeader,
TableBody,
TableCaption,
TableCell,
TableContainer,
TableFooter,
TableHead,
TableHeader,
TableRow,
TableCell,
TableCaption,
TableContainer,
};
} from "@workspace/ui/components/table";
+10 -8
View File
@@ -6,7 +6,7 @@ import { useCallback, useRef, useState } from "react";
import { useIsomorphicLayoutEffect } from "@/lib/useIsomorphicLayoutEffect";
import { cn } from "@/lib/utils";
import { Button } from "./button";
import { ScrollShadow } from "./ScrollShadow/ScrollShadow";
import { ScrollShadow } from "./ScrollShadow";
import { ToolTipLabel } from "./tooltip";
export type TabLink = {
@@ -216,14 +216,16 @@ function useUnderline<El extends HTMLElement>() {
return { activeTabRef, containerRef, lineRef };
}
export type TabPathLink = {
name: React.ReactNode;
path: string;
exactMatch?: boolean;
isDisabled?: boolean;
isActive?: (pathname: string) => boolean;
};
export function TabPathLinks(props: {
links: {
name: React.ReactNode;
path: string;
exactMatch?: boolean;
isDisabled?: boolean;
isActive?: (pathname: string) => boolean;
}[];
links: TabPathLink[];
className?: string;
tabContainerClassName?: string;
shadowColor?: string;
+7 -68
View File
@@ -1,70 +1,9 @@
"use client";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import * as React from "react";
import { cn } from "@/lib/utils";
const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
className={cn(
"fade-in-0 zoom-in-95 data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 animate-in overflow-hidden rounded-md border border-border bg-popover px-3 py-1.5 text-popover-foreground text-sm shadow-md data-[state=closed]:animate-out",
className,
)}
ref={ref}
sideOffset={sideOffset}
{...props}
/>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
export function ToolTipLabel(props: {
children: React.ReactNode;
label: React.ReactNode;
rightIcon?: React.ReactNode;
leftIcon?: React.ReactNode;
hoverable?: boolean;
contentClassName?: string;
side?: "top" | "right" | "bottom" | "left";
align?: "center" | "start" | "end";
}) {
if (!props.label) {
return props.children;
}
return (
<TooltipProvider>
<Tooltip delayDuration={100} disableHoverableContent={!props.hoverable}>
<TooltipTrigger asChild className="!pointer-events-auto" tabIndex={-1}>
{props.children}
</TooltipTrigger>
<TooltipContent
align={props.align}
className={cn(
"max-w-[400px] whitespace-normal p-0 leading-relaxed",
props.contentClassName,
)}
side={props.side}
sideOffset={10}
>
<div className="flex items-center gap-1.5 p-4 text-sm">
{props.leftIcon}
{props.label}
{props.rightIcon}
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
export {
ToolTipLabel,
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@workspace/ui/components/tooltip";
+1
View File
@@ -3,3 +3,4 @@ export const COOKIE_PREFIX_TOKEN = "tw_token_";
export const LAST_USED_PROJECT_ID = "last-used-project-id";
export const LAST_USED_TEAM_ID = "last-used-team-id";
export const HAS_USED_DASHBOARD = "has-used-dashboard";
@@ -1,6 +1,6 @@
"use client";
import { useMemo } from "react";
import { useCallback, useMemo } from "react";
import type { Chain, ChainMetadata } from "thirdweb/chains";
import { useActiveWalletChain } from "thirdweb/react";
import { useAllChainsData } from "@/hooks/chains/allChains";
@@ -27,6 +27,17 @@ export function useV5DashboardChain(
}, [chainId, idToChain]);
}
export function useGetV5DashboardChain() {
const { idToChain } = useAllChainsData();
return useCallback(
(chainId: number) => {
// eslint-disable-next-line no-restricted-syntax
return defineDashboardChain(chainId, idToChain.get(chainId));
},
[idToChain],
);
}
/**
* same behavior as v4 `useChain()` but for v5
*/
+1 -1
View File
@@ -1,5 +1,5 @@
import { useQuery } from "@tanstack/react-query";
import { getUniversalBridgeTokens } from "@/api/universal-bridge/tokens";
import { getUniversalBridgeTokens } from "@/api/universal-bridge/token-list";
export function useTokensData({
chainId,
+3 -3
View File
@@ -11,8 +11,8 @@ import { useActiveAccount } from "thirdweb/react";
import invariant from "tiny-invariant";
import { apiServerProxy } from "@/actions/proxies";
import type { EngineBackendWalletType } from "@/constants/engine";
import type { ResultItem } from "../../app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/metrics/components/StatusCodes";
import type { EngineStatus } from "../../app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/overview/components/transactions-table";
import type { ResultItem } from "../../app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/(instance)/[engineId]/metrics/components/StatusCodes";
import type { EngineStatus } from "../../app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/(instance)/[engineId]/overview/components/transactions-table";
import { engineKeys } from "../query-keys/cache-keys";
// Engine instances
@@ -1662,7 +1662,7 @@ export function useEngineSystemMetrics(
queryFn: async () => {
const res = await apiServerProxy({
method: "GET",
pathname: `/v1/teams/${teamIdOrSlug}/${projectSlug}/engine/dedicated/${engineId}/metrics`,
pathname: `/v1/teams/${teamIdOrSlug}/${projectSlug}/engine/${engineId}/metrics`,
});
if (!res.ok) {
+6 -1
View File
@@ -7,6 +7,7 @@ import {
import { toast } from "sonner";
import {
type Chain,
getAddress,
NATIVE_TOKEN_ADDRESS,
sendAndConfirmTransaction,
type ThirdwebClient,
@@ -98,7 +99,8 @@ export function useSplitDistributeFunds(contract: ThirdwebContract) {
.filter((token) => token.value !== 0n)
.map(async (currency) => {
const transaction =
currency.name === "Native Token"
getAddress(currency.tokenAddress) ===
getAddress(NATIVE_TOKEN_ADDRESS)
? distribute({ contract })
: distributeByToken({
contract,
@@ -108,6 +110,7 @@ export function useSplitDistributeFunds(contract: ThirdwebContract) {
account,
transaction,
});
toast.promise(promise, {
error: (err) => ({
message: `Error distributing ${currency.name}`,
@@ -116,6 +119,8 @@ export function useSplitDistributeFunds(contract: ThirdwebContract) {
loading: `Distributing ${currency.name}`,
success: `Successfully distributed ${currency.name}`,
});
await promise;
});
return await Promise.all(distributions);
+6
View File
@@ -74,6 +74,12 @@ export function teamStub(id: string, billingPlan: Team["billingPlan"]): Team {
rpc: {
enabled: true,
rateLimit: 1000,
websockets: {
enabled: false,
reasonCode: "enterprise_plan_required",
maxConnections: 0,
maxSubscriptions: 0,
},
},
storage: {
download: {
+8 -7
View File
@@ -60,13 +60,6 @@ export interface UniversalBridgeWalletStats {
developerFeeUsdCents: number;
}
export interface WebhookRequestStats {
date: string;
webhookId: string;
httpStatusCode: number;
totalRequests: number;
}
export interface WebhookLatencyStats {
date: string;
webhookId: string;
@@ -85,6 +78,14 @@ export interface WebhookSummaryStats {
errorBreakdown: Record<string, number>;
}
export interface AIUsageStats {
date: string;
totalPromptTokens: number;
totalCompletionTokens: number;
totalSessions: number;
totalRequests: number;
}
export interface AnalyticsQueryParams {
teamId: string;
projectId?: string;
@@ -43,7 +43,7 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import {
NEXT_PUBLIC_THIRDWEB_ENGINE_FAUCET_WALLET,
NEXT_PUBLIC_TURNSTILE_SITE_KEY,
@@ -11,7 +11,7 @@ import {
import { useDebounce } from "use-debounce";
import { useCustomConnectModal } from "@/components/connect-wallet";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { ToolTipLabel } from "@/components/ui/tooltip";
type AddChainToWalletProps = {
@@ -46,7 +46,7 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Form } from "@/components/ui/form";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { useIsAdmin } from "@/hooks/useContractRoles";
import { useTxNotifications } from "@/hooks/useTxNotifications";
@@ -11,7 +11,7 @@ import { DropZone } from "@/components/blocks/drop-zone/drop-zone";
import { PaginationButtons } from "@/components/blocks/pagination-buttons";
import { Button } from "@/components/ui/button";
import { InlineCode } from "@/components/ui/inline-code";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import {
Sheet,
SheetContent,
@@ -1,4 +1,4 @@
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
export function LoadingPage() {
return (
@@ -23,7 +23,7 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { UnderlineLink } from "@/components/ui/UnderlineLink";
import { useAddContractToProject } from "@/hooks/project-contracts";
import type { EVMContractInfo } from "@/hooks/useActiveChainId";
@@ -15,8 +15,8 @@ import { Button } from "@/components/ui/button";
import { CopyTextButton } from "@/components/ui/CopyTextButton";
import { CodeClient } from "@/components/ui/code/code.client";
import { Label } from "@/components/ui/label";
import { ScrollShadow } from "@/components/ui/ScrollShadow/ScrollShadow";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { ScrollShadow } from "@/components/ui/ScrollShadow";
import { Spinner } from "@/components/ui/Spinner";
import {
Select,
SelectContent,
@@ -5,7 +5,7 @@ import type { ThirdwebContract } from "thirdweb";
import { getInstalledModules, owner } from "thirdweb/modules";
import { useActiveAccount, useReadContract } from "thirdweb/react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { InstalledModulesTable } from "./components/InstalledModulesTable";
import { InstallModuleForm } from "./components/ModuleForm";
@@ -4,7 +4,7 @@ import { CircleSlashIcon } from "lucide-react";
import type { ThirdwebContract } from "thirdweb/contract";
import type { Account } from "thirdweb/wallets";
import { Alert, AlertTitle } from "@/components/ui/alert";
import { ScrollShadow } from "@/components/ui/ScrollShadow/ScrollShadow";
import { ScrollShadow } from "@/components/ui/ScrollShadow";
import { ModuleCard } from "./module-card";
import { useAllModuleContractInfo } from "./moduleContractInfo";
@@ -27,7 +27,7 @@ import type { Account } from "thirdweb/wallets";
import { FormFieldSetup } from "@/components/blocks/FormFieldSetup";
import { TransactionButton } from "@/components/tx-button";
import { Input } from "@/components/ui/input";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import {
Select,
SelectContent,
@@ -28,7 +28,7 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { Skeleton } from "@/components/ui/skeleton";
import { ModuleInstance } from "./module-instance";
import { useModuleContractInfo } from "./moduleContractInfo";
@@ -21,7 +21,7 @@ import {
import { Button } from "@/components/ui/button";
import { CopyTextButton } from "@/components/ui/CopyTextButton";
import { CodeClient } from "@/components/ui/code/code.client";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { TabButtons } from "@/components/ui/tabs";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { useChainSlug } from "@/hooks/chains/chainSlug";
@@ -16,7 +16,7 @@ import { NFTMediaWithEmptyState } from "@/components/blocks/nft-media";
import { PaginationButtons } from "@/components/blocks/pagination-buttons";
import { WalletAddress } from "@/components/blocks/wallet-address";
import { CopyTextButton } from "@/components/ui/CopyTextButton";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { Skeleton } from "@/components/ui/skeleton";
import {
Table,
@@ -6,7 +6,7 @@ import type { ThirdwebContract } from "thirdweb";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { CopyTextButton } from "@/components/ui/CopyTextButton";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import {
Table,
TableBody,
@@ -12,7 +12,7 @@ import { useReadContract } from "thirdweb/react";
import { WalletAddress } from "@/components/blocks/wallet-address";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import {
Table,
TableBody,
@@ -99,6 +99,26 @@ export const WithImageAndMultipleSocialUrls: Story = {
website: mockSocialUrls.website,
},
symbol: "SMPL",
isDashboardUser: true,
},
};
export const NotDashboardUser: Story = {
args: {
chainMetadata: ethereumChainMetadata,
clientContract: mockContract,
contractCreator: null,
image: mockTokenImage,
name: "Sample Token",
socialUrls: {
discord: mockSocialUrls.discord,
github: mockSocialUrls.github,
telegram: mockSocialUrls.telegram,
twitter: mockSocialUrls.twitter,
website: mockSocialUrls.website,
},
symbol: "SMPL",
isDashboardUser: false,
},
};
@@ -117,6 +137,7 @@ export const WithContractCreator: Story = {
website: mockSocialUrls.website,
},
symbol: "SMPL",
isDashboardUser: true,
},
};
@@ -131,6 +152,7 @@ export const WithBrokenImageAndSingleSocialUrl: Story = {
website: mockSocialUrls.website,
},
symbol: "SMPL",
isDashboardUser: true,
},
};
@@ -143,6 +165,7 @@ export const WithoutImageAndNoSocialUrls: Story = {
name: "Sample Token",
socialUrls: {},
symbol: "SMPL",
isDashboardUser: true,
},
};
@@ -163,6 +186,7 @@ export const LongNameAndLotsOfSocialUrls: Story = {
youtube: mockSocialUrls.youtube,
},
symbol: "LONG",
isDashboardUser: true,
},
};
@@ -187,6 +211,7 @@ export const AllSocialUrls: Story = {
youtube: mockSocialUrls.youtube,
},
symbol: "SMPL",
isDashboardUser: true,
},
};
@@ -205,6 +230,7 @@ export const InvalidSocialUrls: Story = {
youtube: mockSocialUrls.youtube,
},
symbol: "SMPL",
isDashboardUser: true,
},
};
@@ -220,5 +246,6 @@ export const SomeSocialUrls: Story = {
website: mockSocialUrls.website,
},
symbol: "SMPL",
isDashboardUser: true,
},
};
@@ -9,7 +9,6 @@ import Link from "next/link";
import { useMemo } from "react";
import { type ThirdwebContract, ZERO_ADDRESS } from "thirdweb";
import type { ChainMetadata } from "thirdweb/chains";
import { useActiveAccount } from "thirdweb/react";
import { Img } from "@/components/blocks/Img";
import { Button } from "@/components/ui/button";
import { CopyAddressButton } from "@/components/ui/CopyAddressButton";
@@ -51,6 +50,7 @@ export function ContractHeaderUI(props: {
imageClassName?: string;
contractCreator: string | null;
className?: string;
isDashboardUser: boolean;
}) {
const socialUrls = useMemo(() => {
const socialUrlsValue: { name: string; href: string }[] = [];
@@ -66,7 +66,6 @@ export function ContractHeaderUI(props: {
return socialUrlsValue;
}, [props.socialUrls]);
const activeAccount = useActiveAccount();
const cleanedChainName = props.chainMetadata?.name
?.replace("Mainnet", "")
@@ -172,8 +171,7 @@ export function ContractHeaderUI(props: {
variant="outline"
/>
{props.contractCreator?.toLowerCase() ===
activeAccount?.address?.toLowerCase() && (
{props.isDashboardUser && (
<ToolTipLabel
contentClassName="max-w-[300px]"
label={
@@ -115,7 +115,6 @@ function RecentTransfersUI(props: {
<WalletAddress
address={transfer.from_address}
client={props.client}
iconClassName="size-4.5"
/>
<ArrowRightIcon className="size-4 text-muted-foreground/50 absolute -right-1 lg:right-3" />
</div>
@@ -133,7 +132,6 @@ function RecentTransfersUI(props: {
address={transfer.to_address}
client={props.client}
key={transfer.log_index}
iconClassName="size-4.5"
/>
</div>
))}
@@ -35,7 +35,7 @@ import { TransactionButton } from "@/components/tx-button";
import { Button } from "@/components/ui/button";
import { DecimalInput } from "@/components/ui/decimal-input";
import { Label } from "@/components/ui/label";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { parseError } from "@/utils/errorParser";
@@ -1,8 +1,10 @@
import { cookies } from "next/headers";
import type { ThirdwebContract } from "thirdweb";
import type { ChainMetadata } from "thirdweb/chains";
import { getContractMetadata } from "thirdweb/extensions/common";
import { decimals, getActiveClaimCondition } from "thirdweb/extensions/erc20";
import { GridPattern } from "@/components/ui/background-patterns";
import { HAS_USED_DASHBOARD } from "@/constants/cookie";
import { resolveFunctionSelectors } from "@/lib/selectors";
import { AssetPageView } from "../_components/asset-page-view";
import { getContractCreator } from "../_components/getContractCreator";
@@ -65,6 +67,9 @@ export async function ERC20PublicPage(props: {
: undefined,
]);
const cookieStore = await cookies();
const isDashboardUser = cookieStore.has(HAS_USED_DASHBOARD);
const buyEmbed = (
<BuyEmbed
chainMetadata={props.chainMetadata}
@@ -96,6 +101,7 @@ export async function ERC20PublicPage(props: {
clientContract={props.clientContract}
contractCreator={contractCreator}
image={contractMetadata.image}
isDashboardUser={isDashboardUser}
name={contractMetadata.name}
socialUrls={
typeof contractMetadata.social_urls === "object" &&
@@ -14,6 +14,7 @@ export function NFTPublicPageLayout(props: {
};
children: React.ReactNode;
contractCreator: string | null;
isDashboardUser: boolean;
}) {
return (
<div className="flex grow flex-col">
@@ -38,6 +39,7 @@ export function NFTPublicPageLayout(props: {
: {}
}
symbol={props.contractMetadata.symbol}
isDashboardUser={props.isDashboardUser}
/>
</div>
</div>
@@ -1,9 +1,11 @@
import { cookies } from "next/headers";
import type { ThirdwebContract } from "thirdweb";
import type { ChainMetadata } from "thirdweb/chains";
import { getContractMetadata } from "thirdweb/extensions/common";
import { isTokenByIndexSupported } from "thirdweb/extensions/erc721";
import { ResponsiveLayout } from "@/components/blocks/Responsive";
import { Skeleton } from "@/components/ui/skeleton";
import { HAS_USED_DASHBOARD } from "@/constants/cookie";
import { resolveFunctionSelectors } from "@/lib/selectors";
import { cn } from "@/lib/utils";
import { getContractCreator } from "../_components/getContractCreator";
@@ -100,12 +102,16 @@ export async function NFTPublicPage(props: {
<Skeleton className="h-[620px] border" />
) : null;
const cookieStore = await cookies();
const isDashboardUser = cookieStore.has(HAS_USED_DASHBOARD);
return (
<NFTPublicPageLayout
chainMetadata={props.chainMetadata}
clientContract={props.clientContract}
contractCreator={contractCreator}
contractMetadata={contractMetadata}
isDashboardUser={isDashboardUser}
>
<ResponsiveLayout
desktop={
@@ -25,7 +25,7 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { ScrollShadow } from "@/components/ui/ScrollShadow/ScrollShadow";
import { ScrollShadow } from "@/components/ui/ScrollShadow";
import { Skeleton, SkeletonContainer } from "@/components/ui/skeleton";
import { TabButtons } from "@/components/ui/tabs";
import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler";
@@ -1,5 +1,5 @@
import { Grid2x2XIcon } from "lucide-react";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { UnderlineLink } from "@/components/ui/UnderlineLink";
import type { ExtensionDetectedState } from "@/types/ExtensionDetectedState";
@@ -21,7 +21,7 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { useContractSources } from "@/hooks/contract-ui/useContractSources";
import { useResolveContractAbi } from "@/hooks/useResolveContractAbi";
import { useDashboardRouter } from "@/lib/DashboardRouter";
@@ -2,14 +2,12 @@
import { SplitIcon } from "lucide-react";
import { useMemo } from "react";
import { toast } from "sonner";
import type { ThirdwebContract } from "thirdweb";
import type { GetBalanceResult } from "thirdweb/extensions/erc20";
import { TransactionButton } from "@/components/tx-button";
import { Button } from "@/components/ui/button";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { useSplitDistributeFunds } from "@/hooks/useSplit";
import { parseError } from "@/utils/errorParser";
export const DistributeButton = ({
contract,
@@ -25,6 +23,7 @@ export const DistributeButton = ({
isLoggedIn: boolean;
}) => {
const validBalances = balances.filter((item) => item.value !== 0n);
const numTransactions = useMemo(() => {
if (
validBalances.length === 1 &&
@@ -38,20 +37,10 @@ export const DistributeButton = ({
return validBalances?.filter((b) => b.value !== 0n).length;
}, [validBalances, balancesIsPending]);
const mutation = useSplitDistributeFunds(contract);
const distributeFundsMutation = useSplitDistributeFunds(contract);
const distributeFunds = () => {
mutation.mutate(undefined, {
onError: (error) => {
toast.error("Failed to process transaction", {
description: parseError(error),
});
console.error(error);
},
onSuccess: () => {
toast.success("Funds splitted successfully");
},
});
distributeFundsMutation.mutateAsync();
};
if (balancesIsError) {
@@ -59,7 +48,7 @@ export const DistributeButton = ({
<TransactionButton
client={contract.client}
isLoggedIn={isLoggedIn}
isPending={mutation.isPending}
isPending={distributeFundsMutation.isPending}
onClick={distributeFunds}
size="sm"
transactionCount={undefined}
@@ -86,7 +75,7 @@ export const DistributeButton = ({
<TransactionButton
client={contract.client}
isLoggedIn={isLoggedIn}
isPending={mutation.isPending}
isPending={distributeFundsMutation.isPending}
onClick={distributeFunds}
transactionCount={numTransactions === 1 ? undefined : numTransactions}
txChainID={contract.chain.id}
@@ -3,7 +3,7 @@ import type { ThirdwebClient } from "thirdweb";
import { DownloadableCode } from "@/components/blocks/code/downloadable-code";
import { DropZone } from "@/components/blocks/drop-zone/drop-zone";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { useCsvUpload } from "@/hooks/useCsvUpload";
import { AirdropCSVTable } from "./airdrop-csv-table";
@@ -4,7 +4,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
import { StarIcon } from "lucide-react";
import { apiServerProxy } from "@/actions/proxies";
import { Button, type ButtonProps } from "@/components/ui/button";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { useFavoriteChainIds } from "@/hooks/favorite-chains";
import { cn } from "@/lib/utils";
@@ -13,7 +13,7 @@ import { useActiveAccount, useSendAndConfirmTransaction } from "thirdweb/react";
import { reportContractPublished } from "@/analytics/report";
import { CustomConnectWallet } from "@/components/connect-wallet";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import {
DASHBOARD_ENGINE_RELAYER_URL,
DASHBOARD_FORWARDER_ADDRESS,
@@ -1,7 +1,7 @@
import { Suspense } from "react";
import type { ThirdwebClient } from "thirdweb";
import { fetchPublishedContracts } from "@/api/contract/fetchPublishedContracts";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
import { ProfileHeader } from "./components/profile-header";
import { PublishedContracts } from "./components/published-contracts";
@@ -9,7 +9,7 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { cn } from "@/lib/utils";
export type DeployModalStep = {
@@ -20,7 +20,7 @@ import { Card } from "@/components/ui/card";
import { CodeClient } from "@/components/ui/code/code.client";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { Textarea } from "@/components/ui/textarea";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
@@ -1,4 +1,4 @@
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
export default function Loading() {
return (
@@ -1,7 +1,7 @@
"use client";
import { useEffect } from "react";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Spinner } from "@/components/ui/Spinner";
import { stripeRedirectPageChannel } from "@/hooks/stripe/redirect-event";
export default function ClosePage() {

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