Compare commits

...
Author SHA1 Message Date
Jonas DanielsGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
98f2b56c19 Version Packages (#4782)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-26 15:29:28 +12:00
gregfromstl 500970e001 [SDK] Fix: logout on iframe (#4809)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on fixing account logout functionality and updating error messages related to transaction limits across various tests in the `thirdweb` package.

### Detailed summary
- Updated `logout` method in `iframe-auth.ts` to check for `AuthQuerier` before calling logout.
- Changed expected address in `deploy-published.test.ts`.
- Updated error messages from `TransactionError: Error - !Qty` to `DropClaimExceedLimit` in multiple test files for `erc20`, `erc721`, and `erc1155`.

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

<!-- end pr-codex -->
2024-09-26 02:14:43 +00:00
jnsdls 9c58c08945 simplify siwe auth login flow (#4808) 2024-09-26 01:23:13 +00:00
kien-ngo 587804d5a8 [Dashboard] Remove chakra useToast (#4802)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on replacing the usage of `useToast` from `@chakra-ui/react` with `toast` from `sonner` for displaying notifications across various components in the dashboard. This change aims to streamline the notification handling.

### Detailed summary
- Removed `useToast` from multiple components.
- Replaced toast notifications with `toast.info`, `toast.success`, and `toast.error` methods from `sonner`.
- Updated the `AddressCopyButton`, `PermissionsTable`, `LatestEvents`, `IpfsUploadDropzone`, and `AuthorizedWalletsTable` components to reflect these changes.

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

<!-- end pr-codex -->
2024-09-26 00:19:57 +00:00
kumaryash90 3f83a377bc New zksolc metadata format (#4568)
## Problem solved

Handle new zksolc metadata format
ref: ipfs://QmS6ehXYWKJEJq3LBSfG2ujKPzQQJooNE7L375zyFbpxmw

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces support for a new `zksolc` metadata format in the `thirdweb` package, enhancing the handling of zkSync contracts by updating various functions and types to accommodate the new compiler type.

### Detailed summary
- Added `compilerType` option to several functions and types to support `solc` and `zksolc`.
- Updated `fetchPublishedContractMetadata` and `deployPublishedContract` to utilize `compilerType`.
- Enhanced `formatCompilerMetadata` to handle `zksolc` specific metadata.
- Modified `fetchDeployMetadata` to validate and fetch `zksolc` metadata correctly.
- Updated `CustomContractForm` to determine `compilerType` based on the active wallet chain.
- Improved error handling for missing `zksolc` metadata.

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

<!-- end pr-codex -->
2024-09-26 00:14:07 +00:00
gregfromstl 5ab3a81f79 [Dashboard] Feature: update ecosystem wallet auth options (#4797)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces new authentication options for the ecosystem feature, updates related types, and enhances the UI components to support these options.

### Detailed summary
- Added new authentication options: `google`, `facebook`, `x`, `discord`, `farcaster`, `telegram`, `phone`, `email`, `guest`, `apple`, `coinbase`, `line`.
- Updated `Ecosystem` type to include `authOptions`.
- Modified `useUpdateEcosystem` to accept optional `authOptions`.
- Added `AuthOptionsSection` to the `EcosystemPermissionsPage`.
- Created `AuthOptionsForm` for managing authentication options.
- Implemented confirmation dialog for adding/removing auth options.

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

<!-- end pr-codex -->
2024-09-25 23:47:49 +00:00
kien-ngo 931569b374 [Dashboard] Remove <Box/> easy mode (#4786)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on replacing all instances of the `Box` component with the `div` element across various files in the codebase, likely for consistency or to simplify the component structure.

### Detailed summary
- Replaced `Box` with `div` in `PermissionsTable.tsx`
- Replaced `Box` with `div` in `LatestEvents.tsx`
- Replaced `Box` with `div` in `templates.tsx`
- Replaced `Box` with `div` in `detail-page.tsx`
- Replaced `Box` with `div` in `contract-publish-form/index.tsx`
- Replaced `Box` with `div` in `custom-factory.tsx`
- Replaced `Box` with `div` in `metadata.tsx`
- Replaced `Box` with `div` in `Solutions.tsx`
- Replaced `Box` with `div` in `MiniPlayground.tsx`
- Replaced `Box` with `div` in `FormItem.tsx`
- Replaced `Box` with `div` in `DevRelEvent.tsx`
- Replaced `Box` with `div` in `shared-metadata-form.tsx`
- Replaced `Box` with `div` in `update-metadata-form.tsx`
- Replaced `Box` with `div` in `external-links-fieldset.tsx`
- Replaced `Box` with `div` in `events-feed.tsx`
- Replaced `Box` with `div` in `lazy-mint-form.tsx`
- Replaced `Box` with `div` in `mint-form.tsx`
- Replaced `Box` with `div` in `page.tsx`
- Replaced `Box` with `div` in `landing-fieldset.tsx`
- Replaced `Box` with `div` in `PlanCard.tsx`
- Replaced `Box` with `div` in `index.tsx` of `CodeBlock`
- Replaced `Box` with `div` in `SponsorshipPolicies/index.tsx`
- Replaced `Box` with `div` in `AccountAbstractionSettingsPage` across multiple instances.

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

<!-- end pr-codex -->
2024-09-25 21:47:51 +00:00
kien-ngo d76618fc00 [Dashboard] Remove Flex easy mode (#4785)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on replacing instances of the `Flex` component with `div` elements that have corresponding CSS classes for layout styling. This change aims to streamline the code while maintaining the same visual structure.

### Detailed summary
- Replaced `<Flex>` with `<div className="flex flex-row">` in multiple components for horizontal alignment.
- Replaced `<Flex>` with `<div className="flex flex-col">` in several components for vertical alignment.
- Ensured all functional components maintain the same layout and behavior after changes.

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

<!-- end pr-codex -->
2024-09-25 21:47:48 +00:00
kien-ngo 46acf33b5a [Dashboard] Remove more Flex (#4784)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on replacing instances of the `Flex` component with `div` elements styled with Tailwind CSS classes. This change aims to standardize layout styling across various components in the application.

### Detailed summary
- Replaced `Flex` with `div` and added Tailwind CSS classes for layout.
- Updated components in files like `GraphicContainer.tsx`, `contract-functions.tsx`, `engine-overview.tsx`, and others.
- Maintained existing functionality while improving styling consistency.

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

<!-- end pr-codex -->
2024-09-25 21:47:45 +00:00
kien-ngo 10a84d2810 [Dashboard] Remove <Center /> (#4765)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on replacing the usage of the `Center` component from `@chakra-ui/react` with `div` elements styled with Flexbox classes for better layout control across various components in the dashboard.

### Detailed summary
- Replaced `Center` with `div` and applied Flexbox utility classes in multiple components for consistent styling.
- Updated components include `TransactionButton`, `ContractSplitPage`, `OnboardingPaymentForm`, and more.
- Maintained existing functionality while improving layout responsiveness.

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

<!-- end pr-codex -->
2024-09-25 21:47:42 +00:00
MananTank 3e8c074011 Fix Publish page not prepopulated with latest published version metadata (#4806)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the `PublishContractPage` by adding logic to handle cases where published contract metadata lacks a version. It fetches existing contracts associated with a publisher and merges their data with the fetched metadata.

### Detailed summary
- Imported `getPublishedContractsWithPublisherMapping` utility.
- Renamed variable `publishMetadata` to `publishMetadataFromUri`.
- Added logic to check if `publishMetadataFromUri` lacks a version.
- Fetched existing published contracts for the publisher.
- Merged existing contract data with `publishMetadataFromUri` if applicable.

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

<!-- end pr-codex -->
2024-09-25 21:34:26 +00:00
MananTank 206340cc1d Add Img component with skeleton + fallback support (#4791)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new `Img` component that handles image loading states with customizable skeletons and fallbacks. It also adds a Storybook story to demonstrate various usages of the `Img` component.

### Detailed summary
- Added `Img` component to handle image loading states.
- Implemented state management for loading, fallback, and loaded statuses.
- Introduced customizable `skeleton` and `fallback` props.
- Created Storybook stories showcasing different scenarios for the `Img` component.

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

<!-- end pr-codex -->
2024-09-25 21:23:16 +00:00
gregandGitHub 6153d57c34 [SDK] Fix: adds ERC20 transfer overrides param (#4804) 2024-09-26 08:23:46 +12:00
jnsdls 39aeb140d2 Handle 400 error in user creation check (#4805) 2024-09-25 20:18:15 +00:00
MananTank 8e9d13c95c Add position relative on all Shadcn/Chakra AspectRatio component replacement divs (#4801)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating various components to add the `relative` class to `div` elements that define aspect ratios, enhancing layout control and positioning.

### Detailed summary
- Added `relative` class to `div` elements in:
  - `GameShowcase.tsx` for aspect ratios `16/9`
  - `GuideCard.tsx` for aspect ratios `2400/1260`
  - `airdrop-upload-erc20.tsx` for aspect ratios `21/9`
  - `chain-header.tsx` for conditional aspect ratios
  - `airdrop-upload.tsx` for aspect ratios `21/9`
  - `snapshot-upload.tsx` for aspect ratios `21/9`
  - `upload-step.tsx` for aspect ratio `square`
  - `NFTCards.tsx` for aspect ratio `square`
  - `MarketplaceDetails.tsx` for aspect ratio `square`
  - `dropzone.tsx` for aspect ratio adjustments
  - `WithoutThirdwebSection.tsx` for aspect ratios `16/10`
  - `logo/index.tsx` for aspect ratio `516/321`
  - Updated `Logo` component for aspect ratio `1377/267`

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

<!-- end pr-codex -->
2024-09-25 15:40:12 +00:00
kien-ngo d35f0b2cc6 [Dashboard] Remove <AspectRatio/> (#4750)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on removing the `AspectRatio` component from the project and replacing its usage with `div` elements styled with Tailwind CSS classes to maintain the aspect ratio.

### Detailed summary
- Deleted `@radix-ui/react-aspect-ratio` from `package.json`.
- Removed all instances of the `AspectRatio` component across various files.
- Replaced `AspectRatio` with `div` elements using Tailwind CSS classes for aspect ratios.
- Updated related props and imports as necessary.

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

<!-- end pr-codex -->
2024-09-25 05:52:04 +00:00
Joaquim VergesandGitHub 2f010fa3fc feat(storage): add JSON object upload example and documentation (#4796) 2024-09-25 15:35:57 +12:00
joaquim-verges 944d56f6d8 ecosystem refactor part 4: react native edition (#4783)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing wallet functionalities and authentication processes in the `thirdweb` library, particularly for ecosystem wallets in React Native. It introduces new wallet interfaces, updates existing methods, and improves error handling for user authentication.

### Detailed summary
- Removed obsolete `web-wallet.ts.changeset/chilled-dancers-cheer.md` file.
- Added support for ecosystem wallets in `useAutoConnect` hook.
- Updated `createInAppWallet` to include new `createOptions` structure.
- Defined `IWebWallet` interface with methods for wallet setup and user status.
- Expanded `EcosystemWalletCreationOptions` to include authentication options.
- Modified `getUserStatus` to handle user status retrieval with better error logging.
- Updated `InAppWebConnector` to utilize the new wallet interfaces and methods.
- Implemented `ShardedWallet` class with user wallet status and account retrieval.
- Enhanced error handling in the authentication processes across various modules.
- Removed deprecated methods from `native-auth.ts`.

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

<!-- end pr-codex -->
2024-09-25 01:41:15 +00:00
joaquim-verges 1f1f99ddd0 ecosystem refactor part 3 (#4781)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new constant `IS_DEV` to streamline environment checks, updates various wallet-related functionalities to use `ClientScopedStorage`, and replaces enum usages with string literals for better type safety. It also enhances error handling and modifies authentication processes.

### Detailed summary
- Added `IS_DEV` constant for environment checks.
- Replaced `process.env.NODE_ENV` checks with `IS_DEV` in multiple files.
- Updated wallet functions to utilize `ClientScopedStorage`.
- Converted enums to string literals for `RecoveryShareManagement` and `AuthProvider`.
- Improved error handling in authentication functions.
- Standardized user status strings across wallet components.
- Enhanced documentation for `ecosystemWallet` function.
- Refactored several functions to accept an object with parameters for better clarity.

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

<!-- end pr-codex -->
2024-09-25 01:41:12 +00:00
joaquim-verges de63195d43 ecosystem wallet refactor (part 2) (#4780)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on refactoring the `thirdweb` wallet implementation, specifically enhancing the `in-app` wallet functionality by integrating `localStorage` for authentication and modifying wallet creation methods.

### Detailed summary
- Deleted `get-auth-token.ts` and `ecosystem-core.ts`.
- Updated wallet creation to use `createInAppWallet`.
- Integrated `localStorage` for authentication in multiple files.
- Modified `EcosystemWalletCreationOptions` to include `InAppWalletCreationOptions`.
- Removed obsolete auth token fetching in various functions.

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

<!-- end pr-codex -->
2024-09-25 01:41:10 +00:00
Joaquim VergesandGitHub e2b3484764 Update pregen docs (#4795)
Signed-off-by: Joaquim Verges <[email protected]>
2024-09-25 11:14:10 +12:00
jnsdls c0d81dcaca Add optional Headers param to fetchChain (#4793)
### TL;DR

Added an optional Headers parameter to the `fetchChain` function.

### What changed?

- Introduced a new optional `headers` parameter to the `fetchChain` function in `apps/dashboard/src/utils/fetchChain.ts`.
- Updated the fetch request to include the provided headers.
- Created a new changeset file to document this patch-level change.

### How to test?

1. Call the `fetchChain` function without providing headers to ensure backward compatibility.
2. Call the `fetchChain` function with custom headers and verify they are included in the request.
3. Confirm that the function still correctly fetches chain metadata from the API.

### Why make this change?

This change allows for greater flexibility when making API requests to fetch chain metadata. By allowing custom headers, it enables use cases such as authentication, caching control, or other custom requirements when interacting with the API.
2024-09-24 21:09:41 +00:00
jnsdls 0aea48e37d fix typedoc setup (#4792)
### TL;DR

Refactored TypeDoc script and added a dedicated TypeScript configuration for documentation generation.

### What changed?

- Simplified the `typedoc.mjs` script by removing the `typedoc` function and directly executing the TypeDoc application.
- Added a new `tsconfig.typedoc.json` file specifically for TypeDoc configuration.
- Updated the TypeDoc application configuration to use the new TypeScript config file.

### How to test?

1. Run the TypeDoc script to generate documentation.
2. Verify that the `typedoc/documentation.json` file is created successfully.
3. Check that the generated documentation includes the correct files and excludes the specified ones.

### Why make this change?

This change improves the documentation generation process by:
1. Simplifying the TypeDoc script for better maintainability.
2. Providing a dedicated TypeScript configuration for documentation, ensuring more accurate type information.
3. Enhancing consistency in the documentation output by using a specific tsconfig file.
2024-09-24 21:03:38 +00:00
arcoraven f91e32f133 Add Alert docs to Portal (#4788)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces new features and updates to the `thirdweb Engine`, including alert notifications, metadata enhancements, and updated documentation for troubleshooting and alert management.

### Detailed summary
- Added new alert notifications feature in `page.mdx` with instructions.
- Introduced `metadata` for `Alert Notifications` and `Troubleshooting` pages.
- Updated asset imports for alert images in `page.mdx`.
- Enhanced troubleshooting documentation for clarity.

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

<!-- end pr-codex -->
2024-09-24 19:52:49 +00:00
MananTank e38400195f Restore deleted whitespaces, Remove unsafe biome formatting (#4789)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on improving the user interface by adding space between certain text elements and enhancing readability across various components in the dashboard application.

### Detailed summary
- Added space after text in various components to improve readability.
- Updated text elements in `templates.tsx`, `index.tsx`, `TransactionModeScreen.tsx`, and others.
- Ensured consistent formatting in `HeroSection.tsx`, `PricingCard.tsx`, and various settings pages.

> The following files were skipped due to too many changes: `apps/dashboard/src/components/embedded-wallets/Configure/index.tsx`, `apps/dashboard/src/contract-ui/tabs/claim-conditions/components/snapshot-upload.tsx`, `apps/dashboard/src/contract-ui/tabs/overview/components/ContractChecklist.tsx`, `packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/FiatSteps.tsx`, `apps/dashboard/src/core-ui/batch-upload/upload-step.tsx`, `packages/thirdweb/package.json`, `apps/dashboard/src/pages/template/[id].tsx`

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

<!-- end pr-codex -->
2024-09-24 23:14:39 +05:30
joaquim-verges e02d87b831 Refactor linkProfile and getProfiles API + prepare for ecosystemWallet in React Native (#4755)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on refactoring the wallet's profile management system, enhancing the `linkProfile` and `getProfiles` functionalities. It also updates the import paths and improves the organization of wallet-related hooks and types.

### Detailed summary
- Deleted `useAdminAccount.ts` and updated references to `useAdminWallet.ts`.
- Refactored `linkProfile` and `getProfiles` APIs for improved functionality.
- Updated import paths for `Ecosystem` type across multiple files.
- Consolidated wallet profiles logic in `useProfiles` hook.
- Enhanced error handling and type definitions in wallet connectors.
- Added `linkProfile` and `getProfiles` methods to `InAppNativeConnector` and `InAppWebConnector`.

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

<!-- end pr-codex -->
2024-09-24 04:05:00 +00:00
joaquim-verges d5312304f7 refactor: update contract implementation resolution and module info retrieval (#4779)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the `InstalledModulesTable` component by incorporating `getContract` for fetching contract information and updating the bytecode retrieval process in the `usePublishedContractsFromDeploy` function.

### Detailed summary
- In `InstalledModulesTable.tsx`:
  - Updated `contractInfo` to use `getContract` with parameters `moduleAddress`, `chain`, and `client`.

- In `hooks.ts`:
  - Replaced `getBytecode` with `resolveImplementation` to fetch bytecode.

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

<!-- end pr-codex -->
2024-09-24 02:45:39 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
b80b61b0a6 Bump react-native-quick-crypto from 0.7.4 to 0.7.5 (#4775)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-24 14:41:05 +12:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d37e3fe6e3 Bump knip from 5.30.4 to 5.30.5 (#4773)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-23 14:13:22 -07:00
jnsdls 3b91dad963 Remove "push" script and task (#4762)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on modifying the `turbo.json` and `package.json` configuration files, specifically removing certain caching options and adjusting command scripts related to the build process.

### Detailed summary
- In `turbo.json`, removed the `push` configuration and its `cache` and `dependsOn` settings.
- In `package.json`, removed the `push` script that executed `turbo run push --filter=./packages/*`.

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

<!-- end pr-codex -->
2024-09-23 19:41:42 +00:00
Jonas DanielsGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
daf5fdafd5 Version Packages (#4761)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-23 12:40:27 -07:00
jnsdls 399ba2efd0 Improve storage upload error handling (#4763) 2024-09-23 18:30:36 +00:00
arcoraven 57a0e271b3 fix: Return 402 status if payment required (#4760)
<!-- start pr-codex -->

## PR-Codex overview
This PR addresses the handling of payment requirement errors in the `@thirdweb-dev/service-utils` package by changing the HTTP status code returned from `403` to `402`. This indicates that payment is required to proceed.

### Detailed summary
- Updated the status code returned in `usageLimit` functions from `403` to `402`.
- Modified the error message for `Storage Pinning` to reflect the change in status.
- Adjusted the error message for `Embedded Wallets` to align with the new status code.

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

<!-- end pr-codex -->
2024-09-23 18:13:37 +00:00
ElasticBottle 2d34771e18 fix: guest auth should use random session Ids (#4759)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on enhancing guest account creation by ensuring the uniqueness of guest session IDs and improving storage handling in the `thirdweb` package.

### Detailed summary
- Updated guest account creation to use unique session IDs.
- Introduced `GUEST_SESSION_LOCAL_STORAGE_NAME` constant for guest session storage.
- Implemented methods in `ClientScopedStorage` for managing guest session IDs.
- Modified `guestAuthenticate` to handle guest session IDs properly.

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

<!-- end pr-codex -->
2024-09-23 17:56:50 +00:00
jnsdls a2b4daafcf enfore more lint rules (#4754)
does what it says on the tin
2024-09-23 16:16:38 +00:00
Joaquim VergesandGitHub b85fe2a59e feat(playground): update in-app wallet auth options and components (#4757) 2024-09-23 20:57:57 +12:00
kien-ngo 5293fba07e [Dashboard] ERC20 Airdrop form (#4722)
- add a new airdrop-upload form for ERC20 that doesn't open a second Drawer (shadcn Sheet)
- removing bunch of react-icons & chakra stuff in the process

<!-- start pr-codex -->

---

## PR-Codex overview
The focus of this PR is to refactor the Token Airdrop feature in the dashboard UI, improving user experience and code organization.

### Detailed summary
- Refactored Token Airdrop UI using `Sheet` component
- Improved error handling and notifications
- Updated Airdrop form layout and functionality

> The following files were skipped due to too many changes: `apps/dashboard/src/contract-ui/tabs/tokens/components/airdrop-upload-erc20.tsx`

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

<!-- end pr-codex -->
2024-09-23 08:53:21 +00:00
Joaquim VergesandGitHub 294ec4dd0d refactor(playground): move in-app wallet layout and metadata to separate file (#4721) 2024-09-23 20:22:52 +12:00
Jonas DanielsGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
5a4ef087ef Version Packages (#4742)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-23 19:17:14 +12:00
gregfromstl fc2ecdfd98 [React] Fix: hides guest accounts from linked profiles (#4687)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on hiding guest accounts from the linked profiles screen in React.

### Detailed summary
- Excludes guest profiles from being displayed on the linked profiles screen
- Updates the rendering logic to filter out guest profiles before displaying them

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

<!-- end pr-codex -->
2024-09-23 06:34:18 +00:00
joaquim-verges 33936b3a86 fix: handle BigInt serialization for ecosystem wallets (#4756)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
The focus of this PR is to handle biging serialization for ecosystem wallets.

### Detailed summary
- Updated `signTypedData`, `signMessage`, and `signTransaction` functions to use `stringify` for serialization
- Improved handling of payload data in ecosystem wallets

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

<!-- end pr-codex -->
2024-09-23 05:29:44 +00:00
joaquim-verges 1133ccc239 fix: improve ABI resolution fallback (#4753)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on fixing ABI resolution fallback in `resolve-abi.ts`.

### Detailed summary
- Removed unnecessary imports
- Updated ABI resolution logic with new imports
- Improved handling of root ABI merging
- Enhanced efficiency in joining ABIs

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

<!-- end pr-codex -->
2024-09-23 02:14:14 +00:00
kien-ngo 3eef910a24 [Dashboard] Remove HStack (#4749)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces changes to replace `HStack` with `div` using flex classes in various components for better styling consistency.

### Detailed summary
- Replaced `HStack` with `div` using flex classes in multiple components for styling consistency.

> The following files were skipped due to too many changes: `apps/dashboard/src/components/smart-wallets/SponsorshipPolicies/index.tsx`

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

<!-- end pr-codex -->
2024-09-23 01:29:48 +00:00
kien-ngo 3d52dd4587 [Dashboard] Remove VStack (#4748)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
The focus of this PR is to replace the `VStack` components with `div` elements styled using Tailwind CSS classes for layout consistency and improved accessibility.

### Detailed summary
- Replaced `VStack` with `div` in multiple components for layout consistency
- Used Tailwind CSS classes for styling and spacing
- Improved accessibility and readability of the codebase

> The following files were skipped due to too many changes: `apps/dashboard/src/components/settings/Account/AccountForm.tsx`, `apps/dashboard/src/components/settings/Account/Usage.tsx`

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

<!-- end pr-codex -->
2024-09-23 01:29:46 +00:00
jnsdls 24ff6f2ab6 knip it in the... (#4751)
1. make `knip` a part of `packages/thirdweb`
2. clean up all the things
3. ???
4. profit

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on removing unused files, updating types, and deprecating constants.

### Detailed summary
- Removed unused files related to benchmarks, wallets, and UI components
- Updated type definitions in various files
- Deprecated and replaced constant `ADDRESS_ZERO` with `ZERO_ADDRESS`

> The following files were skipped due to too many changes: `packages/thirdweb/src/react/web/ui/ConnectWallet/Details.tsx`, `packages/thirdweb/src/pay/utils/definitions.ts`, `packages/thirdweb/src/utils/hashing/sha256.ts`, `packages/thirdweb/src/wallets/eip5792/show-calls-status.ts`, `packages/thirdweb/src/extensions/modules/ClaimableERC20/mint.ts`, `packages/thirdweb/src/react/web/wallets/shared/ConnectWalletSocialOptions.tsx`, `apps/portal/package.json`, `packages/thirdweb/src/extensions/modules/ClaimableERC721/mint.ts`, `packages/thirdweb/src/utils/hashing/hashMessage.ts`, `packages/thirdweb/src/react/native/ui/components/text.tsx`, `packages/thirdweb/src/utils/promise/resolve-promised-value.ts`, `packages/thirdweb/src/auth/core/verify-jwt.ts`, `packages/thirdweb/src/utils/hashing/keccak256.ts`, `packages/thirdweb/src/utils/uint8-array.ts`, `packages/thirdweb/src/react/core/design-system/CustomThemeProvider.tsx`, `packages/thirdweb/src/react/native/ui/connect/SendScreen.tsx`, `packages/thirdweb/src/react/web/ui/MediaRenderer/MediaRenderer.tsx`, `packages/thirdweb/src/gas/estimate-l1-fee.ts`, `packages/thirdweb/src/react/web/ui/ConnectWallet/WalletSelector.tsx`, `packages/thirdweb/src/extensions/modules/MintableERC721/mintWithSignature.ts`, `packages/thirdweb/src/react/native/ui/connect/ExternalWalletsList.tsx`, `packages/thirdweb/src/exports/extensions/erc1155.ts`, `packages/thirdweb/src/extensions/modules/ClaimableERC1155/mint.ts`, `packages/thirdweb/src/extensions/modules/MintableERC1155/mintWithSignature.ts`, `packages/thirdweb/src/utils/encoding/helpers/trim.ts`, `packages/thirdweb/src/wallets/utils/getTokenBalance.ts`, `apps/dashboard/knip.json`, `packages/thirdweb/src/react/native/ui/connect/TokenListScreen.tsx`, `packages/thirdweb/src/extensions/vote/write/executeProposal.ts`, `packages/thirdweb/src/react/native/ui/components/spinner.tsx`, `packages/thirdweb/src/react/web/ui/MediaRenderer/ModelViewer.tsx`, `packages/thirdweb/src/transaction/extract-error.ts`, `packages/thirdweb/src/react/native/ui/components/button.tsx`, `packages/thirdweb/src/extensions/erc1155/write/updateTokenURI.ts`, `packages/thirdweb/src/wallets/interfaces/wallet.ts`, `packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/useSwapSupportedChains.ts`, `packages/thirdweb/src/utils/storage/walletStorage.ts`, `packages/thirdweb/src/wallets/in-app/native/native-connector.ts`, `packages/thirdweb/src/extensions/erc20/write/sigMint.ts`, `packages/thirdweb/src/extensions/erc721/write/updateTokenURI.ts`, `packages/thirdweb/src/react/web/wallets/ecosystem/EcosystemWalletFormUI.tsx`, `packages/thirdweb/src/wallets/eip5792/wait-for-bundle.ts`, `packages/thirdweb/src/utils/jwt/jwt-header.ts`, `packages/thirdweb/src/extensions/modules/MintableERC20/mintWithSignature.ts`, `packages/thirdweb/src/exports/modules.ts`, `packages/thirdweb/src/react/web/wallets/in-app/InAppWalletFormUI.tsx`, `packages/thirdweb/src/transaction/actions/wait-for-tx-receipt.ts`, `packages/thirdweb/src/auth/core/generate-jwt.ts`, `packages/thirdweb/src/react/core/hooks/transaction/useSendTransaction.ts`, `packages/thirdweb/src/exports/extensions/erc1271.ts`, `packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/main/useBuyTxStates.ts`, `packages/thirdweb/src/contract/deployment/publisher.ts`, `packages/thirdweb/src/extensions/erc721/drops/write/updateMetadata.ts`, `packages/thirdweb/src/extensions/erc1155/drops/write/updateMetadata.ts`, `packages/thirdweb/src/extensions/prebuilts/deploy-marketplace.ts`, `packages/thirdweb/src/wallets/in-app/native/auth/index.ts`, `packages/thirdweb/src/wallets/in-app/native/helpers/auth/middleware.ts`, `packages/thirdweb/src/utils/platform.ts`, `.github/CODEOWNERS`, `packages/thirdweb/src/wallets/in-app/web/lib/auth/index.ts`, `packages/thirdweb/src/react/core/utils/structuralSharing.ts`, `packages/thirdweb/src/react/core/hooks/transaction/useSendAndConfirmTransaction.ts`, `packages/thirdweb/src/extensions/prebuilts/get-required-transactions.ts`, `packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/pay-transactions/FiatDetailsScreen.tsx`, `packages/thirdweb/src/extensions/ens/resolve-l2-name.ts`, `packages/thirdweb/src/utils/any-evm/deploy-metadata.ts`, `packages/thirdweb/src/cli/commands/generate/utils.ts`, `packages/thirdweb/src/extensions/farcaster/eip712Signatures/keyRequestSignature.ts`, `packages/thirdweb/src/extensions/airdrop/write/airdropERC20WithSignature.ts`, `packages/thirdweb/src/extensions/airdrop/write/airdropERC721WithSignature.ts`, `packages/thirdweb/src/transaction/actions/zksync/send-eip712-transaction.ts`, `packages/thirdweb/src/wallets/in-app/web/utils/iFrameCommunication/InAppWalletIframeCommunicator.ts`, `packages/thirdweb/src/utils/any-evm/zksync/constants.ts`, `packages/thirdweb/src/extensions/marketplace/direct-listings/utils.ts`, `packages/thirdweb/src/transaction/actions/gasless/send-gasless-transaction.ts`, `packages/thirdweb/src/extensions/prebuilts/deploy-vote.ts`, `packages/thirdweb/src/react/web/ui/components/Drawer.tsx`, `packages/thirdweb/src/extensions/airdrop/write/airdropERC1155WithSignature.ts`, `packages/thirdweb/src/react/core/hooks/others/useWalletBalance.ts`, `packages/thirdweb/src/extensions/permissions/utils.ts`, `packages/thirdweb/src/wallets/in-app/native/helpers/auth/logout.ts`, `packages/thirdweb/src/wallets/smart/lib/utils.ts`, `packages/thirdweb/src/react/web/ui/components/buttons.tsx`, `packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx`, `packages/thirdweb/src/wallets/wallet-connect/receiver/index.ts`, `packages/thirdweb/src/transaction/actions/gasless/providers/engine.ts`, `packages/thirdweb/src/transaction/actions/gasless/providers/openzeppelin.ts`, `packages/thirdweb/src/wallets/in-app/web/types.ts`, `packages/thirdweb/src/extensions/airdrop/write/claimERC20.ts`, `packages/thirdweb/test/globalSetup.ts`, `packages/thirdweb/src/extensions/erc721/read/getAllOwners.ts`, `packages/thirdweb/src/wallets/smart/lib/userop.ts`, `packages/thirdweb/src/wallets/in-app/native/helpers/wallet/retrieval.ts`, `packages/thirdweb/src/wallets/in-app/web/lib/iframe-wallet.ts`, `packages/thirdweb/src/react/web/ui/TransactionButton/TransactionModal.tsx`, `packages/thirdweb/src/react/native/ui/connect/ConnectedModal.tsx`, `packages/thirdweb/src/extensions/erc4337/account/common.ts`, `packages/thirdweb/test/vitest.config.ts`, `packages/thirdweb/src/exports/extensions/farcaster.ts`, `packages/thirdweb/src/exports/chains.ts`, `packages/thirdweb/src/extensions/erc4337/account/permissions.test.ts`, `packages/thirdweb/src/wallets/in-app/core/constants/settings.ts`, `packages/thirdweb/scripts/typedoc.mjs`, `packages/thirdweb/src/extensions/airdrop/write/merkleInfoERC20.ts`, `packages/thirdweb/src/utils/extensions/drops/hash-entry.ts`, `packages/thirdweb/src/extensions/farcaster/eip712Signatures/registerSignature.ts`, `packages/thirdweb/src/react/native/ui/icons/svgs.ts`, `packages/thirdweb/src/transaction/actions/gasless/providers/biconomy.ts`, `packages/thirdweb/src/extensions/farcaster/eip712Signatures/addSignature.ts`, `packages/thirdweb/src/react/web/ui/AutoConnect/AutoConnect.tsx`, `packages/thirdweb/src/react/native/ui/AutoConnect/AutoConnect.tsx`, `packages/service-utils/package.json`, `packages/thirdweb/src/stories/utils.tsx`, `packages/thirdweb/src/react/core/utils/defaultTokens.ts`, `package.json`, `packages/thirdweb/knip.json`, `packages/thirdweb/src/pay/buyWithCrypto/getStatus.ts`, `packages/thirdweb/src/exports/extensions/marketplace.ts`, `packages/thirdweb/src/utils/semver.ts`, `packages/thirdweb/src/extensions/uniswap/read/getUniswapV3Pools.ts`, `apps/dashboard/package.json`, `packages/thirdweb/src/utils/royalty-engine.ts`, `packages/react-native-adapter/package.json`, `packages/thirdweb/src/wallets/in-app/core/authentication/types.ts`, `packages/thirdweb/src/contract/deployment/deploy-deterministic.ts`, `packages/thirdweb/src/contract/deployment/utils/infra.ts`, `packages/thirdweb/src/wallets/injected/index.ts`, `packages/thirdweb/src/wallets/smart/lib/packUserOp.ts`, `packages/thirdweb/src/wallets/in-app/native/helpers/storage/local.ts`, `packages/thirdweb/src/react/web/ui/components/ChainIcon.tsx`, `packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/pay-transactions/BuyTxHistory.tsx`, `packages/thirdweb/src/wallets/in-app/native/helpers/constants.ts`, `packages/thirdweb/src/wallets/in-app/native/helpers/api/fetchers.ts`, `packages/thirdweb/package.json`, `packages/thirdweb/src/react/core/utils/walletIcon.ts`, `pnpm-lock.yaml`

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

<!-- end pr-codex -->
2024-09-23 00:00:12 +00:00
Joaquim VergesandGitHub e9ae98084a feat(ReactNative): respect icon theme color in wallet UI (#4752) 2024-09-23 11:56:47 +12:00
MananTank d936d4e5b9 Remove permissions form field in ecosystem partners page + UI adjusments (#4747)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR removes unused code related to partner permissions and updates UI components in the dashboard ecosystem section.

### Detailed summary
- Removed unused `PartnerPermission` type and related code
- Updated UI components in the dashboard ecosystem section for better user experience

> The following files were skipped due to too many changes: `apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/add-partner-form.client.tsx`

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

<!-- end pr-codex -->
2024-09-21 22:51:27 +00:00
MananTank bc7288370c Migrate Network Config Modal from chakra to shadcn/tailwind (#4745)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR removes unused components, updates UI elements, and refactors form fields in the dashboard application.

### Detailed summary
- Deleted `TooltipBox.tsx` and `ConfirmationPopover.tsx`
- Updated UI elements in `IconUpload.tsx` and `ConfigureNetworkModal.tsx`
- Refactored form fields in `ChainIdInput.tsx`, `RpcInput.tsx`, and `NetworkIDInput.tsx`

> The following files were skipped due to too many changes: `apps/dashboard/src/@/components/ui/radio-group.tsx`, `apps/dashboard/src/components/configure-networks/ConfigureNetworkForm.tsx`

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

<!-- end pr-codex -->
2024-09-21 21:24:47 +00:00
MananTank 0a4e44575d Remove all chain override contexts and move to global store (#4743)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
The focus of this PR is to refactor chain-related functionality and imports, update TypeScript types, and improve store management.

### Detailed summary
- Refactored chain-related functionality and imports
- Updated TypeScript types for chain mapping
- Improved store management for chains
- Updated API calls for chain data
- Removed unused hooks and contexts

> The following files were skipped due to too many changes: `apps/dashboard/src/components/configure-networks/ConfigureNetworkModal.tsx`, `apps/dashboard/src/contract-ui/tabs/account-permissions/components/account-signer.tsx`, `apps/dashboard/src/contract-ui/tabs/split/page.tsx`, `apps/dashboard/src/stores/SyncStoreToStorage.tsx`, `apps/dashboard/src/components/contract-components/contract-publish-form/NetworkDropdown.tsx`, `apps/dashboard/src/components/configure-networks/Form/NetworkIdInput.tsx`, `apps/dashboard/src/contract-ui/tabs/embed/components/embed-setup.tsx`, `apps/dashboard/src/contract-ui/tabs/code/components/code-overview.tsx`, `apps/dashboard/src/components/engine/contract-subscription/contract-subscriptions-table.tsx`, `apps/dashboard/src/components/buttons/MismatchButton.tsx`, `apps/dashboard/src/components/selects/NetworkSelectDropdown.tsx`, `apps/dashboard/src/components/selects/CustomChainRenderer.tsx`, `apps/dashboard/src/components/app-layouts/app.tsx`, `apps/dashboard/src/components/contract-components/published-contract/addresses-modal.tsx`, `apps/dashboard/src/components/contract-components/tables/deployed-contracts.tsx`, `apps/dashboard/src/lib/v5-adapter.ts`, `apps/dashboard/src/components/engine/overview/transactions-table.tsx`, `apps/dashboard/src/@3rdweb-sdk/react/hooks/useRegistry.ts`, `apps/dashboard/src/stores/chainStores.tsx`, `apps/dashboard/src/components/engine/relayer/relayers-table.tsx`, `apps/dashboard/src/components/configure-networks/ConfigureNetworkForm.tsx`, `apps/dashboard/src/hooks/chains/allChains.ts`, `apps/dashboard/src/pages/[chain_id]/[...paths].tsx`, `apps/dashboard/src/components/selects/NetworkSelectorButton.tsx`, `apps/dashboard/src/@3rdweb-sdk/react/components/connect-wallet/index.tsx`

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

<!-- end pr-codex -->
2024-09-21 21:10:49 +00:00
arcoraven f2307fde02 chore: show min version number (#4744)
<!-- start pr-codex -->

## PR-Codex overview
This PR updates the alert management feature message for cloud-hosted Engines to specify version requirement.

### Detailed summary
- Updated alert management feature message for cloud-hosted Engines to require v2.0.10 or later.

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

<!-- end pr-codex -->
2024-09-21 16:30:43 +00:00
kien-ngo a4dac95630 [SDK] Fix jsdoc for getCLaimParams (#4738)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
The focus of this PR is to fix the JSDoc for the `getClaimParams` function in the `thirdweb` package.

### Detailed summary
- Updated JSDoc for `getClaimParams` in `thirdweb/utils` directory.
- Changed import path from `thirdweb/extensions/erc1155` to `thirdweb/utils`.

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

<!-- end pr-codex -->
2024-09-21 11:08:35 +00:00
Jonas DanielsGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
f424d0ab2d Version Packages (#4741)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-21 22:46:48 +12:00
Joaquim VergesandGitHub 915442e1c4 feat: enable wallet linking and guest mode for ecosystem wallets (#4740) 2024-09-21 22:31:25 +12:00
1166 changed files with 24070 additions and 30986 deletions
-1
View File
@@ -11,7 +11,6 @@ packages/thirdweb/src/react/ @joaquim-verges @gregfromstl @MananTank @jnsdls @ed
packages/thirdweb/src/reactive/ @joaquim-verges @gregfromstl @MananTank @jnsdls
packages/thirdweb/src/gas/ @joaquim-verges @jnsdls
packages/thirdweb/src/pay/ @joaquim-verges @gregfromstl @MananTank @jnsdls @edwardysun
packages/typedoc-gen/ @MananTank @jnsdls @joaquim-verges
packages/service-utils/ @arcoraven @jnsdls @joaquim-verges
packages/eslint-config-thirdweb/ @jnsdls @joaquim-verges
packages/tw-tsconfig/ @jnsdls @joaquim-verges
+6
View File
@@ -55,6 +55,11 @@ module.exports = {
"FormErrorMessage",
"MenuGroup",
"MenuItem",
"VStack",
"HStack",
"AspectRatio",
"useToast",
"useClipboard",
// also the types
"ButtonProps",
"BadgeProps",
@@ -65,6 +70,7 @@ module.exports = {
"HelpTextProps",
"MenuGroupProps",
"MenuItemProps",
"AspectRatioProps",
],
message:
'Use the equivalent component from "tw-components" instead.',
+1 -1
View File
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.1/schema.json",
"$schema": "https://biomejs.dev/schemas/1.9.2/schema.json",
"extends": ["../../biome.json"],
"overrides": [
{
+1 -1
View File
@@ -3,6 +3,6 @@
"next": true,
"ignore": ["src/@/components/ui/**"],
"project": ["src/**"],
"ignoreBinaries": ["only-allow", "knip", "biome"],
"ignoreBinaries": ["only-allow", "biome"],
"ignoreDependencies": ["@storybook/blocks", "@thirdweb-dev/service-utils"]
}
+19 -18
View File
@@ -10,6 +10,7 @@
"format": "biome format ./src --write",
"lint": "biome check ./src && knip && eslint ./src",
"fix": "biome check ./src --fix && eslint ./src --fix",
"typecheck": "tsc --noEmit",
"gen:theme-typings": "chakra-cli tokens src/theme/index.ts",
"postinstall": "pnpm run gen:theme-typings",
"postbuild": "next-sitemap",
@@ -30,7 +31,6 @@
"@hookform/resolvers": "^3.9.0",
"@n8tb1t/use-scroll-position": "^2.0.3",
"@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-aspect-ratio": "^1.0.3",
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-dialog": "1.1.1",
@@ -62,13 +62,13 @@
"compare-versions": "^6.1.0",
"date-fns": "^3.6.0",
"flat": "^6.0.1",
"framer-motion": "11.5.4",
"framer-motion": "11.5.6",
"fuse.js": "7.0.0",
"ioredis": "^5.4.1",
"ipaddr.js": "^2.2.0",
"lottie-react": "^2.4.0",
"lucide-react": "0.441.0",
"next": "14.2.12",
"next": "14.2.13",
"next-plausible": "^3.12.0",
"next-seo": "^6.5.0",
"next-themes": "^0.3.0",
@@ -107,24 +107,24 @@
"devDependencies": {
"@chakra-ui/cli": "^2.4.1",
"@chromatic-com/storybook": "2.0.2",
"@next/bundle-analyzer": "14.2.12",
"@next/eslint-plugin-next": "14.2.12",
"@playwright/test": "1.47.1",
"@storybook/addon-essentials": "8.3.1",
"@storybook/addon-interactions": "8.3.1",
"@storybook/addon-links": "8.3.1",
"@storybook/addon-onboarding": "8.3.1",
"@storybook/addon-viewport": "8.3.1",
"@storybook/blocks": "8.3.1",
"@storybook/nextjs": "8.3.1",
"@storybook/react": "8.3.1",
"@storybook/test": "8.3.1",
"@next/bundle-analyzer": "14.2.13",
"@next/eslint-plugin-next": "14.2.13",
"@playwright/test": "1.47.2",
"@storybook/addon-essentials": "8.3.2",
"@storybook/addon-interactions": "8.3.2",
"@storybook/addon-links": "8.3.2",
"@storybook/addon-onboarding": "8.3.2",
"@storybook/addon-viewport": "8.3.2",
"@storybook/blocks": "8.3.2",
"@storybook/nextjs": "8.3.2",
"@storybook/react": "8.3.2",
"@storybook/test": "8.3.2",
"@types/color": "^3.0.6",
"@types/node": "20.14.9",
"@types/papaparse": "^5.3.14",
"@types/pluralize": "^0.0.33",
"@types/qrcode": "^1.5.5",
"@types/react": "^18.3.7",
"@types/react": "^18.3.8",
"@types/react-dom": "^18",
"@types/react-table": "^7.7.20",
"@types/spdx-correct": "^3.1.3",
@@ -134,12 +134,13 @@
"autoprefixer": "^10.4.19",
"checkly": "^4.8.1",
"eslint": "8.57.0",
"eslint-config-biome": "1.8.3",
"eslint-config-biome": "1.8.4",
"eslint-plugin-react-compiler": "0.0.0-experimental-ca16900-20240916",
"eslint-plugin-storybook": "^0.8.0",
"knip": "^5.30.5",
"next-sitemap": "^4.2.3",
"postcss": "8.4.47",
"storybook": "8.3.1",
"storybook": "8.3.2",
"tailwindcss": "3.4.12",
"typescript": "5.6.2"
}
@@ -28,8 +28,8 @@ export const Mobile: Story = {
function Story() {
return (
<div className="min-h-screen bg-background text-foreground py-4">
<div className="lg:p-10 container max-w-[1000px] flex flex-col gap-8">
<div className="min-h-screen bg-background py-4 text-foreground">
<div className="container flex max-w-[1000px] flex-col gap-8 lg:p-10">
<BadgeContainer label="Base">
<DangerSettingCard
title="This is a title"
@@ -24,21 +24,21 @@ export function DangerSettingCard(props: {
};
}) {
return (
<div className="border border-red-500/70 rounded-lg overflow-hidden">
<div className="px-4 lg:px-6 py-6 bg-muted/50">
<h3 className="text-xl font-semibold tracking-tight">{props.title}</h3>
<div className="overflow-hidden rounded-lg border border-red-500/70">
<div className="bg-muted/50 px-4 py-6 lg:px-6">
<h3 className="font-semibold text-xl tracking-tight">{props.title}</h3>
<p className="text-foreground text-sm mt-1.5 mb-4">
<p className="mt-1.5 mb-4 text-foreground text-sm">
{props.description}
</p>
</div>
<div className="bg-red-100 dark:bg-red-500/20 px-4 lg:px-6 py-4 flex justify-end border-red-500/70 border-t">
<div className="flex justify-end border-red-500/70 border-t bg-red-100 px-4 py-4 lg:px-6 dark:bg-red-500/20">
<Dialog>
<DialogTrigger asChild>
<Button
variant="destructive"
className="bg-red-600 hover:bg-red-600/80 text-white font-semibold gap-2"
className="gap-2 bg-red-600 font-semibold text-white hover:bg-red-600/80"
disabled={props.isPending}
>
{props.isPending && <Spinner className="size-3" />}
@@ -64,7 +64,7 @@ export function DangerSettingCard(props: {
<Button
variant="destructive"
className="bg-red-600 hover:bg-red-600/80 text-white font-semibold gap-2"
className="gap-2 bg-red-600 font-semibold text-white hover:bg-red-600/80"
onClick={props.buttonOnClick}
disabled={props.isPending}
>
@@ -30,7 +30,7 @@ export function ExportToCSVButton(props: {
<Button
variant="outline"
disabled={props.disabled}
className={cn("border flex gap-2 items-center text-xs", props.className)}
className={cn("flex items-center gap-2 border text-xs", props.className)}
onClick={async () => {
exportMutation.mutate();
}}
@@ -0,0 +1,36 @@
import { Label } from "@/components/ui/label";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { AsteriskIcon, InfoIcon } from "lucide-react";
export function FormFieldSetup(props: {
htmlFor: string;
label: string;
errorMessage: React.ReactNode | undefined;
children: React.ReactNode;
tooltip: React.ReactNode | undefined;
isRequired: boolean;
}) {
return (
<div>
<div className="mb-2 inline-flex items-center gap-1">
<Label htmlFor={props.htmlFor}>{props.label}</Label>
{props.isRequired && (
<AsteriskIcon className="size-3.5 text-destructive-text" />
)}
{props.tooltip && (
<ToolTipLabel label={props.tooltip}>
<InfoIcon className="size-3.5 text-muted-foreground" />
</ToolTipLabel>
)}
</div>
{props.children}
{props.errorMessage && (
<p className="mt-1 text-destructive-text text-sm">
{props.errorMessage}
</p>
)}
</div>
);
}
@@ -0,0 +1,113 @@
import type { Meta, StoryObj } from "@storybook/react";
import { ImageIcon } from "lucide-react";
import { useState } from "react";
import { BadgeContainer } from "../../../stories/utils";
import { Spinner } from "../ui/Spinner/Spinner";
import { Button } from "../ui/button";
import { Img } from "./Img";
const meta = {
title: "blocks/Img",
component: Story,
parameters: {},
} satisfies Meta<typeof Story>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
args: {},
};
function Story() {
return (
<div className="flex flex-col gap-10 p-10">
<p> All images below are set with size-20 className </p>
<BadgeContainer label="No Src - pending">
<Img className="size-20" src={undefined} />
</BadgeContainer>
<BadgeContainer label="Failed to load - fallback">
<Img className="size-20" src="invalid-src" />
</BadgeContainer>
<BadgeContainer label="No Src, pending - rounded-full">
<Img className="size-20 rounded-full" src={undefined} />
</BadgeContainer>
<BadgeContainer label="Failed to load - fallback - rounded-full">
<Img className="size-20 rounded-full" src="invalid-src" />
</BadgeContainer>
<BadgeContainer label="Valid Src">
<Img className="size-20" src="https://picsum.photos/200" />
</BadgeContainer>
<BadgeContainer label="Valid Src, rounded">
<Img className="size-20 rounded-full" src="https://picsum.photos/200" />
</BadgeContainer>
<ToggleTest />
<BadgeContainer label="Custom Skeleton">
<Img
src={undefined}
skeleton={
<div className="flex items-center justify-center rounded-lg border">
<Spinner className="size-6" />
</div>
}
className="size-20"
/>
</BadgeContainer>
<BadgeContainer label="Custom Fallback">
<Img
fallback={
<div className="flex items-center justify-center rounded-lg border">
<ImageIcon className="size-10 text-muted-foreground" />
</div>
}
className="size-20"
src="invalid-src"
/>
</BadgeContainer>
</div>
);
}
function ToggleTest() {
const [src, setSrc] = useState<undefined | string>(undefined);
return (
<div className="relative flex flex-col gap-10 border p-6">
<Button
variant="outline"
onClick={() => {
if (src) {
setSrc(undefined);
} else {
setSrc("https://picsum.photos/400");
}
}}
className="absolute top-6 right-6 inline-flex"
>
Toggle Src
</Button>
<p> Src is {src ? "set" : "not set"} </p>
<BadgeContainer label="Valid Src">
<Img className="size-20" src={src} />
</BadgeContainer>
<BadgeContainer label="invalid Src">
<Img
className="size-20 rounded-full"
src={src ? "invalid-src" : undefined}
/>
</BadgeContainer>
</div>
);
}
@@ -0,0 +1,62 @@
/* eslint-disable @next/next/no-img-element */
"use client";
import { useState } from "react";
import { cn } from "../../lib/utils";
type imgElementProps = React.DetailedHTMLProps<
React.ImgHTMLAttributes<HTMLImageElement>,
HTMLImageElement
> & {
skeleton?: React.ReactNode;
fallback?: React.ReactNode;
src: string | undefined;
};
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, ...restProps } = props;
const defaultSkeleton = <div className="animate-pulse bg-accent" />;
const defaultFallback = <div className="bg-muted" />;
return (
<div className="relative">
<img
{...restProps}
onLoad={() => {
setStatus("loaded");
}}
onError={() => {
setStatus("fallback");
}}
style={{
opacity: status === "loaded" ? 1 : 0,
}}
alt={restProps.alt || ""}
className={cn(
"fade-in-0 object-cover transition-opacity duration-300",
className,
)}
/>
{status !== "loaded" && (
<div
className={cn(
"fade-in-0 absolute inset-0 overflow-hidden transition-opacity duration-300 [&>*]:h-full [&>*]:w-full",
className,
)}
>
{status === "pending" && (skeleton || defaultSkeleton)}
{status === "fallback" && (fallback || defaultFallback)}
</div>
)}
</div>
);
}
@@ -27,7 +27,7 @@ export function MobileSidebar(props: {
const defaultTrigger = (
<Button
className={cn(
"w-full lg:hidden text-left justify-between gap-2 bg-muted/50",
"w-full justify-between gap-2 bg-muted/50 text-left lg:hidden",
props.triggerClassName,
)}
variant="outline"
@@ -41,7 +41,7 @@ export function MobileSidebar(props: {
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>{props.trigger || defaultTrigger}</DialogTrigger>
<DialogContent
className="p-4 rounded-t-xl rounded-b-none"
className="rounded-t-xl rounded-b-none p-4"
dialogCloseClassName="hidden"
onClick={(e) => {
if (e.target instanceof HTMLAnchorElement) {
@@ -55,7 +55,7 @@ export function MobileSidebar(props: {
<Button size="sm" variant="ghost" asChild key={link.href}>
<NavLink
href={link.href}
className="!text-left justify-start flex gap-2 h-auto py-3"
className="!text-left flex h-auto justify-start gap-2 py-3"
activeClassName="bg-accent"
exactMatch={link.exactMatch}
tracking={link.tracking}
@@ -28,8 +28,8 @@ export const Mobile: Story = {
function Story() {
return (
<div className="min-h-screen bg-background text-foreground py-6">
<div className="lg:p-10 container max-w-[1100px] flex flex-col gap-10">
<div className="min-h-screen bg-background py-6 text-foreground">
<div className="container flex max-w-[1100px] flex-col gap-10 lg:p-10">
<BadgeContainer label="No Header">
<SettingsCard
bottomText="This is bottom text"
@@ -84,7 +84,7 @@ function Story() {
isPending: false,
onClick: () => {},
}}
noPermissionText={"You do not have permission to edit this"}
noPermissionText="You do not have permission to edit this"
errorText={undefined}
>
<ChildrenPlaceholder />
@@ -154,8 +154,8 @@ function Story() {
function ChildrenPlaceholder() {
return (
<div className="h-10 flex items-center justify-center text-muted-foreground/50 text-xs">
{"CHILDREN GOES HERE"}
<div className="flex h-10 items-center justify-center text-muted-foreground/50 text-xs">
CHILDREN GOES HERE
</div>
);
}
@@ -3,37 +3,38 @@ import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import type React from "react";
export function SettingsCard(props: {
bottomText: React.ReactNode;
header?: {
description: string | undefined;
title: string;
};
children: React.ReactNode;
errorText: string | undefined;
noPermissionText: string | undefined;
saveButton?: {
onClick?: () => void;
disabled: boolean;
isPending: boolean;
type?: "submit";
};
}) {
export function SettingsCard(
props: React.PropsWithChildren<{
bottomText: React.ReactNode;
header?: {
description: string | undefined;
title: string;
};
errorText: string | undefined;
noPermissionText: string | undefined;
saveButton?: {
onClick?: () => void;
disabled: boolean;
isPending: boolean;
type?: "submit";
};
}>,
) {
return (
<div className="border border-border rounded-lg bg-muted/50 relative">
<div className="relative rounded-lg border border-border bg-muted/50">
<div
className={cn(
"border-b border-border px-4 lg:px-6 py-6 relative",
"relative border-border border-b px-4 py-6 lg:px-6",
props.noPermissionText && "cursor-not-allowed",
)}
>
{props.header && (
<>
<h3 className="text-xl font-semibold tracking-tight">
<h3 className="font-semibold text-xl tracking-tight">
{props.header.title}
</h3>
{props.header.description && (
<p className="text-foreground text-sm mt-1.5 mb-4">
<p className="mt-1.5 mb-4 text-foreground text-sm">
{props.header.description}
</p>
)}
@@ -43,7 +44,7 @@ export function SettingsCard(props: {
{props.children}
</div>
<div className="px-4 lg:px-6 py-3 flex items-center justify-between gap-2 min-h-[60px]">
<div className="flex min-h-[60px] items-center justify-between gap-2 px-4 py-3 lg:px-6">
{props.noPermissionText ? (
<p className="text-muted-foreground text-sm">
{props.noPermissionText}
@@ -24,7 +24,7 @@ export function Sidebar(props: SidebarContentProps) {
return (
<aside
className={cn(
"w-[230px] flex-shrink-0 hidden lg:block self-start sticky top-0",
"sticky top-0 hidden w-[230px] flex-shrink-0 self-start lg:block",
props.className,
)}
>
@@ -37,7 +37,7 @@ export function Sidebar(props: SidebarContentProps) {
<NavLink
key={link.href}
href={link.href}
className="py-2 hover:bg-muted rounded-md px-3 text-muted-foreground text-sm flex items-center gap-2"
className="flex items-center gap-2 rounded-md px-3 py-2 text-muted-foreground text-sm hover:bg-muted"
activeClassName="text-foreground"
exactMatch={link.exactMatch}
>
@@ -55,8 +55,8 @@ function Story() {
function ContentPlaceholder() {
return (
<div className="bg-muted grow flex items-center justify-center">
<p className="text-muted-foreground text-3xl"> CHILDREN </p>
<div className="flex grow items-center justify-center bg-muted">
<p className="text-3xl text-muted-foreground"> CHILDREN </p>
</div>
);
}
@@ -13,7 +13,7 @@ export function SidebarLayout(props: {
return (
<div
className={cn(
"flex flex-col lg:flex-row gap-6 container px-4 w-full grow max-sm:pt-6",
"container flex w-full grow flex-col gap-6 px-4 max-sm:pt-6 lg:flex-row",
props.className,
)}
>
@@ -23,7 +23,7 @@ export function SidebarLayout(props: {
triggerClassName={props.mobileSidebarClassName}
/>
{/* min-w-0 is enough to ensure the content inside does not stretch this container */}
<main className="grow max-sm:w-full flex flex-col lg:pt-8 pb-10 min-w-0">
<main className="flex min-w-0 grow flex-col pb-10 max-sm:w-full lg:pt-8">
{children}
</main>
</div>
@@ -21,31 +21,31 @@ export function AppFooter(props: AppFooterProps) {
return (
<footer
className={cn(
"bg-muted/50 border-t border-border py-6 md:py-8 w-full",
"w-full border-border border-t bg-muted/50 py-6 md:py-8",
props.className,
)}
>
<div className="container flex flex-col gap-4 md:gap-6">
{/* top row */}
<div className="flex flex-col md:flex-row gap-4 md:justify-between md:items-center">
<div className="flex flex-row gap-3 items-center">
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div className="flex flex-row items-center gap-3">
<ThirdwebMiniLogo className="h-7" />
<p className="text-sm text-muted-foreground">© 2024 thirdweb</p>
<p className="text-muted-foreground text-sm">© 2024 thirdweb</p>
</div>
<div className="flex flex-row gap-3">
<Button size="icon" variant="ghost" asChild className="size-9">
<Link href="https://github.com/thirdweb-dev" target="_blank">
<SiGithub className="text-muted-foreground size-5" />
<SiGithub className="size-5 text-muted-foreground" />
</Link>
</Button>
<Button size="icon" variant="ghost" asChild className="size-9">
<Link href="https://www.tiktok.com/@thirdweb" target="_blank">
<SiTiktok className="text-muted-foreground size-5" />
<SiTiktok className="size-5 text-muted-foreground" />
</Link>
</Button>
<Button size="icon" variant="ghost" asChild className="size-9">
<Link href="https://www.instagram.com/thirdweb/" target="_blank">
<SiInstagram className="text-muted-foreground size-5" />
<SiInstagram className="size-5 text-muted-foreground" />
</Link>
</Button>
<Button size="icon" variant="ghost" asChild className="size-9">
@@ -53,83 +53,83 @@ export function AppFooter(props: AppFooterProps) {
href="hhttps://www.linkedin.com/company/third-web/"
target="_blank"
>
<SiLinkedin className="text-muted-foreground size-5" />
<SiLinkedin className="size-5 text-muted-foreground" />
</Link>
</Button>
<Button size="icon" variant="ghost" asChild className="size-9">
<Link href="https://www.youtube.com/@thirdweb_" target="_blank">
<SiYoutube className="text-muted-foreground size-5" />
<SiYoutube className="size-5 text-muted-foreground" />
</Link>
</Button>
<Button size="icon" variant="ghost" asChild className="size-9">
<Link href="https://discord.gg/thirdweb" target="_blank">
<SiDiscord className="text-muted-foreground size-5" />
<SiDiscord className="size-5 text-muted-foreground" />
</Link>
</Button>
<Button size="icon" variant="ghost" asChild className="size-9">
<Link href="https://www.reddit.com/r/thirdweb/" target="_blank">
<SiReddit className="text-muted-foreground size-5" />
<SiReddit className="size-5 text-muted-foreground" />
</Link>
</Button>
<Button size="icon" variant="ghost" asChild className="size-9">
<Link href="https://x.com/thirdweb" target="_blank">
<SiX className="text-muted-foreground size-5" />
<SiX className="size-5 text-muted-foreground" />
</Link>
</Button>
</div>
</div>
{/* bottom row */}
<div className="grid grid-rows-5 grid-cols-2 grid-flow-col gap-2 md:flex md:flex-row md:justify-between">
<div className="grid grid-flow-col grid-cols-2 grid-rows-5 gap-2 md:flex md:flex-row md:justify-between">
<Link
className="py-[6px] px-[10px] text-muted-foreground text-sm hover:underline"
className="px-[10px] py-[6px] text-muted-foreground text-sm hover:underline"
href="/"
>
Home
</Link>
<Link
className="py-[6px] px-[10px] text-muted-foreground text-sm hover:underline"
className="px-[10px] py-[6px] text-muted-foreground text-sm hover:underline"
href="https://blog.thirdweb.com"
target="_blank"
>
Blog
</Link>
<Link
className="py-[6px] px-[10px] text-muted-foreground text-sm hover:underline"
className="px-[10px] py-[6px] text-muted-foreground text-sm hover:underline"
href="https://portal.thirdweb.com/changelog"
target="_blank"
>
Changelog
</Link>
<Link
className="py-[6px] px-[10px] text-muted-foreground text-sm hover:underline"
className="px-[10px] py-[6px] text-muted-foreground text-sm hover:underline"
href="https://feedback.thirdweb.com/"
target="_blank"
>
Feedback
</Link>
<Link
className="py-[6px] px-[10px] text-muted-foreground text-sm hover:underline"
className="px-[10px] py-[6px] text-muted-foreground text-sm hover:underline"
href="https://thirdweb.com/privacy"
target="_blank"
>
Privacy Policy
</Link>
<Link
className="py-[6px] px-[10px] text-muted-foreground text-sm hover:underline"
className="px-[10px] py-[6px] text-muted-foreground text-sm hover:underline"
href="https://thirdweb.com/tos"
target="_blank"
>
Terms of Service
</Link>
<Link
className="py-[6px] px-[10px] text-muted-foreground text-sm hover:underline"
className="px-[10px] py-[6px] text-muted-foreground text-sm hover:underline"
href="https://thirdweb.com/gas"
target="_blank"
>
Gas Estimator
</Link>
<Link
className="py-[6px] px-[10px] text-muted-foreground text-sm hover:underline"
className="px-[10px] py-[6px] text-muted-foreground text-sm hover:underline"
href="https://thirdweb.com/chainlist"
target="_blank"
>
@@ -36,7 +36,7 @@ function createList(len: number) {
function Story() {
return (
<div className="py-6 max-w-[600px] mx-auto w-full px-4 flex flex-col gap-6">
<div className="mx-auto flex w-full max-w-[600px] flex-col gap-6 px-4 py-6">
<VariantTest defaultValues={[]} storyLabel="5 items" listLen={5} />
<VariantTest defaultValues={[]} storyLabel="5000 items" listLen={5000} />
<VariantTest
@@ -109,12 +109,12 @@ export const MultiSelect = React.forwardRef<
{...props}
onClick={handleTogglePopover}
className={cn(
"flex w-full p-3 rounded-md border min-h-10 h-auto items-center justify-between bg-inherit hover:bg-inherit",
"flex h-auto min-h-10 w-full items-center justify-between rounded-md border bg-inherit p-3 hover:bg-inherit",
className,
)}
>
{selectedValues.length > 0 ? (
<div className="flex justify-between w-full">
<div className="flex w-full justify-between">
{/* badges */}
<div className="flex flex-wrap items-center gap-1.5">
{selectedValues.slice(0, maxCount).map((value) => {
@@ -147,21 +147,21 @@ export const MultiSelect = React.forwardRef<
event.stopPropagation();
handleClear();
}}
className="hover:bg-muted p-1 rounded"
className="rounded p-1 hover:bg-muted"
>
<XIcon className="h-4 cursor-pointer text-muted-foreground" />
</div>
<Separator
orientation="vertical"
className="flex min-h-6 h-full"
className="flex h-full min-h-6"
/>
<ChevronDown className="h-4 cursor-pointer text-muted-foreground" />
</div>
</div>
) : (
<div className="flex items-center justify-between w-full">
<span className="text-sm text-muted-foreground">
<div className="flex w-full items-center justify-between">
<span className="text-muted-foreground text-sm">
{placeholder}
</span>
<ChevronDown className="h-4 cursor-pointer text-muted-foreground" />
@@ -186,10 +186,10 @@ export const MultiSelect = React.forwardRef<
placeholder="Search"
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
className="pl-10 py-4 !h-auto focus-visible:ring-0 focus-visible:ring-offset-0 border-0 border-b border-border rounded-b-none"
className="!h-auto rounded-b-none border-0 border-border border-b py-4 pl-10 focus-visible:ring-0 focus-visible:ring-offset-0"
onKeyDown={handleInputKeyDown}
/>
<SearchIcon className="size-4 absolute left-4 top-1/2 -translate-y-1/2 text-muted-foreground" />
<SearchIcon className="-translate-y-1/2 absolute top-1/2 left-4 size-4 text-muted-foreground" />
</div>
<ScrollShadow
@@ -199,7 +199,7 @@ export const MultiSelect = React.forwardRef<
{/* List */}
<div>
{optionsToShow.length === 0 && (
<div className="py-10 justify-center flex">
<div className="flex justify-center py-10">
No results found
</div>
)}
@@ -213,7 +213,7 @@ export const MultiSelect = React.forwardRef<
aria-selected={isSelected}
onClick={() => toggleOption(option.value)}
variant="ghost"
className="cursor-pointer flex gap-3 py-2 px-3 w-full justify-start rounded-sm"
className="flex w-full cursor-pointer justify-start gap-3 rounded-sm px-3 py-2"
ref={
i === optionsToShow.length - 1 ? lastItemRef : undefined
}
@@ -247,7 +247,7 @@ function ClosableBadge(props: {
onClose: () => void;
}) {
return (
<span className="rounded-xl py-1 px-2 text-xs flex items-center gap-3 bg-muted border border-border text-foreground">
<span className="flex items-center gap-3 rounded-xl border border-border bg-muted px-2 py-1 text-foreground text-xs">
{props.label}
<XIcon
className="h-3 w-3 cursor-pointer text-muted-foreground"
@@ -54,7 +54,7 @@ export function WalletAddress(props: {
// special case for zero address
if (address === ZERO_ADDRESS) {
return <span className="font-mono cursor-pointer">{shortenedAddress}</span>;
return <span className="cursor-pointer font-mono">{shortenedAddress}</span>;
}
return (
@@ -64,7 +64,7 @@ export function WalletAddress(props: {
onClick={(e) => e.stopPropagation()}
variant="link"
className={cn(
"flex flex-row gap-2 items-center px-0",
"flex flex-row items-center gap-2 px-0",
props.className,
)}
>
@@ -75,7 +75,7 @@ export function WalletAddress(props: {
thirdwebClient={thirdwebClient}
/>
)}
<span className="font-mono cursor-pointer">
<span className="cursor-pointer font-mono">
{profiles.data?.[0]?.name || shortenedAddress}
</span>
</Button>
@@ -89,7 +89,7 @@ export function WalletAddress(props: {
>
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold">Wallet Address</h3>
<h3 className="font-semibold text-lg">Wallet Address</h3>
<Button
variant="outline"
size="sm"
@@ -104,18 +104,18 @@ export function WalletAddress(props: {
{hasCopied ? "Copied!" : "Copy"}
</Button>
</div>
<p className="text-sm font-mono bg-muted p-2 rounded text-center">
<p className="rounded bg-muted p-2 text-center font-mono text-sm">
{lessShortenedAddress}
</p>
<h3 className="text-lg font-semibold">Social Profiles</h3>
<h3 className="font-semibold text-lg">Social Profiles</h3>
{profiles.isPending ? (
<p className="text-sm text-muted-foreground">Loading profiles...</p>
<p className="text-muted-foreground text-sm">Loading profiles...</p>
) : !profiles.data?.length ? (
<p className="text-sm text-muted-foreground">No profiles found</p>
<p className="text-muted-foreground text-sm">No profiles found</p>
) : (
profiles.data?.map((profile) => (
<div
className="flex flex-row gap-2 items-center"
className="flex flex-row items-center gap-2"
key={profile.type + profile.name}
>
{profile.avatar &&
@@ -136,13 +136,13 @@ export function WalletAddress(props: {
)}
</Avatar>
)}
<div className="flex flex-col gap-1 w-full">
<div className="flex flex-row gap-4 w-full items-center justify-between">
<h4 className="text-md font-semibold">{profile.name}</h4>
<div className="flex w-full flex-col gap-1">
<div className="flex w-full flex-row items-center justify-between gap-4">
<h4 className="font-semibold text-md">{profile.name}</h4>
<Badge variant="outline">{profile.type}</Badge>
</div>
{profile.bio && (
<p className="text-sm text-muted-foreground whitespace-normal line-clamp-1">
<p className="line-clamp-1 whitespace-normal text-muted-foreground text-sm">
{profile.bio}
</p>
)}
@@ -153,7 +153,7 @@ export function WalletAddress(props: {
<Button
asChild
variant="upsell"
className="text-sm flex flex-row gap-2 items-center"
className="flex flex-row items-center gap-2 text-sm"
size="sm"
>
<a
@@ -10,7 +10,7 @@ export function ColorModeToggle() {
const { setTheme, theme } = useTheme();
return (
<ClientOnly ssr={<Skeleton className="size-10 bg-accent border" />}>
<ClientOnly ssr={<Skeleton className="size-10 border bg-accent" />}>
<Button
variant="ghost"
size="icon"
@@ -20,12 +20,12 @@ export const CodeBlock = ({
{({ className, tokens, getLineProps, getTokenProps }) => (
<Card className={className}>
{canCopy && (
<CopyButton text={code} className="float-right p-2 m-2" />
<CopyButton text={code} className="float-right m-2 p-2" />
)}
<div
className={cn(
"block font-mono text-sm whitespace-pre-wrap p-4 overflow-auto",
"block overflow-auto whitespace-pre-wrap p-4 font-mono text-sm",
className,
)}
>
@@ -25,12 +25,12 @@ export function CopyTextButton(props: {
const { hasCopied, onCopy } = useClipboard(props.textToCopy, 1000);
const copyButton = hasCopied ? (
<CheckIcon
className={cn("size-3 text-green-500 shrink-0", props.iconClassName)}
className={cn("size-3 shrink-0 text-green-500", props.iconClassName)}
/>
) : (
<CopyIcon
className={cn(
"size-3 text-muted-foreground shrink-0",
"size-3 shrink-0 text-muted-foreground",
props.iconClassName,
)}
/>
@@ -42,7 +42,7 @@ export function CopyTextButton(props: {
variant={props.variant || "outline"}
aria-label={props.tooltip}
className={cn(
"h-auto w-auto px-2.5 py-1.5 flex gap-2 rounded-lg text-foreground font-normal",
"flex h-auto w-auto gap-2 rounded-lg px-2.5 py-1.5 font-normal text-foreground",
props.className,
)}
onClick={(e) => {
@@ -53,13 +53,13 @@ export function CopyTextButton(props: {
>
{props.copyIconPosition === "right" ? (
<>
<span className="truncate min-w-0"> {props.textToShow} </span>
<span className="min-w-0 truncate"> {props.textToShow} </span>
{copyButton}
</>
) : (
<>
{copyButton}
<span className="truncate min-w-0"> {props.textToShow} </span>
<span className="min-w-0 truncate"> {props.textToShow} </span>
</>
)}
</Button>
@@ -48,9 +48,9 @@ export function DatePickerWithRange(props: {
<PopoverTrigger asChild>
<Button
id="date"
variant={"outline"}
variant="outline"
className={cn(
"justify-start text-left font-normal gap-2",
"justify-start gap-2 text-left font-normal",
props.className,
)}
>
@@ -60,7 +60,7 @@ export function DatePickerWithRange(props: {
{format(from, "LLL dd, y")} - {format(to, "LLL dd, y")}
</>
)}
<ChevronDownIcon className="h-4 w-4 ml-auto text-muted-foreground" />
<ChevronDownIcon className="ml-auto h-4 w-4 text-muted-foreground" />
</Button>
</PopoverTrigger>
@@ -69,7 +69,7 @@ export function DatePickerWithRange(props: {
<DynamicHeight>
<div>
{!isValid && (
<p className="text-sm pt-2 justify-center text-destructive-text text-center flex items-center gap-2">
<p className="flex items-center justify-center gap-2 pt-2 text-center text-destructive-text text-sm">
<CalendarX2Icon className="h-4 w-4" />
Invalid date range
</p>
@@ -94,7 +94,7 @@ export function ScrollShadow(props: {
}
>
<div
className={`${styles.scrollShadowTop} ${styles.scrollShadowY}`}
className={cn(styles.scrollShadowTop, styles.scrollShadowY)}
ref={shadowTopEl}
style={{
opacity: "0",
@@ -102,28 +102,28 @@ export function ScrollShadow(props: {
}}
/>
<div
className={`${styles.scrollShadowBottom} ${styles.scrollShadowY}`}
className={cn(styles.scrollShadowBottom, styles.scrollShadowY)}
ref={shadowBottomEl}
style={{
opacity: "0",
}}
/>
<div
className={`${styles.scrollShadowLeft} ${styles.scrollShadowX}`}
className={cn(styles.scrollShadowLeft, styles.scrollShadowX)}
ref={shadowLeftEl}
style={{
opacity: "0",
}}
/>
<div
className={`${styles.scrollShadowRight} ${styles.scrollShadowX}`}
className={cn(styles.scrollShadowRight, styles.scrollShadowX)}
ref={shadowRightEl}
style={{
opacity: "0",
}}
/>
<div
className={cn("overflow-auto no-scrollbar", props.scrollableClassName)}
className={cn("no-scrollbar overflow-auto", props.scrollableClassName)}
ref={scrollableEl}
>
{props.children}
@@ -1,10 +1,11 @@
import { cn } from "../../../lib/utils";
import style from "./Spinner.module.css";
export function Spinner(props: { className?: string }) {
return (
<svg
viewBox="0 0 50 50"
className={`${style.loader} ${props.className || "size-4"}`}
className={cn(style.loader, props.className || "size-4")}
>
<title>loading</title>
<circle
@@ -19,7 +19,7 @@ const AlertDialogOverlay = React.forwardRef<
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80 data-[state=closed]:animate-out data-[state=open]:animate-in",
className,
)}
{...props}
@@ -37,7 +37,7 @@ const AlertDialogContent = React.forwardRef<
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] rounded-lg",
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 data-[state=closed]:animate-out data-[state=open]:animate-in",
className,
)}
{...props}
@@ -80,7 +80,7 @@ const AlertDialogTitle = React.forwardRef<
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title
ref={ref}
className={cn("text-lg hyphens-auto font-semibold", className)}
className={cn("hyphens-auto font-semibold text-lg", className)}
{...props}
/>
));
@@ -92,7 +92,7 @@ const AlertDialogDescription = React.forwardRef<
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
));
+1 -1
View File
@@ -52,7 +52,7 @@ const AlertDescription = React.forwardRef<
<div
ref={ref}
className={cn(
"text-sm [&_p]:leading-relaxed text-muted-foreground mt-1.5",
"mt-1.5 text-muted-foreground text-sm [&_p]:leading-relaxed",
className,
)}
{...props}
@@ -1,7 +0,0 @@
"use client";
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
const AspectRatio = AspectRatioPrimitive.Root;
export { AspectRatio };
@@ -19,7 +19,7 @@ const BreadcrumbList = React.forwardRef<
<ol
ref={ref}
className={cn(
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
"flex flex-wrap items-center gap-1.5 break-words text-muted-foreground text-sm sm:gap-2.5",
className,
)}
{...props}
@@ -20,7 +20,7 @@ export const AllVariants: Story = {
function Component() {
return (
<div className="min-h-screen bg-background p-6 text-foregroun flex flex-col gap-6">
<div className="flex min-h-screen flex-col gap-6 bg-background p-6 text-foregroun">
<Variants size="default" />
<Variants size="sm" />
<Variants size="icon" />
+2 -2
View File
@@ -32,7 +32,7 @@ const CardTitle = React.forwardRef<
<h3
ref={ref}
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
"font-semibold text-2xl leading-none tracking-tight",
className,
)}
{...props}
@@ -46,7 +46,7 @@ const CardDescription = React.forwardRef<
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
));
+3 -3
View File
@@ -233,7 +233,7 @@ const ChartTooltipContent = React.forwardRef<
)}
<div
className={cn(
"flex flex-1 gap-2 justify-between leading-none",
"flex flex-1 justify-between gap-2 leading-none",
nestLabel ? "items-end" : "items-center",
)}
>
@@ -244,7 +244,7 @@ const ChartTooltipContent = React.forwardRef<
</span>
</div>
{item.value && (
<span className="font-mono font-medium tabular-nums text-foreground">
<span className="font-medium font-mono text-foreground tabular-nums">
{valueFormatter
? valueFormatter(item.value) || item.value
: item.value}
@@ -287,7 +287,7 @@ const ChartLegendContent = React.forwardRef<
<div
ref={ref}
className={cn(
"flex items-center justify-center gap-x-4 gap-y-2 overflow-x-auto flex-wrap",
"flex flex-wrap items-center justify-center gap-x-4 gap-y-2 overflow-x-auto",
verticalAlign === "top" ? "pb-3" : "pt-3",
className,
)}
@@ -37,7 +37,7 @@ export function CheckboxWithLabel(props: {
// biome-ignore lint/a11y/noLabelWithoutControl: cannot do this here because the checkbox is a child of the label
<label
className={cn(
"flex gap-2 items-start text-muted-foreground text-sm cursor-pointer select-none [&>button]:mt-0.5",
"flex cursor-pointer select-none items-start gap-2 text-muted-foreground text-sm [&>button]:mt-0.5",
props.className,
)}
>
@@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef<
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 bg-black/80 data-[state=closed]:animate-out data-[state=open]:animate-in",
className,
)}
{...props}
@@ -41,16 +41,16 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed grid w-full md:max-w-lg gap-4 border bg-background p-6 shadow-lg duration-300 rounded-t-xl md:rounded-lg border-border",
"fixed grid w-full gap-4 rounded-t-xl border border-border bg-background p-6 shadow-lg duration-300 md:max-w-lg md:rounded-lg",
// on mobile - put the dialog at the bottom of the screen, animate - slide up and fade in
"bottom-0 left-0 right-0",
"right-0 bottom-0 left-0",
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
// on desktop - vertically and horizontally center the dialog, animate - zoom and fade in
"md:bottom-auto md:left-[50%] md:top-[50%] md:translate-x-[-50%] md:translate-y-[-50%] ",
"md:top-[50%] md:bottom-auto md:left-[50%] md:translate-x-[-50%] md:translate-y-[-50%] ",
"md:data-[state=closed]:zoom-out-95 md:data-[state=open]:zoom-in-95 ",
"md:data-[state=closed]:slide-out-to-left-1/2 md:data-[state=closed]:slide-out-to-top-[48%] ",
"md:data-[state=open]:slide-in-from-left-1/2 md:data-[state=open]:slide-in-from-top-[48%] ",
"data-[state=open]:animate-in data-[state=closed]:animate-out",
"data-[state=closed]:animate-out data-[state=open]:animate-in",
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className,
)}
@@ -59,7 +59,7 @@ const DialogContent = React.forwardRef<
{children}
<DialogPrimitive.Close
className={cn(
"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground",
"absolute top-4 right-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground",
props.dialogCloseClassName,
)}
>
@@ -103,7 +103,7 @@ const DialogTitle = React.forwardRef<
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
"font-semibold text-lg leading-none tracking-tight",
className,
)}
{...props}
@@ -117,7 +117,7 @@ const DialogDescription = React.forwardRef<
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
));
@@ -47,7 +47,7 @@ const DropdownMenuSubContent = React.forwardRef<
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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",
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg data-[state=closed]:animate-out data-[state=open]:animate-in",
className,
)}
{...props}
@@ -65,7 +65,7 @@ const DropdownMenuContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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",
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md data-[state=closed]:animate-out data-[state=open]:animate-in",
className,
)}
{...props}
@@ -99,7 +99,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className,
)}
checked={checked}
@@ -123,7 +123,7 @@ const DropdownMenuRadioItem = React.forwardRef<
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className,
)}
{...props}
@@ -147,7 +147,7 @@ const DropdownMenuLabel = React.forwardRef<
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
"px-2 py-1.5 font-semibold text-sm",
inset && "pl-8",
className,
)}
+3 -3
View File
@@ -117,7 +117,7 @@ const RequiredFormLabel = React.forwardRef<
{...props}
>
{props.children}
<span className="absolute -top-1.5 -right-2 text-destructive-text">
<span className="-top-1.5 -right-2 absolute text-destructive-text">
•
</span>
</Label>
@@ -159,7 +159,7 @@ const FormDescription = React.forwardRef<
<p
ref={ref}
id={formDescriptionId}
className={cn("text-sm text-muted-foreground", className)}
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
);
@@ -181,7 +181,7 @@ const FormMessage = React.forwardRef<
<p
ref={ref}
id={formMessageId}
className={cn("text-sm font-medium text-destructive-text", className)}
className={cn("font-medium text-destructive-text text-sm", className)}
{...props}
>
{body}
@@ -18,7 +18,7 @@ const HoverCardContent = React.forwardRef<
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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",
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",
className,
)}
{...props}
@@ -31,7 +31,7 @@ const ImageUpload = React.forwardRef<HTMLInputElement, ImageUploadProps>(
<div
{...getRootProps()}
className={cn(
"border bg-card relative group cursor-pointer hover:border-primary flex-col gap-1.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 overflow-hidden transition-all rounded-md flex items-center justify-center p-4 py-6 text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
"group relative flex cursor-pointer flex-col items-center justify-center gap-1.5 overflow-hidden rounded-md border bg-card p-4 py-6 text-muted-foreground transition-all hover:border-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
isDragActive ? "border-primary" : "border-input",
!activeFile ? "w-full" : "min-h-32 min-w-32",
className,
@@ -47,8 +47,8 @@ const ImageUpload = React.forwardRef<HTMLInputElement, ImageUploadProps>(
/>
) : (
<>
<UploadIcon className="w-5 h-5 transition-all group-hover:scale-110" />
<p className="text-xs text-center">
<UploadIcon className="h-5 w-5 transition-all group-hover:scale-110" />
<p className="text-center text-xs">
Drag and drop a file or click to upload
</p>
</>
+1 -1
View File
@@ -11,7 +11,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-card px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
"flex h-10 w-full rounded-md border border-input bg-card px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
ref={ref}
@@ -46,7 +46,7 @@ const PaginationLink = ({
<Button
variant={!isActive ? "outline" : "default"}
className={cn(
"px-3 py-2 h-auto",
"h-auto px-3 py-2",
className,
props.disabled && "cursor-not-allowed",
)}
@@ -19,7 +19,7 @@ const PopoverContent = React.forwardRef<
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border border-border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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",
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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 w-72 rounded-md border border-border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",
className,
)}
{...props}
@@ -29,7 +29,7 @@ const RadioGroupItem = React.forwardRef<
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
"aspect-square h-4 w-4 rounded-full border border-inverted text-inverted ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
{...props}
@@ -51,19 +51,19 @@ const RadioGroupItemButton = React.forwardRef<
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
"cursor-pointer group flex items-center px-3 peer-hover:border-nonce py-4 space-x-3 space-y-0 border-2 rounded-md min-w-32 font-medium hover:border-foreground/25 transition-all data-[state=checked]:border-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
"group flex min-w-32 cursor-pointer items-center space-x-3 space-y-0 rounded-md border-2 px-3 py-4 font-medium transition-all hover:border-foreground/25 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 peer-hover:border-nonce data-[state=checked]:border-inverted",
className,
)}
{...props}
>
<div className="flex items-center justify-center w-4 h-4 border-2 rounded-full transition-all group-data-[state=checked]:border-primary text-primary group-hover:border-foreground/25 aspect-square ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50">
<div className="flex aspect-square h-4 w-4 items-center justify-center rounded-full border-2 text-inverted ring-offset-background transition-all focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 group-hover:border-foreground/25 group-data-[state=checked]:border-inverted">
{/* Show on checked */}
<RadioGroupPrimitive.Indicator className="flex items-center justify-center transition-all rounded-full">
<Circle className="w-2 h-2 text-current fill-current" />
<RadioGroupPrimitive.Indicator className="flex items-center justify-center rounded-full transition-all">
<Circle className="h-2 w-2 fill-current text-current" />
</RadioGroupPrimitive.Indicator>
{/* Show on hover */}
<div className="items-center justify-center hidden transition-all rounded-full text-foreground/25 group-hover:flex group-data-[state=checked]:hidden">
<Circle className="w-2 h-2 text-current fill-current" />
<div className="hidden items-center justify-center rounded-full text-foreground/25 transition-all group-hover:flex group-data-[state=checked]:hidden">
<Circle className="h-2 w-2 fill-current text-current" />
</div>
</div>
<Label className="cursor-pointer">{props.children}</Label>
@@ -31,7 +31,7 @@ function randomName() {
function Component() {
return (
<div className="min-h-screen bg-background p-6 text-foregroun flex flex-col gap-10">
<div className="flex min-h-screen flex-col gap-10 bg-background p-6 text-foregroun">
<BadgeContainer label="5 items, no placeholder, no label, value filled">
<SelectDemo listItems={5} selectFirst />
</BadgeContainer>
@@ -19,7 +19,7 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex gap-2 h-10 w-full items-center justify-between rounded-md border border-input bg-card px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
"flex h-10 w-full items-center justify-between gap-2 rounded-md border border-input bg-card px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className,
)}
{...props}
@@ -87,9 +87,9 @@ const SelectContent = React.forwardRef<
};
}}
className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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",
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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 relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md data-[state=closed]:animate-out data-[state=open]:animate-in",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
"data-[side=left]:-translate-x-1 data-[side=top]:-translate-y-1 data-[side=right]:translate-x-1 data-[side=bottom]:translate-y-1",
className,
)}
position={position}
@@ -117,7 +117,7 @@ const SelectLabel = React.forwardRef<
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
className={cn("py-1.5 pr-2 pl-8 font-semibold text-sm", className)}
{...props}
/>
));
@@ -130,7 +130,7 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className,
)}
{...props}
+6 -6
View File
@@ -21,7 +21,7 @@ const SheetOverlay = React.forwardRef<
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80 data-[state=closed]:animate-out data-[state=open]:animate-in",
className,
)}
{...props}
@@ -65,7 +65,7 @@ const SheetContent = React.forwardRef<
{...props}
>
{children}
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary border-border">
<SheetPrimitive.Close className="absolute top-4 right-4 rounded-sm border-border opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
@@ -80,7 +80,7 @@ const SheetHeader = ({
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left border-border",
"flex flex-col space-y-2 border-border text-center sm:text-left",
className,
)}
{...props}
@@ -94,7 +94,7 @@ const SheetFooter = ({
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 border-border",
"flex flex-col-reverse border-border sm:flex-row sm:justify-end sm:space-x-2",
className,
)}
{...props}
@@ -109,7 +109,7 @@ const SheetTitle = React.forwardRef<
<SheetPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold text-foreground border-border",
"border-border font-semibold text-foreground text-lg",
className,
)}
{...props}
@@ -123,7 +123,7 @@ const SheetDescription = React.forwardRef<
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground border-border", className)}
className={cn("border-border text-muted-foreground text-sm", className)}
{...props}
/>
));
@@ -25,7 +25,7 @@ function SkeletonContainer<T>(props: {
style={props.style}
aria-hidden={isLoading ? "true" : "false"}
className={cn(
isLoading && "animate-pulse rounded-lg bg-muted inline-block",
isLoading && "inline-block animate-pulse rounded-lg bg-muted",
props.className,
)}
>
@@ -36,7 +36,7 @@ export const Mobile: Story = {
function Component() {
return (
<div className="flex flex-col gap-6 lg:max-w-[1000px] lg:mx-auto min-w-0 py-6 px-4">
<div className="flex min-w-0 flex-col gap-6 px-4 py-6 lg:mx-auto lg:max-w-[1000px]">
<BadgeContainer label="Normal">
<TableDemo />
</BadgeContainer>
+8 -8
View File
@@ -10,7 +10,7 @@ const Table = React.forwardRef<
<table
ref={ref}
className={cn(
"caption-bottom w-full text-sm border-collapse tabular-nums lining-nums align-top",
"w-full caption-bottom border-collapse align-top text-sm lining-nums tabular-nums",
className,
)}
{...props}
@@ -25,7 +25,7 @@ const TableHeader = React.forwardRef<
<thead
ref={ref}
className={cn(
"bg-muted/50 border-b border-border [&_tr]:border-b",
"border-border border-b bg-muted/50 [&_tr]:border-b",
className,
)}
{...props}
@@ -53,7 +53,7 @@ const TableFooter = React.forwardRef<
<tfoot
ref={ref}
className={cn(
"border-t border-border bg-muted/50 font-medium [&>tr]:last:border-b-0",
"border-border border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
className,
)}
{...props}
@@ -68,7 +68,7 @@ const TableRow = React.forwardRef<
<tr
ref={ref}
className={cn(
"border-b border-border last:border-0 data-[state=selected]:bg-muted",
"border-border border-b last:border-0 data-[state=selected]:bg-muted",
className,
)}
{...props}
@@ -83,7 +83,7 @@ const TableHead = React.forwardRef<
<th
ref={ref}
className={cn(
"px-6 py-4 uppercase text-xs text-left tracking-wider align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
"px-6 py-4 text-left align-middle font-medium text-muted-foreground text-xs uppercase tracking-wider [&:has([role=checkbox])]:pr-0",
className,
)}
{...props}
@@ -98,7 +98,7 @@ const TableCell = React.forwardRef<
<td
ref={ref}
className={cn(
"px-6 py-4 text-sm text-start align-middle [&:has([role=checkbox])]:pr-0",
"px-6 py-4 text-start align-middle text-sm [&:has([role=checkbox])]:pr-0",
className,
)}
{...props}
@@ -112,7 +112,7 @@ const TableCaption = React.forwardRef<
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("text-sm text-muted-foreground py-4", className)}
className={cn("py-4 text-muted-foreground text-sm", className)}
{...props}
/>
));
@@ -121,7 +121,7 @@ TableCaption.displayName = "TableCaption";
function TableContainer(props: { children: React.ReactNode }) {
return (
<ScrollShadow
className="border border-border rounded-lg whitespace-nowrap relative"
className="relative whitespace-nowrap rounded-lg border border-border"
shadowColor="hsl(var(--muted))"
>
{props.children}
+6 -6
View File
@@ -27,7 +27,7 @@ export function TabLinks(props: {
return (
<div className={cn("relative", props.className)}>
{/* Bottom line */}
<div className="h-[1px] bg-border absolute bottom-0 left-0 right-0" />
<div className="absolute right-0 bottom-0 left-0 h-[1px] bg-border" />
<ScrollShadow
scrollableClassName="pb-[8px] relative"
@@ -51,7 +51,7 @@ export function TabLinks(props: {
href={tab.href}
aria-disabled={tab.isDisabled}
className={cn(
"rounded-lg hover:bg-muted px-3 font-normal text-sm lg:text-sm relative h-auto text-muted-foreground",
"relative h-auto rounded-lg px-3 font-normal text-muted-foreground text-sm hover:bg-muted lg:text-sm",
!tab.isActive && !tab.isDisabled && "hover:text-foreground",
tab.isDisabled && "pointer-events-none",
tab.isActive && "!text-foreground",
@@ -67,7 +67,7 @@ export function TabLinks(props: {
{/* Active line */}
<div
ref={lineRef}
className="absolute left-0 bottom-0 h-[2px] bg-foreground rounded-lg fade-in-0 animate-in"
className="fade-in-0 absolute bottom-0 left-0 h-[2px] animate-in rounded-lg bg-foreground"
/>
</ScrollShadow>
</div>
@@ -94,7 +94,7 @@ export function TabButtons(props: {
return (
<div className={cn("relative", props.containerClassName)}>
{/* Bottom line */}
<div className="h-[1px] bg-border absolute bottom-0 left-0 right-0" />
<div className="absolute right-0 bottom-0 left-0 h-[1px] bg-border" />
<ScrollShadow
scrollableClassName="pb-[8px] relative"
@@ -111,7 +111,7 @@ export function TabButtons(props: {
variant="ghost"
ref={tab.isActive ? activeTabRef : undefined}
className={cn(
"rounded-lg hover:bg-accent px-2 lg:px-3 font-medium text-sm lg:text-base relative h-auto inline-flex gap-1.5 items-center",
"relative inline-flex h-auto items-center gap-1.5 rounded-lg px-2 font-medium text-sm hover:bg-accent lg:px-3 lg:text-base",
!tab.isActive &&
"text-muted-foreground hover:text-foreground",
!tab.isEnabled && "cursor-not-allowed opacity-50",
@@ -130,7 +130,7 @@ export function TabButtons(props: {
{/* Active line */}
<div
ref={lineRef}
className="absolute left-0 bottom-0 h-[2px] bg-foreground rounded-lg fade-in-0 animate-in"
className="fade-in-0 absolute bottom-0 left-0 h-[2px] animate-in rounded-lg bg-foreground"
/>
</ScrollShadow>
</div>
@@ -19,7 +19,7 @@ const TooltipContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md border border-border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out 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",
"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,
)}
{...props}
@@ -48,9 +48,9 @@ export function ToolTipLabel(props: {
</TooltipTrigger>
<TooltipContent
sideOffset={10}
className="max-w-[400px] leading-relaxed whitespace-normal"
className="max-w-[400px] whitespace-normal leading-relaxed"
>
<div className="p-2 text-sm flex items-center gap-1.5">
<div className="flex items-center gap-1.5 p-2 text-sm">
{props.leftIcon}
{props.label}
{props.rightIcon}
@@ -1,4 +1,3 @@
import {} from "@/constants/env";
import { useQuery } from "@tanstack/react-query";
import { useMemo } from "react";
import { useActiveAccount } from "thirdweb/react";
+1 -1
View File
@@ -117,7 +117,7 @@ function DashboardRouterTopProgressBarInner() {
const width = isLoading ? progress : 100;
return (
<span
className="block fixed top-0 h-[2px] bg-blue-500"
className="fixed top-0 block h-[2px] bg-blue-500"
style={{
opacity: isLoading ? "100" : "0",
width: `${width}%`,
+7 -1
View File
@@ -1,4 +1,6 @@
type Store<T> = {
import { useSyncExternalStore } from "react";
export type Store<T> = {
getValue(): T;
setValue(newValue: T): void;
subscribe(listener: () => void): () => void;
@@ -45,3 +47,7 @@ export function createStore<T>(initialValue: T): Store<T> {
},
};
}
export function useStore<T>(store: Store<T>): T {
return useSyncExternalStore(store.subscribe, store.getValue, store.getValue);
}
@@ -3,28 +3,31 @@
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Button } from "@/components/ui/button";
import { useThirdwebClient } from "@/constants/thirdweb.client";
import { useStore } from "@/lib/reactive";
import { getSDKTheme } from "app/components/sdk-component-theme";
import { CustomChainRenderer } from "components/selects/CustomChainRenderer";
import { mapV4ChainToV5Chain } from "contexts/map-chains";
import { useTrack } from "hooks/analytics/useTrack";
import {
useSupportedChains,
useSupportedChainsRecord,
} from "hooks/chains/configureChains";
import {
useAddRecentlyUsedChainId,
useRecentlyUsedChains,
} from "hooks/chains/recentlyUsedChains";
import { useTheme } from "next-themes";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useCallback, useMemo, useState } from "react";
import type { Chain } from "thirdweb";
import { AutoConnect, ConnectButton, useConnectModal } from "thirdweb/react";
import {
AutoConnect,
ConnectButton,
type NetworkSelectorProps,
useConnectModal,
} from "thirdweb/react";
import { useFavoriteChainIds } from "../../../../app/(dashboard)/(chain)/components/client/star-button";
import { LazyConfigureNetworkModal } from "../../../../components/configure-networks/LazyConfigureNetworkModal";
import type { StoredChain } from "../../../../contexts/configured-chains";
import { useFavoriteChains } from "../../hooks/useFavoriteChains";
import { useAllChainsData } from "../../../../hooks/chains/allChains";
import {
type StoredChain,
addRecentlyUsedChainId,
recentlyUsedChainIdsStore,
} from "../../../../stores/chainStores";
import { useLoggedInUser } from "../../hooks/useLoggedInUser";
import { popularChains } from "../popularChains";
@@ -36,43 +39,81 @@ export const CustomConnectWallet = (props: {
const thirdwebClient = useThirdwebClient();
const loginRequired =
props.loginRequired === undefined ? true : props.loginRequired;
const { theme } = useTheme();
const recentChainsv4 = useRecentlyUsedChains();
const addRecentlyUsedChainId = useAddRecentlyUsedChainId();
const t = theme === "light" ? "light" : "dark";
const allv4Chains = useSupportedChains();
const chainsRecord = useSupportedChainsRecord();
const favChainsQuery = useFavoriteChains();
const allChains = useMemo(() => {
return allv4Chains.map(mapV4ChainToV5Chain);
}, [allv4Chains]);
const popularChainsWithMeta = useMemo(() => {
return popularChains.map((x) => chainsRecord[x.id] || x);
}, [chainsRecord]);
// chains
const favChainIdsQuery = useFavoriteChainIds();
const recentChainIds = useStore(recentlyUsedChainIdsStore);
const { idToChain, allChains } = useAllChainsData();
const allChainsWithMetadata = useMemo(
() => allChains.map(mapV4ChainToV5Chain),
[allChains],
);
const recentlyUsedChainsWithMetadata = useMemo(
() =>
recentChainIds
.map((id) => {
const c = idToChain.get(id);
// eslint-disable-next-line no-restricted-syntax
return c ? mapV4ChainToV5Chain(c) : undefined;
})
.filter((x) => !!x),
[recentChainIds, idToChain],
);
const favoriteChainsWithMetadata = useMemo(() => {
return (favChainIdsQuery.data || [])
.map((id) => {
const c = idToChain.get(Number(id));
// eslint-disable-next-line no-restricted-syntax
return c ? mapV4ChainToV5Chain(c) : undefined;
})
.filter((x) => !!x);
}, [idToChain, favChainIdsQuery.data]);
const popularChainsWithMetadata = useMemo(() => {
// eslint-disable-next-line no-restricted-syntax
return popularChains.map((x) =>
// eslint-disable-next-line no-restricted-syntax
{
const v4Chain = idToChain.get(x.id);
// eslint-disable-next-line no-restricted-syntax
return v4Chain ? mapV4ChainToV5Chain(v4Chain) : x;
},
);
}, [idToChain]);
// Network Config Modal
const [isNetworkConfigModalOpen, setIsNetworkConfigModalOpen] =
useState(false);
const [editChain, setEditChain] = useState<StoredChain | undefined>(
undefined,
);
const chainSections = useMemo(() => {
const chainSections: NetworkSelectorProps["sections"] = useMemo(() => {
return [
{
label: "Favorites",
chains: favChainsQuery.data.map(mapV4ChainToV5Chain),
},
{
label: "Popular",
chains: popularChainsWithMeta.map(mapV4ChainToV5Chain),
chains: favoriteChainsWithMetadata,
},
{
label: "Recent",
chains: recentChainsv4.map(mapV4ChainToV5Chain),
chains: recentlyUsedChainsWithMetadata,
},
{
label: "Popular",
chains: popularChainsWithMetadata,
},
];
}, [recentChainsv4, favChainsQuery.data, popularChainsWithMeta]);
}, [
recentlyUsedChainsWithMetadata,
favoriteChainsWithMetadata,
popularChainsWithMetadata,
]);
// ensures login status on pages that need it
const { isPending, isLoggedIn } = useLoggedInUser();
@@ -81,10 +122,10 @@ export const CustomConnectWallet = (props: {
if (isPending) {
return (
<>
<div className="w-[144px] h-[48px] bg-muted border border-border rounded-lg flex items-center justify-center">
<div className="flex h-[48px] w-[144px] items-center justify-center rounded-lg border border-border bg-muted">
<Spinner className="size-4" />
</div>
{/* need autoconnect here so that we actually connect */}
{/* need auto connect here so that we actually connect */}
<AutoConnect client={thirdwebClient} />
</>
);
@@ -100,7 +141,7 @@ export const CustomConnectWallet = (props: {
Sign In
</Link>
</Button>
{/* need autoconnect here so that we actually connect */}
{/* need auto connect here so that we actually connect */}
<AutoConnect client={thirdwebClient} />
</>
);
@@ -138,7 +179,7 @@ export const CustomConnectWallet = (props: {
detailsButton={{
className: props.detailsButtonClassName,
}}
chains={allChains}
chains={allChainsWithMetadata}
detailsModal={{
networkSelector: {
sections: chainSections,
@@ -186,7 +227,7 @@ function ConnectWalletWelcomeScreen(props: {
backgroundColor: props.theme === "dark" ? "#18132f" : "#c7b5f1",
backgroundImage: `url("/assets/connect-wallet/welcome-gradient-${props.theme}.png")`,
}}
className="flex flex-col p-6 h-full bg-cover bg-center bg-no-repeat"
className="flex h-full flex-col bg-center bg-cover bg-no-repeat p-6"
>
<div className="flex flex-grow flex-col justify-center">
<div>
@@ -207,7 +248,7 @@ function ConnectWalletWelcomeScreen(props: {
<div className="h-10" />
<h2
className="text-xl text-center font-semibold"
className="text-center font-semibold text-xl"
style={{
color: fontColor,
}}
@@ -218,7 +259,7 @@ function ConnectWalletWelcomeScreen(props: {
<div className="h-4" />
<p
className="text-center opacity-80 font-semibold"
className="text-center font-semibold opacity-80"
style={{
color: fontColor,
}}
@@ -229,7 +270,7 @@ function ConnectWalletWelcomeScreen(props: {
</div>
<TrackedAnchorLink
className="text-center font-semibold opacity-70 hover:opacity-100 hover:no-underline"
className="text-center font-semibold opacity-70 hover:no-underline hover:opacity-100"
target="_blank"
category="custom-connect-wallet"
label="new-to-wallets"
@@ -5,7 +5,6 @@ import {
useQueryClient,
} from "@tanstack/react-query";
import { THIRDWEB_ANALYTICS_API_HOST, THIRDWEB_API_HOST } from "constants/urls";
import type { ChainMetadata } from "thirdweb/chains";
import invariant from "tiny-invariant";
import { accountKeys, apiKeys, authorizedWallets } from "../cache-keys";
import { useMutationWithInvalidate } from "./query/useQueryWithNetwork";
@@ -947,28 +946,3 @@ export function useAuthorizedWallets() {
gcTime: 0,
});
}
/**
*
*/
async function fetchChainsFromApi() {
// always fetch from prod for chains for now
// TODO: re-visit this
const res = await fetch("https://api.thirdweb.com/v1/chains");
const json = await res.json();
if (json.error) {
throw new Error(json.error.message);
}
return json.data as ChainMetadata[];
}
export function useApiChains() {
return useQuery({
queryKey: ["all-chains"],
queryFn: () => {
return fetchChainsFromApi();
},
});
}
@@ -1,31 +0,0 @@
import { useFavouriteChainIds } from "app/(dashboard)/(chain)/components/client/star-button";
import { useSupportedChains } from "hooks/chains/configureChains";
import { useMemo } from "react";
import type { ChainMetadata } from "thirdweb/chains";
export function useFavoriteChains() {
const allChains = useSupportedChains();
const favChainsQuery = useFavouriteChainIds();
const data = useMemo(() => {
if (favChainsQuery.data) {
const _chains: ChainMetadata[] = [];
// biome-ignore lint/complexity/noForEach: FIXME
favChainsQuery.data.forEach((chainId) => {
const chain = allChains.find((c) => String(c.chainId) === chainId);
if (chain) {
_chains.push(chain);
}
});
return _chains;
}
return [] as ChainMetadata[];
}, [favChainsQuery.data, allChains]);
return {
isPending: favChainsQuery.isPending,
data,
};
}
@@ -8,7 +8,6 @@ import {
import type { BasicContract } from "contract-ui/types/types";
import { getAllMultichainRegistry } from "dashboard-extensions/common/read/getAllMultichainRegistry";
import { useAllChainsData } from "hooks/chains/allChains";
import { useSupportedChainsRecord } from "hooks/chains/configureChains";
import { useMemo } from "react";
import { sendAndConfirmTransaction } from "thirdweb";
import { remove } from "thirdweb/extensions/thirdweb";
@@ -42,7 +41,8 @@ export const useAllContractList = (
) => {
const multiChainQuery = useMultiChainRegContractList(walletAddress);
const configuredChainsRecord = useSupportedChainsRecord();
// TODO - instead of using ALL chains, fetch only the ones used here
const { idToChain } = useAllChainsData();
const contractList = useMemo(() => {
const data = multiChainQuery.data || [];
@@ -51,14 +51,12 @@ export const useAllContractList = (
// biome-ignore lint/complexity/noForEach: FIXME
data.forEach((net) => {
if (net.chainId in configuredChainsRecord) {
const chainRecord = configuredChainsRecord[net.chainId];
if (chainRecord.status !== "deprecated") {
if (chainRecord.testnet) {
testnets.push(net);
} else {
mainnets.push(net);
}
const chn = idToChain.get(net.chainId);
if (chn && chn.status !== "deprecated") {
if (chn.testnet) {
testnets.push(net);
} else {
mainnets.push(net);
}
}
});
@@ -71,7 +69,7 @@ export const useAllContractList = (
testnets.sort((a, b) => a.chainId - b.chainId);
return mainnets.concat(testnets);
}, [multiChainQuery.data, onlyMainnet, configuredChainsRecord]);
}, [multiChainQuery.data, onlyMainnet, idToChain]);
return {
...multiChainQuery,
@@ -85,12 +83,10 @@ type RemoveContractParams = {
};
export function useRemoveContractMutation() {
const { chainIdToChainRecord } = useAllChainsData();
const queryClient = useQueryClient();
const account = useActiveAccount();
return useMutation({
mutationFn: async (data: RemoveContractParams) => {
invariant(chainIdToChainRecord, "chains not initialzed yet");
invariant(data.chainId, "chainId not provided");
invariant(account, "No wallet connected");
const { contractAddress, chainId } = data;
@@ -129,7 +129,7 @@ export function FaucetButton({
// faucet is empty
if (isFaucetEmpty) {
return (
<Button variant="outline" disabled className="w-full !opacity-100 ">
<Button variant="outline" disabled className="!opacity-100 w-full ">
Faucet is empty right now
</Button>
);
@@ -138,7 +138,7 @@ export function FaucetButton({
// Can not claim
if (canClaimFaucetQuery.data && canClaimFaucetQuery.data.canClaim === false) {
return (
<Button variant="outline" className="w-full !opacity-100 " disabled>
<Button variant="outline" className="!opacity-100 w-full " disabled>
{canClaimFaucetQuery.data.type === "throttle" && (
<>
Your next claim is available{" "}
@@ -163,7 +163,7 @@ export function FaucetButton({
// eligible to claim and faucet has balance
return (
<div className="flex flex-col w-full text-center">
<div className="flex w-full flex-col text-center">
<Button
variant="primary"
className="w-full gap-2"
@@ -185,7 +185,7 @@ export function FaucetButton({
</Button>
{faucetWalletBalanceQuery.data && (
<p className="mt-3 text-xs text-muted-foreground">
<p className="mt-3 text-muted-foreground text-xs">
{Number(faucetWalletBalanceQuery.data.displayValue).toFixed(3)}{" "}
{faucetWalletBalanceQuery.data.symbol} left in the faucet
</p>
@@ -30,7 +30,7 @@ export function PayModalButton(props: { chainId: number; label: string }) {
</Button>
</DialogTrigger>
<DialogContent
className="md:max-w-[360px] p-0 border-none bg-transparent"
className="border-none bg-transparent p-0 md:max-w-[360px]"
dialogOverlayClassName="backdrop-blur-lg"
dialogCloseClassName="focus:ring-0"
>
@@ -44,7 +44,7 @@ export const AddChainToWallet: React.FC<AddChainToWalletProps> = (props) => {
>
<Button
disabled={disabled}
className="gap-2 w-full"
className="w-full gap-2"
variant="outline"
onClick={() => {
// Connct directly to this chain
@@ -83,7 +83,7 @@ export function ChainLiveStats(props: { rpc: string }) {
textToCopy={props.rpc}
copyIconPosition="right"
variant="ghost"
className="px-2 py-1 -translate-x-2 text-base"
className="-translate-x-2 px-2 py-1 text-base"
iconClassName="size-4"
/>
</div>
@@ -96,7 +96,7 @@ export function ChainLiveStats(props: { rpc: string }) {
) : stats.data ? (
<p className="fade-in-0 animate-in">{stats.data.latency}ms</p>
) : (
<div className="flex py-1 h-[28px] w-[70px]">
<div className="flex h-[28px] w-[70px] py-1">
<Skeleton className="h-full w-full" />
</div>
)}
@@ -109,7 +109,7 @@ export function ChainLiveStats(props: { rpc: string }) {
) : stats.data ? (
<p className="fade-in-0 animate-in">{stats.data.blockNumber}</p>
) : (
<div className="flex py-1 h-[28px] w-[140px]">
<div className="flex h-[28px] w-[140px] py-1">
<Skeleton className="h-full w-full" />
</div>
)}
@@ -120,10 +120,10 @@ export function ChainLiveStats(props: { rpc: string }) {
function PulseDot() {
return (
<ToolTipLabel label={"Live Data"}>
<ToolTipLabel label="Live Data">
<span className="relative flex size-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-sky-400 opacity-75" />
<span className="relative inline-flex rounded-full size-2 bg-primary" />
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-sky-400 opacity-75" />
<span className="relative inline-flex size-2 rounded-full bg-primary" />
</span>
</ToolTipLabel>
);
@@ -12,28 +12,28 @@ export function BuyFundsSection(props: { chain: ChainMetadata }) {
return (
<section>
<SectionTitle title="Buy Funds" />
<div className="border border-border px-4 py-10 rounded-lg flex justify-center bg-muted/30">
<div className="max-w-[520px] flex flex-col items-center ">
<div className="flex justify-center rounded-lg border border-border bg-muted/30 px-4 py-10">
<div className="flex max-w-[520px] flex-col items-center ">
<div className="flex items-center">
<ChainIcon
className="size-12 -mr-2 rounded-full border p-1"
className="-mr-2 size-12 rounded-full border p-1"
iconUrl={props.chain.icon?.url}
/>
<CreditCardIcon
bg="hsl(var(--background))"
className="size-12 -ml-2"
className="-ml-2 size-12"
/>
</div>
<div className="h-6" />
<h2 className="text-lg tracking-tight font-semibold text-center px-4">
<h2 className="px-4 text-center font-semibold text-lg tracking-tight">
Buy Funds on {sanitizedChainName} using thirdweb Pay
</h2>
<div className="h-2" />
<p className="text-muted-foreground text-sm max-w-[520px] text-center px-4">
<p className="max-w-[520px] px-4 text-center text-muted-foreground text-sm">
Instantly onramp, bridge & swap into {sanitizedChainName} with any
token.
</p>
@@ -49,9 +49,9 @@ export function BuyFundsSection(props: { chain: ChainMetadata }) {
<Link
href="https://portal.thirdweb.com/connect/pay/overview"
className="inline-flex gap-1.5 items-center text-sm text-muted-foreground hover:text-foreground"
className="inline-flex items-center gap-1.5 text-muted-foreground text-sm hover:text-foreground"
>
Learn more about thirdweb Pay{" "}
Learn more about thirdweb Pay
<ExternalLinkIcon className="size-3" />
</Link>
</div>
@@ -11,7 +11,7 @@ export function ChainOverviewSection(props: { chain: ChainMetadata }) {
return (
<section>
<SectionTitle title="Chain Overview" />
<div className="grid grid-cols-1 gap-6 lg:gap-8 md:grid-cols-2 lg:grid-cols-3 border rounded-lg p-4 md:p-6 bg-muted/50">
<div className="grid grid-cols-1 gap-6 rounded-lg border bg-muted/50 p-4 md:grid-cols-2 md:p-6 lg:grid-cols-3 lg:gap-8">
{/* Info */}
{chain.infoURL && (
<PrimaryInfoItem title="Info">
@@ -33,7 +33,7 @@ export function ChainOverviewSection(props: { chain: ChainMetadata }) {
textToShow={chain.chainId.toString()}
tooltip="Copy Chain ID"
variant="ghost"
className="-translate-x-2 py-1 px-2 text-base"
className="-translate-x-2 px-2 py-1 text-base"
iconClassName="size-4"
copyIconPosition="right"
/>
@@ -7,9 +7,9 @@ export function ClaimChainSection() {
return (
<section>
<SectionTitle title="Claim Chain" />
<div className="border p-4 rounded-lg bg-muted/50">
<div className="rounded-lg border bg-muted/50 p-4">
<p className="mb-1"> This chain page is unclaimed </p>
<p className="text-muted-foreground text-sm mb-5">
<p className="mb-5 text-muted-foreground text-sm">
Are you the owner of this ecosystem? Claim this page to add additional
information on this page to help your developers get started!
</p>
@@ -21,7 +21,7 @@ export function ClaimChainSection() {
target="_blank"
>
<span>Claim Chain</span>
<ExternalLinkIcon className="size-4 -mt-[3px]" />
<ExternalLinkIcon className="-mt-[3px] size-4" />
</Link>
</Button>
</div>
@@ -15,25 +15,25 @@ export async function FaucetSection(props: { chain: ChainMetadata }) {
return (
<section>
<SectionTitle title="Faucet" />
<div className="border border-border px-4 py-10 rounded-lg flex justify-center bg-muted/50">
<div className="max-w-[520px] w-full flex flex-col items-center ">
<div className="flex justify-center rounded-lg border border-border bg-muted/50 px-4 py-10">
<div className="flex w-full max-w-[520px] flex-col items-center ">
<div className="flex items-center">
<ChainIcon
className="size-12 -mr-2 rounded-full border p-1"
className="-mr-2 size-12 rounded-full border p-1"
iconUrl={props.chain.icon?.url}
/>
<GiftIcon bg="hsl(var(--background))" className="size-12 -ml-2" />
<GiftIcon bg="hsl(var(--background))" className="-ml-2 size-12" />
</div>
<div className="h-6" />
<h2 className="text-lg tracking-tight font-semibold text-center px-4">
<h2 className="px-4 text-center font-semibold text-lg tracking-tight">
{sanitizedChainName} Faucet
</h2>
<div className="h-2" />
<p className="text-muted-foreground text-sm max-w-[520px] text-center px-4">
<p className="max-w-[520px] px-4 text-center text-muted-foreground text-sm">
Get free {chain.nativeCurrency.symbol} fast and reliably.{" "}
{amountToGive} {chain.nativeCurrency.symbol}/day.
</p>
@@ -1,5 +1,5 @@
export function SectionTitle(props: { title: string }) {
return (
<h2 className="text-xl tracking-tight mb-2 font-semibold">{props.title}</h2>
<h2 className="mb-2 font-semibold text-xl tracking-tight">{props.title}</h2>
);
}
@@ -20,27 +20,26 @@ export function SupportedProductsSection(props: {
return (
<section>
<SectionTitle title="thirdweb Products" />
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3">
{enabledProducts.map((product) => {
return (
<div
key={product.id}
className="border rounded-lg p-4 flex gap-3 relative hover:bg-muted bg-muted/50 transition-colors duration-300 pr-8"
className="relative flex gap-3 rounded-lg border bg-muted/50 p-4 pr-8 transition-colors duration-300 hover:bg-muted"
>
<CircleCheckIcon className="size-5 text-success-text absolute top-4 right-4" />
<product.icon className="size-5 shrink-0 mt-0.5" />
<CircleCheckIcon className="absolute top-4 right-4 size-5 text-success-text" />
<product.icon className="mt-0.5 size-5 shrink-0" />
<div>
<h3 className="font-medium mb-1.5">
<h3 className="mb-1.5 font-medium">
<Link
href={product.link}
className="before:absolute before:inset-0"
target="_blank"
>
{" "}
{product.name}{" "}
{product.name}
</Link>
</h3>
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
{product.description}
</p>
</div>
@@ -1,4 +1,3 @@
import { AspectRatio } from "@/components/ui/aspect-ratio";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import Link from "next/link";
@@ -20,27 +19,29 @@ export function ChainHeader(props: ChainHeaderProps) {
// force the banner image to be 4:1 aspect ratio and full-width on mobile devices
<div className="flex flex-col">
{!props.headerImageUrl && <div className="h-8 md:hidden" />}
<AspectRatio
ratio={props.headerImageUrl ? 4 : 8}
className="border-b border-border max-sm:-mx-4"
<div
className={cn(
"max-sm:-mx-4 relative border-border border-b",
props.headerImageUrl ? "aspect-[4/1]" : "aspect-[8/1]",
)}
>
{props.headerImageUrl && (
// eslint-disable-next-line @next/next/no-img-element
<img
src={props.headerImageUrl}
alt=""
className="object-cover object-center h-full w-full"
className="h-full w-full object-cover object-center"
/>
)}
</AspectRatio>
</div>
{/* below header */}
<div className="relative flex flex-row justify-end items-end">
<div className="relative flex flex-row items-end justify-end">
{/* chain logo */}
<ChainIcon
iconUrl={props.logoUrl}
className={cn(
"p-2 lg:p-4 absolute top-0 left-0 size-20 lg:size-36 rounded-full -translate-y-[50%] overflow-hidden border border-border bg-muted",
"-translate-y-[50%] absolute top-0 left-0 size-20 overflow-hidden rounded-full border border-border bg-muted p-2 lg:size-36 lg:p-4",
props.headerImageUrl && "lg:left-4",
)}
/>
@@ -48,7 +49,7 @@ export function ChainHeader(props: ChainHeaderProps) {
{/* action group */}
<div className="pt-3 lg:pt-6">
{/* Desktop only */}
<div className="hidden lg:flex flex-row gap-2">
<div className="hidden flex-row gap-2 lg:flex">
<AddChainToWallet
chain={
// Do not include chain overrides for chain pages
@@ -12,18 +12,18 @@ export type ChainCTAProps = {
export function ChainCTA(props: ChainCTAProps) {
return (
<div
className="bg-cover bg-center bg-no-repeat rounded-xl border border-border overflow-hidden"
className="overflow-hidden rounded-xl border border-border bg-center bg-cover bg-no-repeat"
style={{
backgroundImage: `url(${props.backgroundImageUrl})`,
}}
>
<div className="grid items-center justify-center gap-6 p-8 md:grid-cols-[1fr_auto] md:gap-12 bg-gradient-to-r from-background to-background/50">
<div className="grid items-center justify-center gap-6 bg-gradient-to-r from-background to-background/50 p-8 md:grid-cols-[1fr_auto] md:gap-12">
<div className="space-y-4 text-center md:text-left">
<h2 className="text-2xl md:text-3xl font-bold tracking-tighter text-foreground md:max-w-[85%]">
<h2 className="font-bold text-2xl text-foreground tracking-tighter md:max-w-[85%] md:text-3xl">
{props.title}
</h2>
{props.description && (
<p className="max-w-[600px] text-muted-foreground leading-relaxed text-sm md:text-base">
<p className="max-w-[600px] text-muted-foreground text-sm leading-relaxed md:text-base">
{props.description}
</p>
)}
@@ -9,21 +9,21 @@ export function ExplorersSection(props: {
return (
<section>
<SectionTitle title="Explorers" />
<div className="grid grid-cols-1 gap-3 md:gap-4 md:grid-cols-2 lg:grid-cols-3">
<div className="grid grid-cols-1 gap-3 md:grid-cols-2 md:gap-4 lg:grid-cols-3">
{props.explorers.map((explorer) => {
return (
<div
key={explorer.url}
className="p-4 border rounded-xl relative hover:bg-muted bg-muted/50"
className="relative rounded-xl border bg-muted/50 p-4 hover:bg-muted"
>
<ExternalLinkIcon className="size-4 absolute top-4 right-4 text-muted-foreground" />
<h3 className="mb-1 text-base font-semibold capitalize">
<ExternalLinkIcon className="absolute top-4 right-4 size-4 text-muted-foreground" />
<h3 className="mb-1 font-semibold text-base capitalize">
{explorer.name}
</h3>
<Link
href={explorer.url}
target="_blank"
className="text-sm flex gap-1.5 items-center before:absolute before:inset-0 before:z-0 text-muted-foreground"
className="flex items-center gap-1.5 text-muted-foreground text-sm before:absolute before:inset-0 before:z-0"
>
{explorer.url.endsWith("/")
? explorer.url.slice(0, -1)
@@ -6,7 +6,7 @@ export function PrimaryInfoItem(props: {
return (
<div>
<div className="flex items-center gap-2">
<h3 className="text-base text-muted-foreground font-medium">
<h3 className="font-medium text-base text-muted-foreground">
{props.title}
</h3>
{props.titleIcon}
@@ -79,7 +79,7 @@ export default async function ChainPageLayout({
return (
<>
<div className="border-b border-border h-14 pl-7 flex">
<div className="flex h-14 border-border border-b pl-7">
<Breadcrumb className="my-auto">
<BreadcrumbList>
<BreadcrumbItem>
@@ -119,10 +119,10 @@ export default async function ChainPageLayout({
</BreadcrumbList>
</Breadcrumb>
</div>
<div className="container flex flex-row h-full">
<div className="container flex h-full flex-row">
<Sidebar links={sidebarLinks} />
<div className="flex flex-col w-full pb-10">
<div className="flex w-full flex-col pb-10">
{/* Icon + Background */}
<ChainHeader
headerImageUrl={chainMetadata?.headerImgUrl}
@@ -130,7 +130,7 @@ export default async function ChainPageLayout({
chain={chain}
/>
<div className="flex lg:hidden justify-end">
<div className="flex justify-end lg:hidden">
<MobileSidebar
links={sidebarLinks}
trigger={
@@ -142,7 +142,7 @@ export default async function ChainPageLayout({
<div className="mt-5 border-t pt-6 pb-2">
<Link
href="/chainlist"
className="px-2 text-sm flex justify-start gap-2 items-center text-muted-foreground hover:text-foreground"
className="flex items-center justify-start gap-2 px-2 text-muted-foreground text-sm hover:text-foreground"
>
<ArrowLeftIcon className="size-4" />
View all chains
@@ -157,14 +157,14 @@ export default async function ChainPageLayout({
<div className="flex flex-col gap-3 md:gap-2">
{/* Gas Sponsored badge - Mobile */}
{chainMetadata?.gasSponsored && (
<div className="md:hidden flex">
<div className="flex md:hidden">
<GasSponseredBadge />
</div>
)}
{/* Chain name */}
<div className="flex flex-row gap-1.5 items-center">
<h1 className="text-2xl font-semibold lg:text-3xl lg:font-semibold tracking-tight">
<div className="flex flex-row items-center gap-1.5">
<h1 className="font-semibold text-2xl tracking-tight lg:font-semibold lg:text-3xl">
{chain.name}
</h1>
@@ -185,13 +185,13 @@ export default async function ChainPageLayout({
{/* description */}
{chainMetadata?.about && (
<p className="text-muted-foreground text-sm lg:text-base mb-2">
<p className="mb-2 text-muted-foreground text-sm lg:text-base">
{chainMetadata.about}
</p>
)}
{/* mobile action row */}
<div className="sm:hidden w-full">
<div className="w-full sm:hidden">
<div className="grid grid-cols-2 gap-2">
<AddChainToWallet
chain={
@@ -219,7 +219,7 @@ export default async function ChainPageLayout({
function GasSponseredBadge() {
return (
<div className="flex items-center gap-2 rounded-full text-[hsl(334,81.12%,69.65%)] bg-[hsla(335,57%,51%,0.2)] px-2.5 py-1">
<div className="flex items-center gap-2 rounded-full bg-[hsla(335,57%,51%,0.2)] px-2.5 py-1 text-[hsl(334,81.12%,69.65%)]">
<TicketCheckIcon className="size-4" />
<span className="font-medium text-xs">Gas Sponsored</span>
</div>
@@ -34,8 +34,8 @@ export default async function Page(props: {
{/* Deprecated Alert */}
{isDeprecated && (
<section className="bg-destructive rounded-lg text-destructive-foreground border border-destructive-foreground/20">
<div className="container py-4 flex flex-row items-center gap-4">
<section className="rounded-lg border border-destructive-foreground/20 bg-destructive text-destructive-foreground">
<div className="container flex flex-row items-center gap-4 py-4">
<CircleAlertIcon className="size-6 flex-shrink-0" />
<h3 className="font-medium">
This chain has been marked as deprecated. Some or all services may
@@ -63,13 +63,13 @@ export default async function Page(props: {
{topContracts.length > 0 && (
<section>
<div className="mb-3 flex items-center justify-between">
<h2 className="text-xl tracking-tight font-semibold">
<h2 className="font-semibold text-xl tracking-tight">
Popular Contracts
</h2>
<Button asChild variant="outline">
<Link
href={`/${chain.slug}/popular`}
className="flex gap-2 items-center"
className="flex items-center gap-2"
>
See All
<ArrowRightIcon className="size-4" />
@@ -21,11 +21,11 @@ export default async function Page(props: {
return (
<section className="mt-2">
<h2 className="text-2xl tracking-tighter font-semibold mb-2">
<h2 className="mb-2 font-semibold text-2xl tracking-tighter">
Popular Contracts
</h2>
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
Explore contracts on Ethereum and sort them by your preferred metrics
</p>
@@ -41,7 +41,7 @@ export default async function Page(props: {
)}
{topContracts.length === 0 && (
<div className="text-muted-foreground text-lg flex border rounded-lg h-[200px] border-border items-center justify-center">
<div className="flex h-[200px] items-center justify-center rounded-lg border border-border text-lg text-muted-foreground">
<div className="flex items-center gap-2">
<CircleAlertIcon className="size-5 text-destructive-text" />
No contracts found
@@ -42,9 +42,9 @@ export default async function Page(props: {
});
return (
<main className="flex flex-col gap-y-10 container py-6">
<main className="container flex flex-col gap-y-10 py-6">
<header className="flex flex-col gap-y-2">
<p className="text-3xl font-bold">Transaction Details</p>
<p className="font-bold text-3xl">Transaction Details</p>
<div className="flex gap-x-2">
{shortenAddress(transaction.from)} called on{" "}
{shortenAddress(transaction.to || ZERO_ADDRESS)}
@@ -55,12 +55,12 @@ export default async function Page(props: {
<section className="flex flex-col gap-y-2">
<div className="grid grid-cols-2">
<div className="flex gap-x-2 items-center">Transaction hash</div>
<div className="flex items-center gap-x-2">Transaction hash</div>
<p>{transaction.hash}</p>
</div>
<div className="grid grid-cols-2">
<div className="flex gap-x-2 items-center">
<div className="flex items-center gap-x-2">
Status {/* and method */}
</div>
<div className="flex gap-x-2">
@@ -75,12 +75,12 @@ export default async function Page(props: {
</div>
<div className="grid grid-cols-2">
<div className="flex gap-x-2 items-center">Block</div>
<div className="flex items-center gap-x-2">Block</div>
<p>{Number(transaction.blockNumber)}</p>
</div>
<div className="grid grid-cols-2">
<div className="flex gap-x-2 items-center">Timestamp</div>
<div className="flex items-center gap-x-2">Timestamp</div>
<p>
{new Date(
Number.parseInt(block.timestamp.toString()) * 1000,
@@ -93,12 +93,12 @@ export default async function Page(props: {
<section className="flex flex-col gap-y-2">
<div className="grid grid-cols-2">
<div className="flex gap-x-2 items-center">From</div>
<div className="flex items-center gap-x-2">From</div>
<p>{transaction.from}</p>
</div>
<div className="grid grid-cols-2">
<div className="flex gap-x-2 items-center">
<div className="flex items-center gap-x-2">
Interacted with contract
</div>
<p>{transaction.to}</p>
@@ -109,27 +109,27 @@ export default async function Page(props: {
<section className="flex flex-col gap-y-2">
<div className="grid grid-cols-2">
<div className="flex gap-x-2 items-center">Value</div>
<div className="flex items-center gap-x-2">Value</div>
<p>{toEther(transaction.value)} ETH</p>
</div>
<div className="grid grid-cols-2">
<div className="flex gap-x-2 items-center">Transaction fee</div>
<div className="flex items-center gap-x-2">Transaction fee</div>
<p>{toEther((transaction.gasPrice || 0n) * receipt.gasUsed)} ETH</p>
</div>
<div className="grid grid-cols-2">
<div className="flex gap-x-2 items-center">Gas price</div>
<div className="flex items-center gap-x-2">Gas price</div>
<p>{toEther(transaction.gasPrice || 0n)} ETH</p>
</div>
<div className="grid grid-cols-2">
<div className="flex gap-x-2 items-center">Gas usage</div>
<div className="flex items-center gap-x-2">Gas usage</div>
<p>{receipt.gasUsed.toString()}</p>
</div>
<div className="grid grid-cols-2">
<div className="flex gap-x-2 items-center">Gas fees (Gwei)</div>
<div className="flex items-center gap-x-2">Gas fees (Gwei)</div>
<p>
Base: {toTokens(block.baseFeePerGas || 0n, 9)} | Max:{" "}
{toTokens(transaction.maxFeePerGas || 0n, 9)} | Max priority:{" "}
@@ -138,7 +138,7 @@ export default async function Page(props: {
</div>
<div className="grid grid-cols-2">
<div className="flex gap-x-2 items-center">Burnt fees</div>
<div className="flex items-center gap-x-2">Burnt fees</div>
<p>{toEther(block.baseFeePerGas || 0n * receipt.gasUsed)}</p>
</div>
</section>
@@ -147,7 +147,7 @@ export default async function Page(props: {
<section className="flex flex-col gap-y-2">
<div className="grid grid-cols-2">
<div className="flex gap-x-2 items-center">Other</div>
<div className="flex items-center gap-x-2">Other</div>
<p>
Txn type: {hexToNumber(transaction.typeHex || "0x0")} (
{transaction.type}) | Nonce: {transaction.nonce} | Position:{" "}
@@ -156,8 +156,8 @@ export default async function Page(props: {
</div>
<div className="grid grid-cols-2 items-start">
<div className="flex gap-x-2 items-center">Raw Input:</div>
<div className="bg-muted/50 p-4 rounded-lg break-words text-sm">
<div className="flex items-center gap-x-2">Raw Input:</div>
<div className="break-words rounded-lg bg-muted/50 p-4 text-sm">
{transaction.input}
</div>
</div>
@@ -74,8 +74,7 @@ export default async function ChainListLayout(props: {
const searchParams = { type: props.params.chain_type, ...props.searchParams };
return (
<>
{" "}
<div className="border-b border-border h-14 pl-7 flex">
<div className="flex h-14 border-border border-b pl-7">
<Breadcrumb className="my-auto">
<BreadcrumbList>
<BreadcrumbItem>
@@ -111,19 +110,19 @@ export default async function ChainListLayout(props: {
</BreadcrumbList>
</Breadcrumb>
</div>
<section className="container mx-auto py-10 px-4 h-full flex flex-col">
<section className="container mx-auto flex h-full flex-col px-4 py-10">
<header className="flex flex-col gap-4">
<div className="flex gap-4 flex-col lg:flex-row lg:justify-between lg:items-center">
<div className="flex gap-4 flex-row items-center justify-between lg:justify-start lg:flex-col">
<h1 className="font-semibold text-4xl lg:text-5xl tracking-tighter">
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
<div className="flex flex-row items-center justify-between gap-4 lg:flex-col lg:justify-start">
<h1 className="font-semibold text-4xl tracking-tighter lg:text-5xl">
List of{" "}
{props.params.chain_type.charAt(0).toUpperCase() +
props.params.chain_type.slice(1)}
</h1>
<AddYourChainButton className="lg:hidden" />
</div>
<div className="flex flex-row lg:flex-col gap-4 items-end">
<div className="flex flex-row gap-4 w-full">
<div className="flex flex-row items-end gap-4 lg:flex-col">
<div className="flex w-full flex-row gap-4">
<SearchInput />
<ChainListView activeView={activeView} />
<AddYourChainButton className="hidden lg:flex" />
@@ -131,7 +130,7 @@ export default async function ChainListLayout(props: {
<div className="flex flex-row gap-2">
<AllFilters hideChainType={true} />
<div className="hidden lg:flex flex-row gap-2">
<div className="hidden flex-row gap-2 lg:flex">
<ChainOptionsFilter />
<ChainServiceFilter />
</div>
@@ -30,7 +30,7 @@ export function AllFilters(props: { hideChainType?: boolean }) {
<PopoverTrigger asChild>
<Button
variant="outline"
className="w-auto h-10 gap-2 p-2 lg:px-4 lg:py-2 lg:h-10 lg:border-0"
className="h-10 w-auto gap-2 p-2 lg:h-10 lg:border-0 lg:px-4 lg:py-2"
>
<Filter strokeWidth={1} className="lg:size-4" />
<span className="hidden lg:inline">All Filters</span>
@@ -39,7 +39,7 @@ export function AllFilters(props: { hideChainType?: boolean }) {
<PopoverContent
align="end"
className="flex flex-col gap-4 mt-0.5 max-w-full"
className="mt-0.5 flex max-w-full flex-col gap-4"
>
<h3 className="font-bold">Filters</h3>
@@ -112,7 +112,7 @@ const FilterSection: React.FC<PropsWithChildren<FilterSectionProps>> = ({
}) => {
return (
<section className="flex flex-col">
<h4 className="mb-2 text-sm font-semibold text-muted-foreground">
<h4 className="mb-2 font-semibold text-muted-foreground text-sm">
{title}
</h4>
{children}
@@ -182,7 +182,7 @@ export const ChainTypeFilter: React.FC<ChainTypeFilterProps> = ({
<PopoverTrigger asChild>
<Button
variant="outline"
className={value === "all" ? undefined : "pr-11 border-primary"}
className={value === "all" ? undefined : "border-primary pr-11"}
>
{value === "mainnets"
? "Mainnets Only"
@@ -196,7 +196,7 @@ export const ChainTypeFilter: React.FC<ChainTypeFilterProps> = ({
<Button
variant="ghost"
size="icon"
className="absolute h-auto w-10 right-[1px] top-[1px] bottom-[1px]"
className="absolute top-[1px] right-[1px] bottom-[1px] h-auto w-10"
onClick={() => {
const url = makeUrl("all");
router.replace(url);
@@ -208,7 +208,7 @@ export const ChainTypeFilter: React.FC<ChainTypeFilterProps> = ({
</div>
<PopoverContent
align="end"
className="flex flex-col gap-4 mt-0.5 max-w-full"
className="mt-0.5 flex max-w-full flex-col gap-4"
>
{section}
</PopoverContent>
@@ -254,7 +254,7 @@ export const ChainOptionsFilter: React.FC<ChainOptionsFilterProps> = ({
/>
<Label
htmlFor="deprecated"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
className="font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Include Deprecated
</Label>
@@ -275,7 +275,7 @@ export const ChainOptionsFilter: React.FC<ChainOptionsFilterProps> = ({
<PopoverTrigger asChild>
<Button
variant="outline"
className={hasFilters ? "pr-11 border-primary" : undefined}
className={hasFilters ? "border-primary pr-11" : undefined}
>
Chain Options
{!hasFilters && <ChevronDownIcon className="ml-2 size-4" />}
@@ -285,7 +285,7 @@ export const ChainOptionsFilter: React.FC<ChainOptionsFilterProps> = ({
<Button
variant="ghost"
size="icon"
className="absolute h-auto w-10 right-[1px] top-[1px] bottom-[1px]"
className="absolute top-[1px] right-[1px] bottom-[1px] h-auto w-10"
onClick={() => {
mutableSearchParams.delete("includeDeprecated");
mutableSearchParams.delete("page");
@@ -301,7 +301,7 @@ export const ChainOptionsFilter: React.FC<ChainOptionsFilterProps> = ({
</div>
<PopoverContent
align="end"
className="flex flex-col gap-4 mt-0.5 max-w-full"
className="mt-0.5 flex max-w-full flex-col gap-4"
>
{section}
</PopoverContent>
@@ -336,7 +336,7 @@ export const ChainServiceFilter: React.FC<ChainServiceFilterProps> = ({
const section = (
<FilterSection title="Services">
{products.map((product) => (
<div className="flex items-center gap-2 group" key={product.id}>
<div className="group flex items-center gap-2" key={product.id}>
<Checkbox
id={product.id}
checked={isServiceActive(mutableSearchParams, product.id)}
@@ -351,7 +351,7 @@ export const ChainServiceFilter: React.FC<ChainServiceFilterProps> = ({
/>
<Label
htmlFor={product.id}
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
className="font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{product.name}
</Label>
@@ -366,7 +366,7 @@ export const ChainServiceFilter: React.FC<ChainServiceFilterProps> = ({
router.push(url);
}}
size="sm"
className="ml-auto rounded-full lg:opacity-0 group-hover:opacity-100"
className="ml-auto rounded-full group-hover:opacity-100 lg:opacity-0"
variant="ghost"
>
Only
@@ -399,7 +399,7 @@ export const ChainServiceFilter: React.FC<ChainServiceFilterProps> = ({
<PopoverTrigger asChild>
<Button
variant="outline"
className={hasActiveFilter ? "pr-11 border-primary" : undefined}
className={hasActiveFilter ? "border-primary pr-11" : undefined}
>
{buttonTitle}
{!hasActiveFilter && <ChevronDownIcon className="ml-2 size-4" />}
@@ -409,7 +409,7 @@ export const ChainServiceFilter: React.FC<ChainServiceFilterProps> = ({
<Button
variant="ghost"
size="icon"
className="absolute h-auto w-10 right-[1px] top-[1px] bottom-[1px]"
className="absolute top-[1px] right-[1px] bottom-[1px] h-auto w-10"
onClick={() => {
mutableSearchParams.delete("service");
mutableSearchParams.delete("page");
@@ -425,7 +425,7 @@ export const ChainServiceFilter: React.FC<ChainServiceFilterProps> = ({
</div>
<PopoverContent
align="end"
className="flex flex-col gap-4 mt-0.5 max-w-full"
className="mt-0.5 flex max-w-full flex-col gap-4"
>
{section}
</PopoverContent>
@@ -45,10 +45,10 @@ export const SearchInput: React.FC = () => {
return (
<div className="relative w-full">
<SearchIcon className="absolute size-4 top-[50%] -translate-y-1/2 left-3 text-muted-foreground" />
<SearchIcon className="-translate-y-1/2 absolute top-[50%] left-3 size-4 text-muted-foreground" />
<Input
placeholder="Search by name or chain ID"
className={"pl-9 h-10 py-2 rounded-lg"}
className="h-10 rounded-lg py-2 pl-9"
defaultValue={searchParams?.get("query") || ""}
onChange={(e) => handleSearch(e.target.value)}
ref={inputRef}
@@ -56,7 +56,7 @@ export const SearchInput: React.FC = () => {
{searchParams?.has("query") && (
<Button
size="icon"
className="absolute top-[50%] -translate-y-1/2 right-2 text-muted-foreground"
className="-translate-y-1/2 absolute top-[50%] right-2 text-muted-foreground"
variant="ghost"
onClick={() => {
const params = new URLSearchParams(searchParams ?? undefined);
@@ -159,7 +159,7 @@ export async function ChainsData(props: {
<main>
{/* empty state */}
{paginatedChains.length === 0 ? (
<div className="border p-8 h-[300px] lg:h-[500px] flex justify-center items-center rounded-lg">
<div className="flex h-[300px] items-center justify-center rounded-lg border p-8 lg:h-[500px]">
<p className="text-2xl">No Results found</p>
</div>
) : props.activeView === "table" ? (
@@ -191,7 +191,7 @@ export async function ChainsData(props: {
<div className="relative h-6 w-6">
<StarButton
chainId={chain.chainId}
className="absolute z-10 top-0 h-full w-full left-0"
className="absolute top-0 left-0 z-10 h-full w-full"
/>
</div>
}
@@ -202,7 +202,7 @@ export async function ChainsData(props: {
</Table>
</TableContainer>
) : (
<ul className="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
<ul className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
{paginatedChains.map((chain) => (
<li key={chain.chainId} className="h-full">
<ChainListCard
@@ -219,7 +219,7 @@ export async function ChainsData(props: {
<div className="relative h-6 w-6">
<StarButton
chainId={chain.chainId}
className="absolute z-10 top-0 h-full w-full left-0"
className="absolute top-0 left-0 z-10 h-full w-full"
/>
</div>
}
@@ -235,7 +235,7 @@ export async function ChainsData(props: {
<ChainlistPagination totalPages={totalPages} activePage={activePage} />
)}
<div className="h-4" />
<p className="text-sm text-center text-muted-foreground text-balance">
<p className="text-balance text-center text-muted-foreground text-sm">
Showing{" "}
<span className="text-accent-foreground">{paginatedChains.length}</span>{" "}
out of{" "}
@@ -31,12 +31,12 @@ export async function ChainListCard({
return (
<div className="relative h-full">
<Card className="w-full h-full bg-muted/50 hover:bg-muted">
<Card className="h-full w-full bg-muted/50 hover:bg-muted">
<CardHeader className="flex flex-row items-center justify-between p-4">
<div className="flex flex-row items-center gap-2">
<ChainIcon iconUrl={iconUrl} className="size-6" />
<Link
className="static group before:content-[''] before:absolute before:top-0 before:bottom-0 before:left-0 before:right-0 before:z-0"
className="group static before:absolute before:top-0 before:right-0 before:bottom-0 before:left-0 before:z-0 before:content-['']"
href={`/${chainSlug}`}
>
<CardTitle className="text-xl">{chainName}</CardTitle>
@@ -49,9 +49,9 @@ export async function ChainListCard({
<CardContent className="px-4 pt-0 pb-4">
{/* table of `chain id` `native token` `managed support`, header row on left value row on right */}
<table className="w-full">
<tbody className="[&_td>*]:min-h-[25px] text-sm">
<tbody className="text-sm [&_td>*]:min-h-[25px]">
<tr>
<th className="font-normal text-left text-muted-foreground">
<th className="text-left font-normal text-muted-foreground">
Chain ID
</th>
<td className="text-right">
@@ -59,14 +59,14 @@ export async function ChainListCard({
textToCopy={chainId.toString()}
textToShow={chainId.toString()}
tooltip="Copy Chain ID"
className="inline-flex z-10 relative text-base translate-x-2 py-0.5"
className="relative z-10 inline-flex translate-x-2 py-0.5 text-base"
variant="ghost"
copyIconPosition="left"
/>
</td>
</tr>
<tr>
<th className="font-normal text-left text-muted-foreground">
<th className="text-left font-normal text-muted-foreground">
Native Token
</th>
<td className="text-right">
@@ -76,7 +76,7 @@ export async function ChainListCard({
</td>
</tr>
<tr>
<th className="font-normal text-left text-muted-foreground">
<th className="text-left font-normal text-muted-foreground">
Available Services
</th>
@@ -90,17 +90,17 @@ export async function ChainListCard({
</table>
{(isDeprecated || chainMetadata?.gasSponsored) && (
<div className="flex gap-5 pt-4 mt-5 border-t">
<div className="mt-5 flex gap-5 border-t pt-4">
{!isDeprecated && chainMetadata?.gasSponsored && (
<div className="gap-1.5 flex items-center">
<TicketCheckIcon className="text-foreground size-5" />
<div className="flex items-center gap-1.5">
<TicketCheckIcon className="size-5 text-foreground" />
<p className="text-sm">Gas Sponsored</p>
</div>
)}
{isDeprecated && (
<div className="gap-1.5 flex items-center">
<CircleAlertIcon className="text-destructive-text size-5" />
<div className="flex items-center gap-1.5">
<CircleAlertIcon className="size-5 text-destructive-text" />
<p className="text-sm">Deprecated</p>
</div>
)}
@@ -41,25 +41,25 @@ export async function ChainListRow({
<TableCell>{favoriteButton}</TableCell>
{/* Name */}
<TableCell>
<div className="flex flex-row items-center gap-4 w-[370px]">
<div className="flex w-[370px] flex-row items-center gap-4">
<div className="flex items-center gap-2">
<ChainIcon iconUrl={iconUrl} className="size-6" />
<Link
href={`/${chainSlug}`}
className="static group before:content-[''] before:absolute before:top-0 before:bottom-0 before:left-0 before:right-0 before:z-0"
className="group static before:absolute before:top-0 before:right-0 before:bottom-0 before:left-0 before:z-0 before:content-['']"
>
{chainName}
</Link>
{!isDeprecated && chainMetadata?.gasSponsored && (
<ToolTipLabel label="Gas Sponsored">
<TicketCheckIcon className="text-link-foreground size-5 z-10 " />
<TicketCheckIcon className="z-10 size-5 text-link-foreground " />
</ToolTipLabel>
)}
{isDeprecated && (
<ToolTipLabel label="Deprecated">
<CircleAlertIcon className="text-destructive-text size-5 z-10 " />
<CircleAlertIcon className="z-10 size-5 text-destructive-text " />
</ToolTipLabel>
)}
</div>
@@ -71,7 +71,7 @@ export async function ChainListRow({
textToCopy={chainId.toString()}
textToShow={chainId.toString()}
tooltip="Copy Chain ID"
className="z-10 relative text-base"
className="relative z-10 text-base"
variant="ghost"
copyIconPosition="right"
/>
@@ -80,8 +80,8 @@ export async function ChainListRow({
<TableCell>{currencySymbol}</TableCell>
<TableCell>
<div className="flex flex-row gap-14 items-center w-[520px] ">
<div className="flex items-center gap-7 z-10">
<div className="flex w-[520px] flex-row items-center gap-14 ">
<div className="z-10 flex items-center gap-7">
{products.map((p) => {
return (
<ProductIcon
@@ -134,9 +134,9 @@ function ProductIcon(props: {
label={props.label}
leftIcon={
props.isEnabled ? (
<CheckIcon className="text-success-text size-4" />
<CheckIcon className="size-4 text-success-text" />
) : (
<XIcon className="text-destructive-text size-4" />
<XIcon className="size-4 text-destructive-text" />
)
}
>
@@ -35,17 +35,17 @@ export default function ChainListPage(props: { searchParams: SearchParams }) {
: "grid";
return (
<section className="container mx-auto py-10 px-4 h-full flex flex-col">
<section className="container mx-auto flex h-full flex-col px-4 py-10">
<header className="flex flex-col gap-4">
<div className="flex gap-4 flex-col lg:flex-row lg:justify-between lg:items-center">
<div className="flex gap-4 flex-row items-center justify-between lg:justify-start lg:flex-col">
<h1 className="font-semibold text-4xl lg:text-5xl tracking-tighter">
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
<div className="flex flex-row items-center justify-between gap-4 lg:flex-col lg:justify-start">
<h1 className="font-semibold text-4xl tracking-tighter lg:text-5xl">
Chainlist
</h1>
<AddYourChainButton className="lg:hidden" />
</div>
<div className="flex flex-row lg:flex-col gap-4 items-end">
<div className="flex flex-row gap-4 w-full">
<div className="flex flex-row items-end gap-4 lg:flex-col">
<div className="flex w-full flex-row gap-4">
<SearchInput />
<ChainListView activeView={activeView} />
<AddYourChainButton className="hidden lg:flex" />
@@ -53,7 +53,7 @@ export default function ChainListPage(props: { searchParams: SearchParams }) {
<div className="flex flex-row gap-2">
<AllFilters />
<div className="hidden lg:flex flex-row gap-2">
<div className="hidden flex-row gap-2 lg:flex">
<ChainTypeFilter />
<ChainOptionsFilter />
<ChainServiceFilter />
@@ -42,7 +42,7 @@ async function removeChainFromFavorites(chainId: number) {
return result?.data?.favorite;
}
export function useFavouriteChainIds() {
export function useFavoriteChainIds() {
const loggedInUser = useLoggedInUser();
return useQuery({
queryKey: ["favoriteChains", loggedInUser.user?.address],
@@ -59,7 +59,7 @@ export function StarButton(props: {
}) {
const loggedInUser = useLoggedInUser();
const queryClient = useQueryClient();
const favChainsQuery = useFavouriteChainIds();
const favChainsQuery = useFavoriteChainIds();
const mutation = useMutation({
mutationFn: (preferred: boolean) => {
@@ -17,7 +17,7 @@ export default async function DirectDeployPage(props: DirectDeployPageProps) {
uri: parsedUri.startsWith("ipfs://") ? parsedUri : `ipfs://${parsedUri}`,
});
return (
<div className="container py-8 flex flex-col gap-4">
<div className="container flex flex-col gap-4 py-8">
<DeployContractInfo
name={metadata.name}
displayName={metadata.displayName}
@@ -5,6 +5,7 @@ import { ContractPublishForm } from "components/contract-components/contract-pub
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { fetchDeployMetadata } from "thirdweb/contract";
import { getPublishedContractsWithPublisherMapping } from "../../../published-contract/[publisher]/[contract_id]/utils/getPublishedContractsWithPublisherMapping";
type DirectDeployPageProps = {
params: {
@@ -20,11 +21,13 @@ export default async function PublishContractPage(
? decodedPublishUri
: `ipfs://${decodedPublishUri}`;
const publishMetadata = await fetchDeployMetadata({
const publishMetadataFromUri = await fetchDeployMetadata({
uri: publishUri,
client: getThirdwebClient(),
});
let publishMetadata = publishMetadataFromUri;
const pathname = `/contracts/publish/${props.params.publish_uri}`;
const address = getActiveAccountCookie();
@@ -32,13 +35,36 @@ export default async function PublishContractPage(
redirect(`/login?next=${encodeURIComponent(pathname)}`);
}
// Deploying the next version of a contract scenario:
// check if this is a pre-deployed metadata ( doesn't have a version )
// If that's the case:
// - get the publish metadata with name+publisher address
// - merge the two objects with publishMetadataFromUri taking higher precedence
if (!publishMetadataFromUri.version) {
const publishedContractVersions =
await getPublishedContractsWithPublisherMapping({
publisher: address,
contract_id: publishMetadataFromUri.name,
});
const publishedContract = publishedContractVersions[0];
if (publishedContract) {
publishMetadata = {
...publishedContract,
...publishMetadataFromUri,
version: publishedContract.version,
};
}
}
const token = getJWTCookie(address);
if (!token) {
redirect(`/login?next=${encodeURIComponent(pathname)}`);
}
return (
<div className="container py-8 flex flex-col gap-8">
<div className="container flex flex-col gap-8 py-8">
<ChakraProviderSetup>
<ContractPublishForm
jwt={token}
@@ -33,7 +33,7 @@ export async function EcosystemLandingPage(props: {
// otherwise we fall through to the page
return (
<main className="container flex flex-col max-w-2xl gap-8 pb-6 mx-auto">
<main className="container mx-auto flex max-w-2xl flex-col gap-8 pb-6">
<Image
src={headerImage}
alt="Ecosystems"
@@ -41,7 +41,7 @@ export async function EcosystemLandingPage(props: {
className="w-full"
/>
<div className="flex flex-col gap-2 text-left">
<h2 className="text-2xl font-bold sm:text-3xl text-foreground text-balance">
<h2 className="text-balance font-bold text-2xl text-foreground sm:text-3xl">
One wallet, a whole ecosystem of apps and games
</h2>
<p className="text-muted-foreground">
@@ -76,20 +76,20 @@ export async function EcosystemLandingPage(props: {
</Link>
</Button>
</div>
<div className="w-full h-0 my-2 border-t" />
<div className="my-2 h-0 w-full border-t" />
<Link
href="https://portal.thirdweb.com/connect/ecosystems/overview"
className="mb-6 group"
className="group mb-6"
>
<Card className="flex flex-col gap-3 p-6 md:p-8 md:px-10">
<div className="flex items-center gap-2">
<BookMarkedIcon className="size-5 text-muted-foreground" />
<h4 className="text-lg font-bold">Learn more</h4>
<h4 className="font-bold text-lg">Learn more</h4>
</div>
<p className="text-muted-foreground text-md">
<p className="text-md text-muted-foreground">
Learn how to create and manage Ecosystem Wallets in our docs.
</p>
<div className="flex flex-row items-center gap-2 mt-4 transition-all group-hover:gap-3 group-hover:text-primary/80 text-primary text-md">
<div className="mt-4 flex flex-row items-center gap-2 text-md text-primary transition-all group-hover:gap-3 group-hover:text-primary/80">
View Docs <MoveRightIcon className="size-4" />
</div>
</Card>
@@ -1,4 +1,5 @@
"use client";
import { AuthOptionsSection } from "./components/server/auth-options-section";
import { EcosystemPartnersSection } from "./components/server/ecosystem-partners-section";
import { IntegrationPermissionsSection } from "./components/server/integration-permissions-section";
import { useEcosystem } from "./hooks/use-ecosystem";
@@ -11,6 +12,7 @@ export function EcosystemPermissionsPage({
return (
<div className="flex flex-col gap-12">
<IntegrationPermissionsSection ecosystem={ecosystem} />
<AuthOptionsSection ecosystem={ecosystem} />
{ecosystem?.permission === "PARTNER_WHITELIST" && (
<EcosystemPartnersSection ecosystem={ecosystem} />
)}
@@ -31,7 +31,7 @@ export async function EcosystemLayoutSlug({
}
return (
<div className="flex flex-col w-full gap-10">
<div className="flex w-full flex-col gap-10">
<EcosystemHeader
ecosystem={ecosystem}
ecosystemLayoutPath={ecosystemLayoutPath}
@@ -0,0 +1,41 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { PlusIcon } from "lucide-react";
import { useState } from "react";
import type { Ecosystem } from "../../../../types";
import { AddPartnerForm } from "./add-partner-form.client";
export function AddPartnerDialogButton(props: {
ecosystem: Ecosystem;
}) {
const [open, setOpen] = useState(false);
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button className="gap-2 max-sm:w-full">
<PlusIcon className="size-4" />
Add Partner
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader className="mb-2">
<DialogTitle className="font-semibold text-2xl tracking-tight">
Add Partner
</DialogTitle>
</DialogHeader>
<AddPartnerForm
ecosystem={props.ecosystem}
onPartnerAdded={() => setOpen(false)}
/>
</DialogContent>
</Dialog>
);
}
@@ -1,4 +1,5 @@
"use client";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Button } from "@/components/ui/button";
import {
Form,
@@ -6,17 +7,10 @@ import {
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
@@ -26,14 +20,17 @@ import type { Ecosystem } from "../../../../types";
import { partnerFormSchema } from "../../constants";
import { useAddPartner } from "../../hooks/use-add-partner";
export function AddPartnerForm({ ecosystem }: { ecosystem: Ecosystem }) {
export function AddPartnerForm({
ecosystem,
onPartnerAdded,
}: { ecosystem: Ecosystem; onPartnerAdded: () => void }) {
const form = useForm<z.input<typeof partnerFormSchema>>({
resolver: zodResolver(partnerFormSchema),
});
const { mutateAsync: addPartner, isPending } = useAddPartner({
onSuccess: () => {
form.reset();
onPartnerAdded();
},
onError: (error) => {
const message =
@@ -57,18 +54,18 @@ export function AddPartnerForm({ ecosystem }: { ecosystem: Ecosystem }) {
allowlistedBundleIds: values.bundleIds
.split(/,| /)
.filter((d) => d.length > 0),
permissions: [values.permissions],
});
})}
className="flex flex-col gap-2 lg:flex-row"
className="flex flex-col gap-6"
>
<div className="grid gap-2 lg:grid-cols-12 grow">
<div className="flex flex-col gap-4">
<FormField
control={form.control}
name="name"
defaultValue="" // Note: you *must* provide a default value here or the field won't reset
render={({ field }) => (
<FormItem className="col-span-4 lg:col-span-3">
<FormItem>
<FormLabel> App Name </FormLabel>
<FormControl>
<Input
placeholder="App name"
@@ -80,8 +77,8 @@ export function AddPartnerForm({ ecosystem }: { ecosystem: Ecosystem }) {
className={cn(
"text-xs transition-all",
form.formState.errors.name?.message
? "text-destructive block opacity-100 translate-y-0"
: "opacity-0 lg:-translate-y-4 hidden",
? "block translate-y-0 text-destructive opacity-100"
: "lg:-translate-y-4 hidden opacity-0",
)}
>
{form.formState.errors.name?.message}
@@ -94,22 +91,16 @@ export function AddPartnerForm({ ecosystem }: { ecosystem: Ecosystem }) {
name="domains"
defaultValue="" // Note: you *must* provide a default value here or the field won't reset
render={({ field }) => (
<FormItem className="col-span-4 lg:col-span-4">
<FormItem>
<FormLabel> Domains </FormLabel>
<FormControl>
<>
<Input placeholder="Domains" className="peer" {...field} />
<FormDescription
className={cn(
"hidden text-xs transition-all lg:block lg:-translate-y-4 lg:opacity-0 peer-focus-visible:opacity-100 peer-focus-visible:translate-y-0",
form.formState.errors.domains?.message &&
"text-destructive lg:translate-y-0 lg:opacity-100 block", // If there are errors show them rather than the tip
)}
>
{form.formState.errors.domains?.message ??
"Space or comma-separated list of regex domains (e.g. *.example.com)"}
</FormDescription>
</>
<Input placeholder="Domains" className="peer" {...field} />
</FormControl>
<FormDescription>
Space or comma-separated list of regex domains (e.g.
*.example.com)
</FormDescription>
</FormItem>
)}
/>
@@ -118,75 +109,24 @@ export function AddPartnerForm({ ecosystem }: { ecosystem: Ecosystem }) {
name="bundleIds"
defaultValue="" // Note: you *must* provide a default value here or the field won't reset
render={({ field }) => (
<FormItem className="col-span-4 lg:col-span-3">
<FormItem>
<FormLabel> Bundle ID </FormLabel>
<FormControl>
<>
<Input
placeholder="Bundle ID"
className="peer"
{...field}
/>
<FormDescription
className={cn(
"hidden text-xs transition-all lg:block lg:-translate-y-4 lg:opacity-0 peer-focus-visible:opacity-100 peer-focus-visible:translate-y-0",
form.formState.errors.bundleIds?.message &&
"text-destructive translate-y-0 opacity-100 block",
)}
>
{form.formState.errors.bundleIds?.message ??
"Space or comma-separated list of bundle IDs"}
</FormDescription>
</>
<Input placeholder="Bundle ID" className="peer" {...field} />
</FormControl>
<FormDescription>
Space or comma-separated list of bundle IDs
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="permissions"
defaultValue="PROMPT_USER_V1" // Note: you *must* provide a default value here or the field won't reset
render={({ field }) => (
<FormItem className="col-span-4 lg:col-span-2">
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<ToolTipLabel label="Should wallet actions prompt the user for approval?">
<FormControl>
<SelectTrigger className="w-full">
<SelectValue placeholder="Wallet prompts" />
</SelectTrigger>
</FormControl>
</ToolTipLabel>
<SelectContent>
<SelectItem value="FULL_CONTROL_V1">
Never prompt
</SelectItem>
<SelectItem value="PROMPT_USER_V1">Prompt user</SelectItem>
</SelectContent>
<FormDescription
className={cn(
"hidden text-xs transition-all lg:block lg:-translate-y-4 lg:opacity-0 peer-focus-visible:opacity-100 peer-focus-visible:translate-y-0",
form.formState.errors.permissions?.message &&
"text-destructive lg:translate-y-0 lg:opacity-100 block", // If there are errors show them rather than the tip
)}
>
{form.formState.errors.permissions?.message ??
"Wallet signing"}
</FormDescription>
</Select>
</FormItem>
)}
/>
</div>
<Button
disabled={isPending}
type="submit"
variant="outline"
className="w-full lg:w-auto"
>
<Button disabled={isPending} type="submit" className="w-full gap-2">
{isPending && <Spinner className="size-4" />}
Add
</Button>
</form>
@@ -0,0 +1,99 @@
"use client";
import { ConfirmationDialog } from "@/components/ui/ConfirmationDialog";
import { Checkbox, CheckboxWithLabel } from "@/components/ui/checkbox";
import { Skeleton } from "@/components/ui/skeleton";
import { cn } from "@/lib/utils";
import { useState } from "react";
import { toast } from "sonner";
import invariant from "tiny-invariant";
import { type Ecosystem, authOptions } from "../../../../types";
import { useUpdateEcosystem } from "../../hooks/use-update-ecosystem";
export function AuthOptionsForm({ ecosystem }: { ecosystem: Ecosystem }) {
const [messageToConfirm, setMessageToConfirm] = useState<
| {
title: string;
description: string;
authOptions: typeof ecosystem.authOptions;
}
| undefined
>();
const {
mutateAsync: updateEcosystem,
variables,
isPending,
} = useUpdateEcosystem({
onError: (error) => {
const message =
error instanceof Error ? error.message : "Failed to create ecosystem";
toast.error(message);
},
});
return (
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-5 md:gap-2">
{authOptions.map((option) => (
<CheckboxWithLabel
key={option}
className={cn(
isPending &&
variables?.authOptions?.includes(option) &&
"animate-pulse",
"hover:cursor-pointer hover:text-foreground",
)}
>
<Checkbox
checked={ecosystem.authOptions.includes(option)}
onClick={() => {
if (ecosystem.authOptions.includes(option)) {
setMessageToConfirm({
title: `Are you sure you want to remove ${option.slice(0, 1).toUpperCase() + option.slice(1)} as an authentication option for this ecosystem?`,
description:
"Users will no longer be able to log into your ecosystem using this option. Any users that previously used this option will be unable to log in.",
authOptions: ecosystem.authOptions.filter(
(o) => o !== option,
),
});
} else {
setMessageToConfirm({
title: `Are you sure you want to add ${option.slice(0, 1).toUpperCase() + option.slice(1)} as an authentication option for this ecosystem?`,
description:
"Users will be able to log into your ecosystem using this option. If you later remove this option users that used it will no longer be able to log in.",
authOptions: [...ecosystem.authOptions, option],
});
}
}}
/>
{option.slice(0, 1).toUpperCase() + option.slice(1)}
</CheckboxWithLabel>
))}
<ConfirmationDialog
open={!!messageToConfirm}
onOpenChange={(open) => {
if (!open) {
setMessageToConfirm(undefined);
}
}}
title={messageToConfirm?.title}
description={messageToConfirm?.description}
onSubmit={() => {
invariant(messageToConfirm, "Must have message for modal to be open");
updateEcosystem({
ecosystem,
authOptions: messageToConfirm.authOptions,
});
}}
/>
</div>
);
}
export function AuthOptionsFormSkeleton() {
return (
<div className="flex flex-col gap-2 py-2 md:flex-row md:gap-4">
{authOptions.map((option) => (
<Skeleton key={option} className="h-14 w-full md:w-32" />
))}
</div>
);
}
@@ -73,10 +73,10 @@ function EcosystemSelect(props: {
<DropdownMenuTrigger asChild>
<Button
variant="outline"
className="relative flex justify-start w-full pl-3 pr-8 truncate md:w-48"
className="relative flex w-full justify-start truncate pr-8 pl-3 md:w-48"
>
<div className="truncate">{props.ecosystem?.name}</div>
<ChevronsUpDown className="absolute w-4 h-4 text-muted-foreground right-2" />
<ChevronsUpDown className="absolute right-2 h-4 w-4 text-muted-foreground" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-full md:w-48">
@@ -85,10 +85,10 @@ function EcosystemSelect(props: {
<DropdownMenuItem key={ecosystem.id} asChild>
<Link
href={`${props.ecosystemLayoutPath}/${ecosystem.slug}`}
className="relative flex items-center pl-8 pr-3 cursor-pointer"
className="relative flex cursor-pointer items-center pr-3 pl-8"
>
{ecosystem.slug === props.ecosystem.slug && (
<CheckIcon className="absolute w-4 h-4 text-foreground left-2" />
<CheckIcon className="absolute left-2 h-4 w-4 text-foreground" />
)}
<div className="truncate">{ecosystem.name}</div>
</Link>
@@ -97,8 +97,8 @@ function EcosystemSelect(props: {
</DropdownMenuGroup>
<DropdownMenuSeparator />
<Link href={`${props.ecosystemLayoutPath}/create`} className="">
<DropdownMenuItem className="relative flex items-center pl-8 pr-3 cursor-pointer">
<PlusCircleIcon className="absolute w-4 h-4 left-2" />
<DropdownMenuItem className="relative flex cursor-pointer items-center pr-3 pl-8">
<PlusCircleIcon className="absolute left-2 h-4 w-4" />
<div className="truncate">New Ecosystem</div>
</DropdownMenuItem>
</Link>
@@ -131,13 +131,13 @@ export function EcosystemHeader(props: {
<div className="flex flex-col gap-8">
<EcosystemAlertBanner ecosystem={ecosystem} />
<header className="flex flex-col gap-12">
<div className="flex flex-col justify-between gap-4 md:flex-row md:grid-cols-4">
<div className="flex flex-col justify-between gap-4 md:grid-cols-4 md:flex-row">
<div className="flex items-center gap-4">
{!ecosystem.imageUrl ? (
<Skeleton className="size-24" />
) : (
ecosystem.imageUrl && (
<div className="relative overflow-hidden rounded-md size-24">
<div className="relative size-24 overflow-hidden rounded-md">
<Image
src={resolveScheme({
uri: ecosystem.imageUrl,
@@ -156,7 +156,7 @@ export function EcosystemHeader(props: {
{!ecosystem.name ? (
<Skeleton className="h-12 w-[225px]" />
) : (
<h2 className="text-4xl font-bold text-foreground">
<h2 className="font-bold text-4xl text-foreground">
{ecosystem.name}
</h2>
)}
@@ -102,8 +102,8 @@ export function IntegrationPermissionsToggle({
export function IntegrationPermissionsToggleSkeleton() {
return (
<div className="flex flex-col gap-2 py-2 md:flex-row md:gap-4">
<Skeleton className="w-full h-14 md:w-32" />
<Skeleton className="w-full h-14 md:w-32" />
<Skeleton className="h-14 w-full md:w-32" />
<Skeleton className="h-14 w-full md:w-32" />
</div>
);
}
@@ -1,4 +1,5 @@
"use client";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Button } from "@/components/ui/button";
import {
Form,
@@ -6,20 +7,12 @@ import {
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { zodResolver } from "@hookform/resolvers/zod";
import { Loader2 } from "lucide-react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import type { z } from "zod";
@@ -38,7 +31,6 @@ export function UpdatePartnerForm({
name: partner.name,
domains: partner.allowlistedDomains.join(","),
bundleIds: partner.allowlistedBundleIds.join(","),
permissions: partner.permissions[0],
},
});
@@ -70,18 +62,18 @@ export function UpdatePartnerForm({
allowlistedBundleIds: values.bundleIds
.split(/,| /)
.filter((d) => d.length > 0),
permissions: [values.permissions],
});
})}
className="flex flex-col gap-4"
>
<div className="grid gap-4 grow">
<div className="flex grow flex-col gap-5">
<FormField
control={form.control}
name="name"
defaultValue="" // Note: you *must* provide a default value here or the field won't reset
render={({ field }) => (
<FormItem className="col-span-4">
<FormLabel> Name </FormLabel>
<FormControl>
<Input
placeholder="App name"
@@ -93,8 +85,8 @@ export function UpdatePartnerForm({
className={cn(
"text-xs transition-all",
form.formState.errors.name?.message
? "text-destructive block opacity-100 translate-y-0"
: "opacity-0 hidden",
? "block translate-y-0 text-destructive opacity-100"
: "hidden opacity-0",
)}
>
{form.formState.errors.name?.message}
@@ -108,21 +100,20 @@ export function UpdatePartnerForm({
defaultValue="" // Note: you *must* provide a default value here or the field won't reset
render={({ field }) => (
<FormItem className="col-span-4 lg:col-span-4">
<FormLabel> Domains </FormLabel>
<FormControl>
<>
<Input placeholder="Domains" className="peer" {...field} />
<FormDescription
className={cn(
"text-xs block",
form.formState.errors.domains?.message &&
"text-destructive translate-y-0 opacity-100 block", // If there are errors show them rather than the tip
)}
>
{form.formState.errors.domains?.message ??
"Space or comma-separated list of regex domains (e.g. *.example.com)"}
</FormDescription>
</>
<Input placeholder="Domains" className="peer" {...field} />
</FormControl>
<FormDescription
className={cn(
"block text-xs",
form.formState.errors.domains?.message &&
"block translate-y-0 text-destructive opacity-100", // If there are errors show them rather than the tip
)}
>
{form.formState.errors.domains?.message ??
"Space or comma-separated list of regex domains (e.g. *.example.com)"}
</FormDescription>
</FormItem>
)}
/>
@@ -132,75 +123,32 @@ export function UpdatePartnerForm({
defaultValue="" // Note: you *must* provide a default value here or the field won't reset
render={({ field }) => (
<FormItem className="col-span-4">
<FormLabel> Bundle ID </FormLabel>
<FormControl>
<>
<Input
placeholder="Bundle ID"
className="peer"
{...field}
/>
<FormDescription
className={cn(
"text-xs block",
form.formState.errors.bundleIds?.message &&
"text-destructive block",
)}
>
{form.formState.errors.bundleIds?.message ??
"Space or comma-separated list of bundle IDs"}
</FormDescription>
</>
<Input placeholder="Bundle ID" className="peer" {...field} />
</FormControl>
<FormDescription
className={cn(
"block text-xs",
form.formState.errors.bundleIds?.message &&
"block text-destructive",
)}
>
{form.formState.errors.bundleIds?.message ??
"Space or comma-separated list of bundle IDs"}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="permissions"
defaultValue="PROMPT_USER_V1" // Note: you *must* provide a default value here or the field won't reset
render={({ field }) => (
<FormItem className="col-span-4">
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<ToolTipLabel label="Should wallet actions prompt the user for approval?">
<FormControl>
<SelectTrigger className="w-full">
<SelectValue placeholder="Wallet prompts" />
</SelectTrigger>
</FormControl>
</ToolTipLabel>
<SelectContent>
<SelectItem value="FULL_CONTROL_V1">
Never prompt
</SelectItem>
<SelectItem value="PROMPT_USER_V1">Prompt user</SelectItem>
</SelectContent>
<FormDescription
className={cn(
"hidden text-xs transition-all peer-focus-visible:opacity-100 peer-focus-visible:translate-y-0",
form.formState.errors.permissions?.message &&
"text-destructive block", // If there are errors show them rather than the tip
)}
>
{form.formState.errors.permissions?.message ??
"Wallet signing"}
</FormDescription>
</Select>
</FormItem>
)}
/>
</div>
<Button
disabled={isPending}
type="submit"
variant="outline"
className="w-full"
className="mt-4 w-full gap-2"
>
{isPending && <Loader2 className="size-4 mr-1 animate-spin" />}
{isPending && <Spinner className="size-4" />}
Update
</Button>
</form>
@@ -20,11 +20,8 @@ export function UpdatePartnerModal({
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger>{children}</DialogTrigger>
<DialogContent
className="w-[90vw] sm:w-auto z-[10001]"
dialogOverlayClassName="z-[10000]"
>
<DialogHeader>
<DialogContent className="z-[10001]" dialogOverlayClassName="z-[10000]">
<DialogHeader className="mb-2">
<DialogTitle>Update {partner.name}</DialogTitle>
</DialogHeader>
<UpdatePartnerForm
@@ -0,0 +1,30 @@
import { Skeleton } from "@/components/ui/skeleton";
import type { Ecosystem } from "../../../../types";
import {
AuthOptionsForm,
AuthOptionsFormSkeleton,
} from "../client/auth-options-form.client";
export function AuthOptionsSection({ ecosystem }: { ecosystem?: Ecosystem }) {
return (
<section className="flex flex-col gap-4 md:gap-8">
<div className="flex flex-col gap-1">
<h4 className="font-semibold text-2xl text-foreground">
Authentication Options
</h4>
{ecosystem ? (
<p className="text-muted-foreground text-sm">
Configure the authentication options your ecosystem supports.
</p>
) : (
<Skeleton className="h-6 w-[300px]" />
)}
</div>
{ecosystem ? (
<AuthOptionsForm ecosystem={ecosystem} />
) : (
<AuthOptionsFormSkeleton />
)}
</section>
);
}
@@ -1,24 +1,28 @@
import type { Ecosystem } from "../../../../types";
import { AddPartnerForm } from "../client/add-partner-form.client";
import { AddPartnerDialogButton } from "../client/AddPartnerDialogButton";
import { PartnersTable } from "./partners-table";
export function EcosystemPartnersSection({
ecosystem,
}: { ecosystem: Ecosystem }) {
return (
<section className="flex flex-col gap-4">
<div className="flex flex-col gap-1">
<h4 className="text-2xl font-semibold text-foreground">
Ecosystem partners
</h4>
<p className="text-sm text-muted-foreground">
Configure apps that can use your wallet. Creating a partner will
generate a unique partner ID that can access your ecosystem. You will
need to generate at least one partner ID for use in your own app.
</p>
<section className="flex flex-col gap-8 lg:gap-6">
<div className="flex flex-col gap-6 lg:flex-row lg:items-start lg:justify-between">
<div className="flex flex-col gap-1">
<h4 className="font-semibold text-2xl text-foreground">
Ecosystem partners
</h4>
<p className="text-muted-foreground text-sm leading-normal">
Configure apps that can use your wallet. Creating a partner will
generate a unique partner ID that can access your ecosystem. <br />{" "}
You will need to generate at least one partner ID for use in your
own app.
</p>
</div>
<AddPartnerDialogButton ecosystem={ecosystem} />
</div>
<AddPartnerForm ecosystem={ecosystem} />
<PartnersTable ecosystem={ecosystem} />
</section>
);
@@ -11,11 +11,11 @@ export function IntegrationPermissionsSection({
return (
<section className="flex flex-col gap-4 md:gap-8">
<div className="flex flex-col gap-1">
<h4 className="text-2xl font-semibold text-foreground">
<h4 className="font-semibold text-2xl text-foreground">
Integration permissions
</h4>
{ecosystem ? (
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
{ecosystem.permission === "PARTNER_WHITELIST"
? "Your ecosystem has an allowlist. Only preset partners can add your wallet to their app."
: "Your ecosystem is public. Anyone can add your wallet to their app."}

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