Compare commits

...
Author SHA1 Message Date
Jonas DanielsGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
70adb57293 Version Packages (#4841)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-28 20:48:14 +12:00
Kien NgoandGitHub 96fa4102ba [SDK] Avoid IPFS uploads in tests whenever possible (#4824) 2024-09-28 20:26:44 +12:00
Joaquim VergesandGitHub fc56142880 fix: createAndSignUserOp with multiple transactions (#4839) 2024-09-28 20:26:06 +12:00
gregfromstl 842baa0513 [SDK] Fix: ensure optional chains are passed to wc (#4838)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on enhancing the `wallet-connect` functionality by ensuring that optional chains are correctly handled in the wallet connection request process.

### Detailed summary
- Updated the destructuring of the return value from `getChainsToRequest` to include `requiredChain`.
- Changed how `optionalChains` and `chains` are assigned in `connectWC` and `initProvider` functions.
- Modified the return structure of `getChainsToRequest` to include `requiredChain` and `optionalChains`.

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

<!-- end pr-codex -->
2024-09-28 07:51:20 +00:00
jnsdls 601958d5b7 [Add] Framer rewrite for /framer-test route (#4577)
<!-- start pr-codex -->

## PR-Codex overview
This PR updates the `next.config.js` file to enhance security policies and add new redirect paths for Framer.

### Detailed summary
- Updated `style-src` and `font-src` directives to include `vercel.live`, `assets.vercel.com`, and `framerusercontent.com`.
- Added `framerusercontent.com` and `events.framer.com` to the `script-src` directive.
- Introduced `FRAMER_PATHS` for specific redirect paths.
- Added new redirect mappings for Framer paths to `https://landing.thirdweb.com`.

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

<!-- end pr-codex -->
2024-09-27 22:30:41 +00:00
Jonas DanielsGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
bec828de86 Version Packages (#4822)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-27 15:30:03 -07:00
gregfromstl a576a5b858 [SDK] Test: Adds storage mocks and basic tests (#4833)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces new features and improvements related to file uploads and downloads using IPFS and Arweave, along with enhancements to testing with `msw`. It also updates dependencies and modifies some configurations.

### Detailed summary
- Updated `.gitignore` to include `.aider*`.
- Modified `web-node.ts` to streamline header handling.
- Added `msw` as a dependency in `package.json`.
- Enhanced `download.ts` to better handle request timeouts.
- Implemented `msw` handlers in `storage.ts` for IPFS and Arweave.
- Expanded tests in `download.test.ts` for various download scenarios.
- Added tests in `web-node.test.ts` for file uploads and error handling.

> The following files were skipped due to too many changes: `pnpm-lock.yaml`

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

<!-- end pr-codex -->
2024-09-27 22:04:39 +00:00
MananTank 5e3be734a3 Fix line break in engine Allowlisted Domains textarea placeholder (#4835)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR updates the `placeholder` prop of the `Textarea` component in the `cors.tsx` file to use a JavaScript expression instead of a string literal.

### Detailed summary
- Changed the `placeholder` prop from a string literal (`"https://example.com\nhttp://localhost:3000"`) to a JavaScript expression (`{"https://example.com\nhttp://localhost:3000"}`) for potentially better handling of the string.

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

<!-- end pr-codex -->
2024-09-27 22:00:05 +00:00
MananTank ce6d873ccc Fix Page crash on Module selection (#4834)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the handling of `selectedModule` in the `ModuleForm.tsx` component by including additional properties for better data management.

### Detailed summary
- Added `selectedModule?.contractId` to the data array.
- Added `selectedModule?.bytecodeHash` to the data array.

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

<!-- end pr-codex -->
2024-09-27 21:39:17 +00:00
gregfromstl bbe75b0bc8 [SDK] Fix: Only request specified wallet connect chains (#4832)
Falls in line with the WC spec and appeases some picky wallets

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on fixing wallet connection issues in the `thirdweb` package, particularly for wallets with limited chain support. It improves handling of optional chains and modifies how chains are requested.

### Detailed summary
- Updated handling of `optionalChains` to default to `[1]` if empty.
- Changed `chains` to be `undefined` if `chainToRequest` is not provided.
- Modified `setRequestedChainsIds` function to accept `chains` as `undefined`.
- Simplified logic for `chainsToRequest` to use `undefined` when no chains are available.

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

<!-- end pr-codex -->
2024-09-27 19:47:31 +00:00
gregfromstl cefa4eb097 [SDK] Docs: fix social profiles docs (#4821)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR updates the method used to retrieve social profiles in the `profiles.ts` file, changing the function name from `getProfiles` to `getSocialProfiles`.

### Detailed summary
- Changed the import statement from `getProfiles` to `getSocialProfiles` from the `thirdweb/social` module.
- Updated the usage of the function in the example to reflect the new function name.

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

<!-- end pr-codex -->
2024-09-27 16:34:01 +00:00
kien-ngo 63ceb2874c [Dashboard] Remove <Stack/> (3) (#4831)
## 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 `Stack` component from Chakra UI with `div` elements styled using Tailwind CSS classes. This change aims to improve layout consistency across various components in the application.

### Detailed summary
- Replaced `<Stack>` with `<div className="flex ...">` in multiple components for layout consistency.
- Adjusted spacing and alignment properties to match previous `Stack` behavior.
- Updated components in `airdrop-tab.tsx`, `select-option.tsx`, `airdrop-upload.tsx`, and others.

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

<!-- end pr-codex -->
2024-09-27 16:08:38 +00:00
kien-ngo 509c7981ee [Dashboard] Remove <Stack /> (2) (#4830)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on replacing instances of the `Stack` component with `div` elements styled using Tailwind CSS classes, enhancing the layout while maintaining functionality across various components in the dashboard application.

### Detailed summary
- Replaced `<Stack>` with `<div className="flex flex-col gap-X">` in multiple components for consistent styling.
- Updated `spacing` properties to `gap` for better alignment with Tailwind CSS practices.
- Maintained functionality across components such as `PermissionEditor`, `EventsFeedItem`, and various modal dialogs.

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

<!-- end pr-codex -->
2024-09-27 15:54:39 +00:00
kien-ngo 8ea9420cc4 [Dashboard] Remove <Stack /> (1) (#4829)
## 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 `Stack` component with `div` elements styled with Flexbox classes for layout consistency across various components in the application.

### Detailed summary
- Replaced `Stack` components with `div` elements using Flexbox for layout in multiple files.
- Updated spacing and alignment properties to maintain visual consistency.
- Adjusted modal body structures to use Flexbox for better responsiveness.

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

<!-- end pr-codex -->
2024-09-27 15:48:40 +00:00
kien-ngo 8dda1139dc [Dashboard] Remove default <Stack /> (#4828)
@MananTank here's the source code: https://github.com/chakra-ui/chakra-ui/blob/main/packages/react/src/components/stack/stack.tsx

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on replacing `Stack` components with `div` elements styled with `flex` and `gap` classes across various files for a more consistent layout approach in the UI.

### Detailed summary
- Replaced `Stack` with `div className="flex flex-col gap-2"` in multiple components.
- Updated layout in `airdrop-tab.tsx`, `mint-form.tsx`, `lazy-mint-form.tsx`, `split/page.tsx`, `shared-metadata-form.tsx`, `update-metadata-form.tsx`, `transaction-timeline.tsx`, `NewsletterSection.tsx`, and more.
- Enhanced UI consistency and responsiveness.

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

<!-- end pr-codex -->
2024-09-27 11:21:45 +00:00
kien-ngo a637cab051 [Dashboard] Remove @react-icons/all-files (#4819)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating icon components in the `dashboard` application by replacing `react-icons` with custom SVG icons for improved performance and maintainability.

### Detailed summary
- Removed `@react-icons/all-files` dependency.
- Added new SVG components: `MediumIcon`, `YoutubeIcon`, `TiktokIcon`, `TelegramIcon`, `RedditIcon`, `MetaIcon`, `InstagramIcon`.
- Updated `SupportPage`, `PublisherSocials`, `AppFooter`, and other components to use new icons.

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

<!-- end pr-codex -->
2024-09-27 03:07:36 +00:00
kien-ngo c31f919cd4 [Dashboard] Replace Linkedin icon (#4818)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new `LinkedInIcon` component and replaces instances of the `SiLinkedin` icon with the new `LinkedInIcon` in two files, enhancing the consistency of icon usage across the application.

### Detailed summary
- Created a new `LinkedInIcon` component in `LinkedinIcon.tsx`.
- Replaced `SiLinkedin` with `LinkedInIcon` in `socialLinks.tsx`.
- Replaced `SiLinkedin` with `LinkedInIcon` in `PublisherSocials.tsx`.

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

<!-- end pr-codex -->
2024-09-27 02:41:46 +00:00
kien-ngo ffcd199381 [Dashboard] Replace twitter icon (#4817)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new `XIcon` component and replaces existing Twitter icons in various components with this new icon, enhancing consistency and potentially improving the visual design.

### Detailed summary
- Added a new `XIcon` component in `XIcon.tsx`.
- Replaced `SiTwitter` icon with `XIcon` in:
  - `edit-profile.tsx`
  - `socialLinks.tsx`
  - `PublisherSocials.tsx`

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

<!-- end pr-codex -->
2024-09-27 02:41:43 +00:00
kien-ngo b1d78b1f42 [Dashboard] Replace Discord icon (#4816)
## 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 `SiDiscord` icon from `@react-icons/all-files` with a custom `DiscordIcon` component in multiple files, enhancing the icon's integration and potentially improving styling consistency.

### Detailed summary
- Replaced `SiDiscord` icon with `DiscordIcon` in:
  - `edit-profile.tsx`
  - `socialLinks.tsx`
  - `PublisherSocials.tsx`
- Introduced `DiscordIcon` component in `DiscordIcon.tsx` with an SVG representation.

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

<!-- end pr-codex -->
2024-09-27 01:52:10 +00:00
kien-ngo bc348ea953 [Dashboard] Replace Unity icon (#4814)
## 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 `SiUnity` icon from the `react-icons` library with a new custom `UnityIcon` component across multiple files in the dashboard application.

### Detailed summary
- Added a new `UnityIcon` component in `UnityIcon.tsx`.
- Replaced `SiUnity` with `UnityIcon` in:
  - `ConnectSDKCard.tsx`
  - `ConnectPlaygroundButton.tsx`
  - `CodeOptionButton.tsx`
  - `SupportedPlatformLink.tsx`
  - `CodeSegment.tsx`

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

<!-- end pr-codex -->
2024-09-27 01:24:04 +00:00
kien-ngo 43a24e2ed4 [Dashboard] Replace React icon (#4813)
## 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 `SiReact` icon with a new custom `ReactIcon` component across several files in the dashboard application.

### Detailed summary
- Added `ReactIcon` component in `apps/dashboard/src/components/icons/brand-icons/ReactIcon.tsx`.
- Replaced `SiReact` with `ReactIcon` in:
  - `ConnectSDKCard.tsx`
  - `ConnectPlaygroundButton.tsx`
  - `CodeOptionButton.tsx`
  - `SupportedPlatformLink.tsx`
  - `CodeSegment.tsx`

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

<!-- end pr-codex -->
2024-09-27 01:24:02 +00:00
kien-ngo 7b40f2e8e4 [Dashboard] Replace JS and TS icons (#4812)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on replacing existing icon imports for `JavaScript` and `TypeScript` with custom icon components, enhancing the visual representation in the application.

### Detailed summary
- Replaced `SiJavascript` with `JavaScriptIcon` in `ConnectPlaygroundButton.tsx`, `CodeOptionButton.tsx`, `CodeSegment.tsx`.
- Replaced `SiTypescript` with `TypeScriptIcon` in `SupportedPlatformLink.tsx`, `ConnectSDKCard.tsx`, `CodeSegment.tsx`.
- Added new components: `JavaScriptIcon.tsx` and `TypeScriptIcon.tsx` for custom icons.

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

<!-- end pr-codex -->
2024-09-27 01:24:00 +00:00
kien-ngo c1d94aec4f [Dashboard] Replace SiGithub (#4811)
## 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 `SiGithub` icon from `@react-icons` with a custom `GithubIcon` component across several files in the dashboard application.

### Detailed summary
- Replaced `SiGithub` with `GithubIcon` in:
  - `GameCard.tsx`
  - `app-footer.tsx`
  - `edit-profile.tsx`
  - `socialLinks.tsx`
  - `PublisherSocials.tsx`
- Added `GithubIcon` component in `GithubIcon.tsx` with SVG implementation.

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

<!-- end pr-codex -->
2024-09-27 01:23:58 +00:00
kien-ngo ebd5908d4a [Dashboard] Remove non-branding icons (#4810)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on replacing various `react-icons` with `lucide-react` icons across multiple components, along with some minor adjustments to properties and imports. It aims to standardize icon usage within the codebase.

### Detailed summary
- Removed `IconType` from multiple interfaces in `types.ts` and `AnimatedCLICommand.tsx`.
- Replaced `react-icons` with `lucide-react` icons in `HackathonFooter`, `NFTClaimButton`, and other components.
- Updated icon rendering logic in multiple components for consistency.
- Adjusted imports and usage of icons in `FileInput`, `BatchLazyMint`, and `MismatchButton`.

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

<!-- end pr-codex -->
2024-09-27 01:11:42 +00:00
Edward SunandGitHub 21b032d1b8 Remove text in UI (#4823) 2024-09-26 16:56:46 -07:00
kumaryash90 d0616c4486 Fix Cronos zkevm deployments (#4815)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR adds support for the `cronos zkevm` by updating the `isZkSyncChain` check to include additional chain IDs for both the testnet and mainnet.

### Detailed summary
- Updated the `isZkSyncChain` function in the file `packages/thirdweb/src/utils/any-evm/zksync/isZkSyncChain.ts`.
- Added chain ID `282` for `cronos zkevm testnet`.
- Added chain ID `388` for `cronos zkevm mainnet`.

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

<!-- end pr-codex -->
2024-09-26 22:13:03 +00:00
MananTank 88db5eaeab Add upload to IPFS on teams & account avatar form fields (#4807)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the account and team settings pages by adding image upload functionality for accounts and teams, updating component imports, and improving layout responsiveness.

### Detailed summary
- Added `updateAccountImage` method to `AccountSettingsPage` and `AccountSettingsPageUI`.
- Introduced `updateTeamImage` method in `TeamGeneralSettingsPage` and `TeamGeneralSettingsPageUI`.
- Changed layout from `min-h-full` to `min-h-screen`.
- Updated import statements to reflect new component names.
- Implemented async image upload functionality with IPFS integration.
- Added error handling for image update methods.

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

<!-- end pr-codex -->
2024-09-26 20:43:18 +00:00
MananTank 09b1c28c09 Replace Avatar placeholders with Avatar components in teams and account layout (#4803)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on updating the account handling in various components to use an `account` object instead of separate email fields, and introduces new avatar components for enhanced visual representation of users and projects.

### Detailed summary
- Replaced `email` with `account` in multiple components.
- Introduced `GradientAvatar` and `ProjectAvatar` components for displaying user and project images.
- Updated story files to showcase new avatar components.
- Added placeholder comments for future image integration.
- Adjusted layout and styling for better alignment with new account structure.

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

<!-- end pr-codex -->
2024-09-26 20:43:14 +00:00
162 changed files with 1889 additions and 2470 deletions
+2 -1
View File
@@ -27,4 +27,5 @@ sitemap*.xml
packages/*/typedoc/*
*storybook.log
storybook-static
storybook-static
.aider*
+2
View File
@@ -60,6 +60,7 @@ module.exports = {
"AspectRatio",
"useToast",
"useClipboard",
"Badge",
// also the types
"ButtonProps",
"BadgeProps",
@@ -71,6 +72,7 @@ module.exports = {
"MenuGroupProps",
"MenuItemProps",
"AspectRatioProps",
"BadgeProps",
],
message:
'Use the equivalent component from "tw-components" instead.',
+10 -3
View File
@@ -5,10 +5,10 @@ const ContentSecurityPolicy = `
img-src * data: blob:;
media-src * data: blob:;
object-src 'none';
style-src 'self' 'unsafe-inline' https://vercel.live;
font-src 'self' https://vercel.live https://assets.vercel.com;
style-src 'self' 'unsafe-inline' vercel.live;
font-src 'self' vercel.live assets.vercel.com framerusercontent.com;
frame-src * data:;
script-src 'self' 'unsafe-eval' 'unsafe-inline' 'wasm-unsafe-eval' 'inline-speculation-rules' *.thirdweb.com *.thirdweb-dev.com vercel.live js.stripe.com;
script-src 'self' 'unsafe-eval' 'unsafe-inline' 'wasm-unsafe-eval' 'inline-speculation-rules' *.thirdweb.com *.thirdweb-dev.com vercel.live js.stripe.com framerusercontent.com events.framer.com;
connect-src * data: blob:;
worker-src 'self' blob:;
block-all-mixed-content;
@@ -39,6 +39,9 @@ const securityHeaders = [
const redirects = require("./redirects");
// add framer paths here
const FRAMER_PATHS = ["/connect/sign-in", "/contracts/modular-contracts"];
/**
* @returns {import('next').RemotePattern[]}
*/
@@ -128,6 +131,10 @@ const moduleExports = {
source: "/thirdweb.eth/:path*",
destination: "/deployer.thirdweb.eth/:path*",
},
...FRAMER_PATHS.map((path) => ({
source: path,
destination: `https://landing.thirdweb.com${path}`,
})),
];
},
images: {
-1
View File
@@ -45,7 +45,6 @@
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-tooltip": "1.1.2",
"@react-icons/all-files": "^4.1.0",
"@sentry/nextjs": "8.30.0",
"@shazow/whatsabi": "^0.14.1",
"@stripe/react-stripe-js": "^2.7.3",
@@ -0,0 +1,89 @@
import type { Meta, StoryObj } from "@storybook/react";
import { useState } from "react";
import { BadgeContainer } from "../../../../stories/utils";
import { Button } from "../../ui/button";
import { GradientAvatar } from "./GradientAvatar";
const meta = {
title: "blocks/Avatars/GradientAvatar",
component: Story,
parameters: {},
} satisfies Meta<typeof Story>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
args: {},
};
function Story() {
return (
<div className="flex flex-col gap-10 p-10">
<p> All images below are set with size-6 className </p>
<BadgeContainer label="No Src, No id - Skeleton">
<GradientAvatar id={undefined} src={undefined} className="size-6" />
</BadgeContainer>
<BadgeContainer label="No Src, id=foo - Skeleton">
<GradientAvatar id={"foo"} src={undefined} className="size-6" />
</BadgeContainer>
<BadgeContainer label="Invalid/Empty Src, id=foo - Gradient">
<GradientAvatar id={"foo"} src={""} className="size-6" />
</BadgeContainer>
<BadgeContainer label="Invalid/Empty Src, id=bar - Gradient">
<GradientAvatar id={"bar"} src={""} className="size-6" />
</BadgeContainer>
<BadgeContainer label="Empty/Invalid Src, No id - default fallback">
<GradientAvatar src="invalid-src" id={undefined} className="size-6" />
</BadgeContainer>
<ToggleTest />
</div>
);
}
function ToggleTest() {
const [data, setData] = useState<undefined | { src: string; id: string }>(
undefined,
);
return (
<div className="relative flex flex-col gap-10 border p-6">
<Button
variant="outline"
onClick={() => {
if (data) {
setData(undefined);
} else {
setData({
id: "foo",
src: "https://picsum.photos/400",
});
}
}}
className="absolute top-6 right-6 inline-flex"
>
Toggle Src
</Button>
<p> Src+ID is: {data ? "set" : "not set"} </p>
<BadgeContainer label="Valid Src">
<GradientAvatar src={data?.src} id={data?.id} className="size-6" />
</BadgeContainer>
<BadgeContainer label="invalid Src">
<GradientAvatar
className="size-6"
src={data ? "invalid-src" : undefined}
id={undefined}
/>
</BadgeContainer>
</div>
);
}
@@ -0,0 +1,58 @@
import { Img } from "@/components/blocks/Img";
import { useMemo } from "react";
import { cn } from "../../../lib/utils";
const gradients = [
["#fca5a5", "#b91c1c"],
["#fdba74", "#c2410c"],
["#fcd34d", "#b45309"],
["#fde047", "#a16207"],
["#a3e635", "#4d7c0f"],
["#86efac", "#15803d"],
["#67e8f9", "#0e7490"],
["#7dd3fc", "#0369a1"],
["#93c5fd", "#1d4ed8"],
["#a5b4fc", "#4338ca"],
["#c4b5fd", "#6d28d9"],
["#d8b4fe", "#7e22ce"],
["#f0abfc", "#a21caf"],
["#f9a8d4", "#be185d"],
["#fda4af", "#be123c"],
];
function getGradientForString(str: string) {
const number = Math.abs(
str.split("").reduce((acc, b, i) => acc + b.charCodeAt(0) * (i + 1), 0),
);
const index = number % gradients.length;
return gradients[index];
}
export function GradientAvatar(props: {
src: string | undefined;
id: string | undefined;
className: string;
}) {
const gradient = useMemo(() => {
if (!props.id) {
return undefined;
}
return getGradientForString(props.id);
}, [props.id]);
return (
<Img
src={props.src}
className={cn("rounded-full", props.className)}
fallback={
gradient ? (
<div
style={{
background: `linear-gradient(45deg, ${gradient[0]} 0%, ${gradient[1]} 100%)`,
}}
/>
) : undefined
}
/>
);
}
@@ -0,0 +1,76 @@
import type { Meta, StoryObj } from "@storybook/react";
import { useState } from "react";
import { BadgeContainer } from "../../../../stories/utils";
import { Button } from "../../ui/button";
import { ProjectAvatar } from "./ProjectAvatar";
const meta = {
title: "blocks/Avatars/ProjectAvatar",
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-6 className </p>
<BadgeContainer label="No Src - Skeleton">
<ProjectAvatar src={undefined} className="size-6" />
</BadgeContainer>
<BadgeContainer label="Invalid/Empty Src - BoxIcon Fallback">
<ProjectAvatar src={""} className="size-6" />
</BadgeContainer>
<ToggleTest />
</div>
);
}
function ToggleTest() {
const [data, setData] = useState<undefined | { src: string; name: string }>(
undefined,
);
return (
<div className="relative flex flex-col gap-10 border p-6">
<Button
variant="outline"
onClick={() => {
if (data) {
setData(undefined);
} else {
setData({
name: "foo",
src: "https://picsum.photos/400",
});
}
}}
className="absolute top-6 right-6 inline-flex"
>
Toggle Src
</Button>
<p> Src+Name is: {data ? "set" : "not set"} </p>
<BadgeContainer label="Valid Src">
<ProjectAvatar src={data?.src} className="size-6" />
</BadgeContainer>
<BadgeContainer label="invalid Src">
<ProjectAvatar
src={data ? "invalid-src" : undefined}
className="size-6"
/>
</BadgeContainer>
</div>
);
}
@@ -0,0 +1,21 @@
import { Img } from "@/components/blocks/Img";
import { BoxIcon } from "lucide-react";
import { cn } from "../../../lib/utils";
export function ProjectAvatar(props: {
src: string | undefined;
className: string | undefined;
}) {
return (
<Img
src={props.src}
className={cn("rounded-lg border", props.className)}
alt={""}
fallback={
<div className="flex items-center justify-center bg-muted/50">
<BoxIcon className="size-[50%] text-muted-foreground" />
</div>
}
/>
);
}
@@ -1,17 +1,15 @@
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { ThirdwebMiniLogo } from "app/components/ThirdwebMiniLogo";
import { DiscordIcon } from "components/icons/brand-icons/DiscordIcon";
import { GithubIcon } from "components/icons/brand-icons/GithubIcon";
import { InstagramIcon } from "components/icons/brand-icons/InstagramIcon";
import { LinkedInIcon } from "components/icons/brand-icons/LinkedinIcon";
import { RedditIcon } from "components/icons/brand-icons/RedditIcon";
import { TiktokIcon } from "components/icons/brand-icons/TiktokIcon";
import { XIcon } from "components/icons/brand-icons/XIcon";
import { YoutubeIcon } from "components/icons/brand-icons/YoutubeIcon";
import Link from "next/link";
import {
SiDiscord,
SiGithub,
SiInstagram,
SiLinkedin,
SiReddit,
SiTiktok,
SiX,
SiYoutube,
} from "react-icons/si";
type AppFooterProps = {
className?: string;
@@ -35,17 +33,17 @@ export function AppFooter(props: AppFooterProps) {
<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="size-5 text-muted-foreground" />
<GithubIcon 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="size-5 text-muted-foreground" />
<TiktokIcon 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="size-5 text-muted-foreground" />
<InstagramIcon className="size-5 text-muted-foreground" />
</Link>
</Button>
<Button size="icon" variant="ghost" asChild className="size-9">
@@ -53,27 +51,27 @@ export function AppFooter(props: AppFooterProps) {
href="hhttps://www.linkedin.com/company/third-web/"
target="_blank"
>
<SiLinkedin className="size-5 text-muted-foreground" />
<LinkedInIcon 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="size-5 text-muted-foreground" />
<YoutubeIcon 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="size-5 text-muted-foreground" />
<DiscordIcon 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="size-5 text-muted-foreground" />
<RedditIcon 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="size-5 text-muted-foreground" />
<XIcon className="size-5 text-muted-foreground" />
</Link>
</Button>
</div>
@@ -67,6 +67,7 @@ export type Account = {
paymentFailureCode: string;
serviceCutoffDate: string;
}[];
// TODO - add image URL
};
interface UpdateAccountInput {
@@ -6,11 +6,11 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { DiscordIcon } from "components/icons/brand-icons/DiscordIcon";
import { BookOpenIcon, ChevronRightIcon } from "lucide-react";
import type { Metadata } from "next";
import Image from "next/image";
import Link from "next/link";
import { SiDiscord } from "react-icons/si";
import accountIcon from "../../../../public/assets/support/account.svg";
import contractsIcon from "../../../../public/assets/support/contracts.png";
import discordIllustration from "../../../../public/assets/support/discord-illustration.png";
@@ -213,7 +213,7 @@ export default function SupportPage() {
asChild
>
<Link href="https://discord.gg/thirdweb">
<SiDiscord />
<DiscordIcon className="size-4" />
<span>Join Discord</span>
</Link>
</Button>
@@ -35,10 +35,10 @@ export function AccountHeader(props: {
const headerProps: AccountHeaderCompProps = {
teamsAndProjects: props.teamsAndProjects,
email: myAccountQuery.data?.email,
logout: logout,
connectButton: <CustomConnectWallet />,
createProject: () => setIsCreateProjectDialogOpen(true),
account: myAccountQuery.data,
};
return (
@@ -45,13 +45,26 @@ function Variants(props: {
return (
<ThirdwebProvider>
<div className="flex min-h-screen flex-col gap-6 bg-gray-700 px-4 py-10">
<BadgeContainer label="Logged in">
<BadgeContainer label="Account Loaded">
<Comp
teamsAndProjects={teamsAndProjectsStub}
email="[email protected]"
logout={() => {}}
connectButton={<ConnectButtonStub />}
createProject={() => {}}
account={{
id: "foo",
email: "[email protected]",
}}
/>
</BadgeContainer>
<BadgeContainer label="Account Loading">
<Comp
teamsAndProjects={teamsAndProjectsStub}
logout={() => {}}
connectButton={<ConnectButtonStub />}
createProject={() => {}}
account={undefined}
/>
</BadgeContainer>
</div>
@@ -1,6 +1,8 @@
import type { Project } from "@/api/projects";
import type { Team } from "@/api/team";
import { GradientAvatar } from "@/components/blocks/Avatars/GradientAvatar";
import { cn } from "@/lib/utils";
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
import Link from "next/link";
import { SecondaryNav } from "../../components/Header/SecondaryNav/SecondaryNav";
import { MobileBurgerMenuButton } from "../../components/MobileBurgerMenuButton";
@@ -10,11 +12,11 @@ import { TeamSelectorMobileMenuButton } from "../../team/components/TeamHeader/T
export type AccountHeaderCompProps = {
className?: string;
email: string | undefined;
logout: () => void;
connectButton: React.ReactNode;
teamsAndProjects: Array<{ team: Team; projects: Project[] }>;
createProject: () => void;
account: Pick<Account, "email" | "id"> | undefined;
};
export function AccountHeaderDesktopUI(props: AccountHeaderCompProps) {
@@ -37,8 +39,12 @@ export function AccountHeaderDesktopUI(props: AccountHeaderCompProps) {
href="/account"
className="flex flex-row items-center gap-2 font-normal text-sm"
>
{/* TODO - replace with account image */}
<div className="size-6 rounded-full border border-border bg-muted" />
{/* TODO - set account Image */}
<GradientAvatar
id={props.account?.id}
src={props.account ? "" : undefined}
className="size-6"
/>
<span> My Account </span>
</Link>
@@ -48,12 +54,13 @@ export function AccountHeaderDesktopUI(props: AccountHeaderCompProps) {
teamsAndProjects={props.teamsAndProjects}
focus="team-selection"
createProject={props.createProject}
account={props.account}
/>
</div>
</div>
<SecondaryNav
email={props.email}
account={props.account}
logout={props.logout}
connectButton={props.connectButton}
/>
@@ -77,8 +84,12 @@ export function AccountHeaderMobileUI(props: AccountHeaderCompProps) {
"flex flex-row items-center gap-2 font-normal text-foreground text-sm",
)}
>
{/* TODO - replace with account image */}
<div className="size-7 rounded-full border border-border bg-muted" />
{/* TODO - set account image */}
<GradientAvatar
id={props.account?.id}
src={props.account ? "" : undefined}
className="size-6"
/>
<span> My Account </span>
</Link>
@@ -86,12 +97,13 @@ export function AccountHeaderMobileUI(props: AccountHeaderCompProps) {
currentTeam={undefined}
teamsAndProjects={props.teamsAndProjects}
upgradeTeamLink={undefined}
account={props.account}
/>
</div>
</div>
<MobileBurgerMenuButton
email={props.email}
email={props.account?.email}
logout={props.logout}
connectButton={props.connectButton}
/>
+1 -1
View File
@@ -10,7 +10,7 @@ export default async function AccountLayout(props: {
children: React.ReactNode;
}) {
return (
<div className="flex min-h-full flex-col bg-background">
<div className="flex min-h-screen flex-col bg-background">
<div className="flex grow flex-col">
<HeaderAndNav />
<div className="border-border border-b py-10">
@@ -2,6 +2,7 @@
import type { Team } from "@/api/team";
import type { TeamAccountRole } from "@/api/team-members";
import { GradientAvatar } from "@/components/blocks/Avatars/GradientAvatar";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
@@ -102,8 +103,8 @@ function TeamRow(props: {
<div className="flex items-center justify-between gap-2">
{/* start */}
<div className="flex items-center gap-4">
{/* TODO - render team avatar */}
<div className="size-8 rounded-full border bg-muted" />
{/* TODO - set image */}
<GradientAvatar className="size-8" src={""} id={props.team.id} />
<div>
<div className="flex items-center gap-3">
@@ -0,0 +1,33 @@
"use client";
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
import type { ThirdwebClient } from "thirdweb";
import { upload } from "thirdweb/storage";
import { AccountSettingsPageUI } from "./AccountSettingsPageUI";
export function AccountSettingsPage(props: {
account: Account;
client: ThirdwebClient;
}) {
return (
<AccountSettingsPageUI
account={props.account}
updateAccountImage={async (file) => {
if (file) {
// upload to IPFS
const ipfsUri = await upload({
client: props.client,
files: [file],
});
// TODO - Implement updating the account image with uri
console.log(ipfsUri);
} else {
// TODO - Implement deleting the account image
}
throw new Error("Not implemented");
}}
/>
);
}
@@ -41,6 +41,9 @@ function Variants() {
name: "John Doe",
email: "[email protected]",
}}
updateAccountImage={async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
}}
/>
</div>
<Toaster richColors />
@@ -42,10 +42,11 @@ type MinimalAccount = Pick<
export function AccountSettingsPageUI(props: {
account: MinimalAccount;
updateAccountImage: (file: File | undefined) => Promise<void>;
}) {
return (
<div className="flex flex-col gap-8">
<AccountAvatarFormControl />
<AccountAvatarFormControl updateAccountImage={props.updateAccountImage} />
<AccountNameFormControl name={props.account.name || ""} />
<AccountEmailFormControl
email={props.account.email || ""}
@@ -58,16 +59,14 @@ export function AccountSettingsPageUI(props: {
);
}
function AccountAvatarFormControl() {
function AccountAvatarFormControl(props: {
updateAccountImage: (file: File | undefined) => Promise<void>;
}) {
const [avatar, setAvatar] = useState<File>(); // TODO: prefill with account avatar
// TODO - implement
const updateAvatarMutation = useMutation({
mutationFn: async (_avatar: File | undefined) => {
// Fake loading
await new Promise((resolve) => setTimeout(resolve, 3000));
console.log("Updating account image to", _avatar);
throw new Error("Not implemented");
await props.updateAccountImage(_avatar);
},
});
@@ -1,13 +1,18 @@
import { getThirdwebClient } from "@/constants/thirdweb.server";
import { redirect } from "next/navigation";
import { AccountSettingsPageUI } from "./AccountSettingsPageUI";
import { getAuthToken } from "../../api/lib/getAuthToken";
import { AccountSettingsPage } from "./AccountSettingsPage";
import { getAccount } from "./getAccount";
export default async function Page() {
const account = await getAccount();
const token = getAuthToken();
if (!account) {
if (!account || !token) {
redirect(`/login?next=${encodeURIComponent("/account")}`);
}
return <AccountSettingsPageUI account={account} />;
return (
<AccountSettingsPage account={account} client={getThirdwebClient(token)} />
);
}
@@ -1,10 +1,11 @@
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
import Link from "next/link";
import type React from "react";
import { ResourcesDropdownButton } from "./ResourcesDropdownButton";
import { AccountButton } from "./account-button.client";
export function SecondaryNav(props: {
email: string | undefined;
account: Pick<Account, "email" | "id"> | undefined;
logout: () => void;
connectButton: React.ReactNode;
}) {
@@ -27,9 +28,9 @@ export function SecondaryNav(props: {
</Link>
<AccountButton
email={props.email}
logout={props.logout}
connectButton={props.connectButton}
account={props.account}
/>
</div>
);
@@ -1,5 +1,6 @@
"use client";
import { GradientAvatar } from "@/components/blocks/Avatars/GradientAvatar";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
@@ -8,14 +9,15 @@ import {
} from "@/components/ui/dropdown-menu";
import { SkeletonContainer } from "@/components/ui/skeleton";
import { cn } from "@/lib/utils";
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
import { LogOutIcon, Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";
import Link from "next/link";
export function AccountButton(props: {
email: string | undefined;
logout: () => void;
connectButton: React.ReactNode;
account?: Pick<Account, "email" | "id">;
}) {
const { setTheme, theme } = useTheme();
@@ -27,15 +29,15 @@ export function AccountButton(props: {
className="!p-0 !h-auto size-10 rounded-full hover:ring-2 hover:ring-offset-2"
variant="ghost"
>
{/* TODO - replace with account image */}
<div className="size-9 rounded-full border border-border bg-muted" />
{/* TODO - set account image */}
<GradientAvatar id={props.account?.id} src={""} className="size-9" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-[300px] rounded-lg p-0" align="end">
<div className="border-border border-b p-4 pb-5">
<SkeletonContainer
skeletonData="[email protected]"
loadedData={props.email}
loadedData={props.account?.email}
render={(v) => <p className="text-muted-foreground text-sm">{v}</p>}
/>
@@ -13,6 +13,7 @@ import { ChevronDownIcon, SearchIcon } from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { ProjectAvatar } from "@/components/blocks/Avatars/ProjectAvatar";
import { CopyButton } from "@/components/ui/CopyButton";
import {
Select,
@@ -119,8 +120,8 @@ function ProjectCard(props: {
key={project.id}
className="relative flex items-center gap-4 rounded-lg border border-border bg-muted/50 p-4 transition-colors hover:bg-muted/70"
>
{/* TODO - replace with project image */}
<div className="size-10 shrink-0 rounded-full bg-border" />
{/* TODO - set image */}
<ProjectAvatar className="size-10 rounded-full" src="" />
<div>
<Link
@@ -0,0 +1,62 @@
"use client";
import type { Team } from "@/api/team";
import { cn } from "@/lib/utils";
import { useAccount } from "@3rdweb-sdk/react/hooks/useApi";
import { usePathname } from "next/navigation";
import { useState } from "react";
import { TeamSettingsSidebar } from "./_components/sidebar/TeamSettingsSidebar";
import { TeamSettingsMobileNav } from "./_components/sidebar/TeamsMobileNav";
import { getTeamSettingsLinks } from "./_components/sidebar/getTeamSettingsLinks";
// on the /~/settings page
// - On desktop: show the general settings as usual
// - On mobile: show the full nav instead of page content and when user clicks on the "General Settings" ( first link ) - hide the full nav and show the page content
export function SettingsLayout(props: {
team: Team;
children: React.ReactNode;
}) {
const [_showFullNavOnMobile, setShowFullNavOnMobile] = useState(true);
const pathname = usePathname();
const isSettingsOverview = (pathname || "").endsWith("/~/settings");
const showFullNavOnMobile = _showFullNavOnMobile && isSettingsOverview;
const links = getTeamSettingsLinks(props.team.slug);
const activeLink = links.find((link) => pathname === link.href);
const accountQuery = useAccount();
return (
<div>
{/* Huge page title */}
<div className="border-border border-b py-10">
<div className="container">
<h1 className="font-semibold text-3xl tracking-tight">
Team Settings
</h1>
</div>
</div>
<div className="md:hidden">
<TeamSettingsMobileNav
teamSlug={props.team.slug}
showFull={showFullNavOnMobile}
setShowFull={setShowFullNavOnMobile}
activeLink={activeLink}
/>
</div>
<div className="container flex grow gap-8 lg:min-h-[900px] [&>*]:py-8 lg:[&>*]:py-10">
<TeamSettingsSidebar team={props.team} account={accountQuery.data} />
<div
className={cn(
"grow",
// if showing full nav on mobile - hide the page content
showFullNavOnMobile && "max-sm:hidden",
)}
>
{props.children}
</div>
</div>
</div>
);
}
@@ -1,10 +1,14 @@
import type { Team } from "@/api/team";
import { GradientAvatar } from "@/components/blocks/Avatars/GradientAvatar";
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
import { SidebarLink } from "./SidebarLink";
import { getTeamSettingsLinks } from "./getTeamSettingsLinks";
export function TeamSettingsSidebar(props: {
teamSlug: string;
team: Team;
account: Pick<Account, "id"> | undefined;
}) {
const teamLinks = getTeamSettingsLinks(props.teamSlug);
const teamLinks = getTeamSettingsLinks(props.team.slug);
const teamGroupLinks = teamLinks.filter((link) => link.group === "team");
const accountGroupLinks = teamLinks.filter(
(link) => link.group === "account",
@@ -12,8 +16,24 @@ export function TeamSettingsSidebar(props: {
return (
<aside className="lg:-ml-2 sticky top-0 hidden w-[250px] shrink-0 grow-0 flex-col gap-4 self-start lg:flex">
<RenderLinkGroup links={teamGroupLinks} title="Team" />
<RenderLinkGroup links={accountGroupLinks} title="Account" />
<RenderLinkGroup
links={teamGroupLinks}
title="Team"
team={props.team}
titleAvatarIcon={{
id: props.team.id,
src: "", // TODO - set team image
}}
/>
<RenderLinkGroup
links={accountGroupLinks}
title="Account"
team={props.team}
titleAvatarIcon={{
id: props.account?.id,
src: "", // TODO - set team image
}}
/>
</aside>
);
}
@@ -24,12 +44,20 @@ function RenderLinkGroup(props: {
href: string;
}>;
title: string;
team: Team;
titleAvatarIcon: {
id: string | undefined;
src: string;
};
}) {
return (
<>
<div className="flex items-center gap-1.5 px-4">
{/* TODO - using placeholder for now */}
<div className="size-4 rounded-full border border-border bg-muted" />
<GradientAvatar
src={props.titleAvatarIcon.src}
className="size-4"
id={props.titleAvatarIcon.id}
/>
<p className="text-muted-foreground text-xs">{props.title}</p>
</div>
@@ -2,12 +2,11 @@ import type { Team } from "@/api/team";
import { Toaster } from "@/components/ui/sonner";
import type { Meta, StoryObj } from "@storybook/react";
import { mobileViewport } from "../../../../../../../stories/utils";
import SettingsLayout from "../layout";
import {
DeleteTeamCard,
GeneralSettingsPage,
LeaveTeamCard,
} from "./GeneralSettingsPage";
TeamGeneralSettingsPageUI,
} from "./TeamGeneralSettingsPageUI";
const meta = {
title: "Team/Settings/General",
@@ -47,17 +46,14 @@ const testTeam: Team = {
function Story() {
return (
<div className="min-h-screen bg-background text-foreground">
<SettingsLayout
params={{
team_slug: testTeam.slug,
<div className="mx-auto w-full max-w-[1100px] px-4 py-6">
<TeamGeneralSettingsPageUI
team={testTeam}
updateTeamImage={async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
}}
>
<GeneralSettingsPage team={testTeam} />
</SettingsLayout>
/>
<ComponentVariantions />
<Toaster richColors />
</div>
);
@@ -1,4 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react";
import { teamStub } from "../../../../../../../stories/stubs";
import {
BadgeContainer,
mobileViewport,
@@ -39,7 +40,25 @@ function Story(props: {
}) {
return (
<div className="container min-h-screen py-6 text-foreground">
{props.type === "desktop" && <TeamSettingsSidebar teamSlug="foo" />}
{props.type === "desktop" && (
<div className="flex flex-col gap-10">
<BadgeContainer label="account loaded">
<TeamSettingsSidebar
team={teamStub("foo", "free")}
account={{
id: "x",
}}
/>
</BadgeContainer>
<BadgeContainer label="account loading">
<TeamSettingsSidebar
team={teamStub("foo", "free")}
account={undefined}
/>
</BadgeContainer>
</div>
)}
{props.type === "mobile" && (
<div className="flex flex-col gap-4">
@@ -0,0 +1,33 @@
"use client";
import type { Team } from "@/api/team";
import type { ThirdwebClient } from "thirdweb";
import { upload } from "thirdweb/storage";
import { TeamGeneralSettingsPageUI } from "./TeamGeneralSettingsPageUI";
export function TeamGeneralSettingsPage(props: {
team: Team;
client: ThirdwebClient;
}) {
return (
<TeamGeneralSettingsPageUI
team={props.team}
updateTeamImage={async (file) => {
if (file) {
// upload to IPFS
const uri = await upload({
client: props.client,
files: [file],
});
// TODO - Implement updating the account image with uri
console.log(uri);
} else {
// TODO - Implement deleting the account image
}
throw new Error("Not implemented");
}}
/>
);
}
@@ -11,15 +11,16 @@ import { FileInput } from "components/shared/FileInput";
import { useState } from "react";
import { toast } from "sonner";
export function GeneralSettingsPage(props: {
export function TeamGeneralSettingsPageUI(props: {
team: Team;
updateTeamImage: (file: File | undefined) => Promise<void>;
}) {
const hasPermissionToDelete = false; // TODO
return (
<div className="flex flex-col gap-8">
<TeamNameFormControl team={props.team} />
<TeamSlugFormControl team={props.team} />
<TeamAvatarFormControl />
<TeamAvatarFormControl updateTeamImage={props.updateTeamImage} />
<TeamIdCard team={props.team} />
<LeaveTeamCard enabled={false} teamName={props.team.name} />
<DeleteTeamCard
@@ -141,16 +142,14 @@ function TeamSlugFormControl(props: {
);
}
function TeamAvatarFormControl() {
function TeamAvatarFormControl(props: {
updateTeamImage: (file: File | undefined) => Promise<void>;
}) {
const [teamAvatar, setTeamAvatar] = useState<File>(); // TODO: prefill with team avatar
// TODO - implement
const updateTeamAvatarMutation = useMutation({
mutationFn: async (_avatar: File | undefined) => {
// Fake loading
await new Promise((resolve) => setTimeout(resolve, 3000));
console.log("Updating team name to", _avatar);
throw new Error("Not implemented");
await props.updateTeamImage(_avatar);
},
});
@@ -1,61 +1,18 @@
"use client";
import { getTeamBySlug } from "@/api/team";
import { notFound } from "next/navigation";
import { SettingsLayout } from "./SettingsLayout";
import { cn } from "@/lib/utils";
import { usePathname } from "next/navigation";
import { useState } from "react";
import { TeamSettingsSidebar } from "./_components/sidebar/TeamSettingsSidebar";
import { TeamSettingsMobileNav } from "./_components/sidebar/TeamsMobileNav";
import { getTeamSettingsLinks } from "./_components/sidebar/getTeamSettingsLinks";
// on the /~/settings page
// - On desktop: show the general settings as usual
// - On mobile: show the full nav instead of page content and when user clicks on the "General Settings" ( first link ) - hide the full nav and show the page content
export default function Layout(props: {
export default async function Layout(props: {
params: {
team_slug: string;
};
children: React.ReactNode;
}) {
const [_showFullNavOnMobile, setShowFullNavOnMobile] = useState(true);
const pathname = usePathname();
const isSettingsOverview = (pathname || "").endsWith("/~/settings");
const showFullNavOnMobile = _showFullNavOnMobile && isSettingsOverview;
const links = getTeamSettingsLinks(props.params.team_slug);
const activeLink = links.find((link) => pathname === link.href);
const team = await getTeamBySlug(props.params.team_slug);
return (
<div>
{/* Huge page title */}
<div className="border-border border-b py-10">
<div className="container">
<h1 className="font-semibold text-3xl tracking-tight">
Team Settings
</h1>
</div>
</div>
if (!team) {
notFound();
}
<div className="md:hidden">
<TeamSettingsMobileNav
teamSlug={props.params.team_slug}
showFull={showFullNavOnMobile}
setShowFull={setShowFullNavOnMobile}
activeLink={activeLink}
/>
</div>
<div className="container flex grow gap-8 lg:min-h-[900px] [&>*]:py-8 lg:[&>*]:py-10">
<TeamSettingsSidebar teamSlug={props.params.team_slug} />
<div
className={cn(
"grow",
// if showing full nav on mobile - hide the page content
showFullNavOnMobile && "max-sm:hidden",
)}
>
{props.children}
</div>
</div>
</div>
);
return <SettingsLayout team={team}>{props.children}</SettingsLayout>;
}
@@ -6,7 +6,6 @@ import {
BadgeContainer,
mobileViewport,
} from "../../../../../../../stories/utils";
import SettingsLayout from "../layout";
import { InviteSection } from "./InviteSection";
import { ManageMembersSection } from "./ManageMembersSection";
import { TeamMembersSettingsPage } from "./TeamMembersSettingsPage";
@@ -88,18 +87,12 @@ const membersStub: TeamMember[] = [
function Story() {
return (
<div className="min-h-screen bg-background text-foreground">
<SettingsLayout
params={{
team_slug: freeTeam.slug,
}}
>
<TeamMembersSettingsPage
team={proTeam}
userHasEditPermission={true}
members={membersStub}
/>
</SettingsLayout>
<div className="mx-auto w-full max-w-[1100px] px-4 py-6">
<TeamMembersSettingsPage
team={proTeam}
userHasEditPermission={true}
members={membersStub}
/>
<CompVariants />
@@ -110,8 +103,8 @@ function Story() {
function CompVariants() {
return (
<div className="border-border border-t py-10">
<div className="mx-auto max-w-[1000px] px-4">
<div className="mt-20 border-border border-t py-10">
<div className="">
<h2 className="py-4 font-semibold text-3xl"> Invite Variants </h2>
{/* Invite */}
@@ -1,6 +1,8 @@
import { getTeamBySlug } from "@/api/team";
import { getThirdwebClient } from "@/constants/thirdweb.server";
import { notFound } from "next/navigation";
import { GeneralSettingsPage } from "./general/GeneralSettingsPage";
import { getAuthToken } from "../../../../../api/lib/getAuthToken";
import { TeamGeneralSettingsPage } from "./general/TeamGeneralSettingsPage";
export default async function Page(props: {
params: {
@@ -8,10 +10,12 @@ export default async function Page(props: {
};
}) {
const team = await getTeamBySlug(props.params.team_slug);
if (!team) {
const token = getAuthToken();
if (!team || !token) {
notFound();
}
return <GeneralSettingsPage team={team} />;
return (
<TeamGeneralSettingsPage team={team} client={getThirdwebClient(token)} />
);
}
@@ -13,6 +13,8 @@ export function ProjectGeneralSettingsPageForTeams(props: {
const { team_slug, project_slug, apiKey } = props;
const projectSettingsLayout = `/team/${team_slug}/${project_slug}/settings`;
// TODO - add a Project Image form field on this page
return (
<ProjectGeneralSettingsPage
apiKey={apiKey}
@@ -2,6 +2,7 @@
import type { Project } from "@/api/projects";
import type { Team } from "@/api/team";
import { ProjectAvatar } from "@/components/blocks/Avatars/ProjectAvatar";
import { ScrollShadow } from "@/components/ui/ScrollShadow/ScrollShadow";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
@@ -62,9 +63,9 @@ export function ProjectSelectorUI(props: {
href={`/team/${team.slug}/${project.slug}/connect/analytics`}
>
<div className="flex items-center gap-2">
{/* TODO - placeholder for now */}
<div className="size-4 rounded-full border bg-muted" />
<span className="truncate"> {project.name} </span>
{/* TODO - set Image */}
<ProjectAvatar src="" className="size-6" />
<span className="truncate"> {project.name}</span>
</div>
{isSelected && (
<CheckIcon className="h-4 w-4 shrink-0 text-muted-foreground" />
@@ -9,6 +9,7 @@ import {
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
import { ChevronsUpDownIcon } from "lucide-react";
import { useState } from "react";
import { ProjectSelectorUI } from "./ProjectSelectorUI";
@@ -20,6 +21,7 @@ type TeamSwitcherProps = {
teamsAndProjects: Array<{ team: Team; projects: Project[] }>;
focus: "project-selection" | "team-selection";
createProject: () => void;
account: Pick<Account, "email" | "id"> | undefined;
};
export function TeamAndProjectSelectorPopoverButton(props: TeamSwitcherProps) {
@@ -83,6 +85,7 @@ export function TeamAndProjectSelectorPopoverButton(props: TeamSwitcherProps) {
? `/team/${currentTeam.slug}/~/settings/billing`
: undefined
}
account={props.account}
/>
{/* Right */}
@@ -45,7 +45,7 @@ export function TeamHeader(props: {
currentProject: props.currentProject,
currentTeam: props.currentTeam,
teamsAndProjects: props.teamsAndProjects,
email: myAccountQuery.data?.email,
account: myAccountQuery.data,
logout: logout,
connectButton: <CustomConnectWallet />,
createProject: () => setIsCreateProjectDialogOpen(true),
@@ -1,6 +1,6 @@
import { Button } from "@/components/ui/button";
import type { Meta, StoryObj } from "@storybook/react";
import { ThirdwebProvider } from "thirdweb/react";
import { Button } from "../../../../@/components/ui/button";
import { teamsAndProjectsStub } from "../../../../stories/stubs";
import { BadgeContainer, mobileViewport } from "../../../../stories/utils";
import { TeamHeaderDesktopUI, TeamHeaderMobileUI } from "./TeamHeaderUI";
@@ -42,24 +42,27 @@ function Variants(props: {
return (
<ThirdwebProvider>
<div className="flex min-h-screen flex-col gap-6 bg-gray-700 px-4 py-10">
<BadgeContainer label="Team, Free">
<BadgeContainer label="Team Free, Account Loaded">
<Comp
teamsAndProjects={teamsAndProjectsStub}
currentTeam={teamsAndProjectsStub[0].team}
currentProject={undefined}
email="[email protected]"
logout={() => {}}
connectButton={<ConnectButtonStub />}
createProject={() => {}}
account={{
email: "[email protected]",
id: "1",
}}
/>
</BadgeContainer>
<BadgeContainer label="Team, Growth">
<BadgeContainer label="Team Growth, Account Loading">
<Comp
teamsAndProjects={teamsAndProjectsStub}
currentTeam={teamsAndProjectsStub[1].team}
currentProject={undefined}
email={undefined}
account={undefined}
logout={() => {}}
connectButton={<ConnectButtonStub />}
createProject={() => {}}
@@ -71,7 +74,10 @@ function Variants(props: {
teamsAndProjects={teamsAndProjectsStub}
currentTeam={teamsAndProjectsStub[2].team}
currentProject={undefined}
email="[email protected]"
account={{
email: "[email protected]",
id: "foo",
}}
logout={() => {}}
connectButton={<ConnectButtonStub />}
createProject={() => {}}
@@ -83,7 +89,10 @@ function Variants(props: {
teamsAndProjects={teamsAndProjectsStub}
currentTeam={teamsAndProjectsStub[2].team}
currentProject={teamsAndProjectsStub[2].projects[0]}
email="[email protected]"
account={{
email: "[email protected]",
id: "foo",
}}
logout={() => {}}
connectButton={<ConnectButtonStub />}
createProject={() => {}}
@@ -1,6 +1,9 @@
import type { Project } from "@/api/projects";
import type { Team } from "@/api/team";
import { GradientAvatar } from "@/components/blocks/Avatars/GradientAvatar";
import { ProjectAvatar } from "@/components/blocks/Avatars/ProjectAvatar";
import { cn } from "@/lib/utils";
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
import Link from "next/link";
import { SecondaryNav } from "../../../components/Header/SecondaryNav/SecondaryNav";
import { MobileBurgerMenuButton } from "../../../components/MobileBurgerMenuButton";
@@ -16,7 +19,7 @@ export type TeamHeaderCompProps = {
teamsAndProjects: Array<{ team: Team; projects: Project[] }>;
currentProject: Project | undefined;
className?: string;
email: string | undefined;
account: Pick<Account, "email" | "id"> | undefined;
logout: () => void;
connectButton: React.ReactNode;
createProject: () => void;
@@ -45,8 +48,8 @@ export function TeamHeaderDesktopUI(props: TeamHeaderCompProps) {
href={`/team/${currentTeam.slug}`}
className="flex flex-row items-center gap-2 font-normal text-sm"
>
{/* TODO - replace with team image */}
<div className="size-6 rounded-full border border-border bg-muted" />
{/* TODO - set team image */}
<GradientAvatar id={currentTeam.id} src={""} className="size-6" />
<span> {currentTeam.name} </span>
<TeamPlanBadge plan={teamPlan} />
@@ -58,6 +61,7 @@ export function TeamHeaderDesktopUI(props: TeamHeaderCompProps) {
teamsAndProjects={props.teamsAndProjects}
focus="team-selection"
createProject={props.createProject}
account={props.account}
/>
</div>
@@ -67,8 +71,10 @@ export function TeamHeaderDesktopUI(props: TeamHeaderCompProps) {
<div className="flex items-center gap-1">
<Link
href={`/team/${props.currentTeam.slug}/${props.currentProject.slug}`}
className="flex flex-row items-center gap-1 font-semibold text-sm"
className="flex flex-row items-center gap-2 font-semibold text-sm"
>
{/* TODO - set project avatar image */}
<ProjectAvatar src="" className="size-6" />
{props.currentProject.name}
</Link>
@@ -78,6 +84,7 @@ export function TeamHeaderDesktopUI(props: TeamHeaderCompProps) {
teamsAndProjects={props.teamsAndProjects}
focus="project-selection"
createProject={props.createProject}
account={props.account}
/>
</div>
</>
@@ -85,7 +92,7 @@ export function TeamHeaderDesktopUI(props: TeamHeaderCompProps) {
</div>
<SecondaryNav
email={props.email}
account={props.account}
logout={props.logout}
connectButton={props.connectButton}
/>
@@ -110,7 +117,7 @@ export function TeamHeaderMobileUI(props: TeamHeaderCompProps) {
props.className,
)}
>
<div className="flex items-center gap-2">
<div className="flex items-center gap-1.5">
<div className="flex items-center gap-1">
<Link
href={`/team/${currentTeam.slug}`}
@@ -118,8 +125,8 @@ export function TeamHeaderMobileUI(props: TeamHeaderCompProps) {
"flex flex-row items-center gap-2 font-normal text-foreground text-sm",
)}
>
{/* TODO - replace with team image */}
<div className="size-7 rounded-full border border-border bg-muted" />
{/* TODO - set team image */}
<GradientAvatar id={currentTeam.id} src={""} className="size-6" />
{!props.currentProject && (
<span className="font-semibold">{currentTeam.name}</span>
@@ -130,6 +137,7 @@ export function TeamHeaderMobileUI(props: TeamHeaderCompProps) {
currentTeam={props.currentTeam}
teamsAndProjects={props.teamsAndProjects}
upgradeTeamLink={`/team/${currentTeam.slug}/settings`}
account={props.account}
/>
</div>
@@ -139,9 +147,11 @@ export function TeamHeaderMobileUI(props: TeamHeaderCompProps) {
<div className="flex items-center gap-1">
<Link
href={`/team/${props.currentTeam.slug}/${props.currentProject.slug}`}
className="truncate font-semibold text-sm max-lg:max-w-[130px]"
className="flex items-center gap-2 font-semibold text-sm"
>
{props.currentProject.name}
<span className="truncate max-sm:max-w-[130px]">
{props.currentProject.name}
</span>
</Link>
<ProjectSelectorMobileMenuButton
@@ -156,7 +166,7 @@ export function TeamHeaderMobileUI(props: TeamHeaderCompProps) {
</div>
<MobileBurgerMenuButton
email={props.email}
email={props.account?.email}
logout={props.logout}
connectButton={props.connectButton}
/>
@@ -1,10 +1,12 @@
import type { Project } from "@/api/projects";
import type { Team } from "@/api/team";
import { GradientAvatar } from "@/components/blocks/Avatars/GradientAvatar";
import { ScrollShadow } from "@/components/ui/ScrollShadow/ScrollShadow";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
import { CheckIcon, CirclePlusIcon } from "lucide-react";
import Link from "next/link";
import { useState } from "react";
@@ -16,6 +18,7 @@ export function TeamSelectionUI(props: {
currentTeam: Team | undefined;
teamsAndProjects: Array<{ team: Team; projects: Project[] }>;
upgradeTeamLink: string | undefined;
account: Pick<Account, "email" | "id"> | undefined;
}) {
const { setHoveredTeam, currentTeam, teamsAndProjects } = props;
const teamPlan = currentTeam ? getValidTeamPlan(currentTeam) : undefined;
@@ -47,8 +50,12 @@ export function TeamSelectionUI(props: {
asChild
>
<Link href="/account">
{/* TODO account image - placeholder for now */}
<div className="size-4 rounded-full border bg-muted" />
{/* TODO set Image src */}
<GradientAvatar
src={""}
className="size-4"
id={props.account?.id}
/>
My Account
</Link>
</Button>
@@ -79,8 +86,12 @@ export function TeamSelectionUI(props: {
>
<Link href={`/team/${team.slug}`}>
<div className="flex items-center gap-2">
{/* TODO - placeholder for now */}
<div className="size-4 rounded-full border bg-muted" />
{/* TODO set Image src */}
<GradientAvatar
src={""}
className="size-4"
id={team.id}
/>
<span className="truncate"> {team.name} </span>
</div>
@@ -5,6 +5,7 @@ import type { Team } from "@/api/team";
import { DynamicHeight } from "@/components/ui/DynamicHeight";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
import { ChevronsUpDownIcon } from "lucide-react";
import { TeamSelectionUI } from "./TeamSelectionUI";
@@ -12,6 +13,7 @@ type TeamSelectorMobileMenuButtonProps = {
currentTeam: Team | undefined;
teamsAndProjects: Array<{ team: Team; projects: Project[] }>;
upgradeTeamLink: string | undefined;
account: Pick<Account, "email" | "id"> | undefined;
};
export function TeamSelectorMobileMenuButton(
@@ -25,7 +27,7 @@ export function TeamSelectorMobileMenuButton(
<DialogTrigger asChild>
<Button
size="icon"
className="!h-auto w-auto rounded-xl px-1 py-2"
className="!h-auto w-auto rounded-xl px-0.5 py-2"
variant="ghost"
aria-label="Select Project"
>
@@ -43,6 +45,7 @@ export function TeamSelectorMobileMenuButton(
setHoveredTeam={() => {}} // don't care on mobile
teamsAndProjects={teamsAndProjects}
upgradeTeamLink={props.upgradeTeamLink}
account={props.account}
/>
</DynamicHeight>
</DialogContent>
@@ -25,7 +25,6 @@ import {
useBreakpointValue,
useDisclosure,
} from "@chakra-ui/react";
import { AiOutlineWarning } from "@react-icons/all-files/ai/AiOutlineWarning";
import { useQuery } from "@tanstack/react-query";
import { FaucetButton } from "app/(dashboard)/(chain)/[chain_id]/(chainPage)/components/client/FaucetButton";
import { GiftIcon } from "app/(dashboard)/(chain)/[chain_id]/(chainPage)/components/icons/GiftIcon";
@@ -36,7 +35,7 @@ import type {
import { getSDKTheme } from "app/components/sdk-component-theme";
import { LOCAL_NODE_PKEY } from "constants/misc";
import { useTrack } from "hooks/analytics/useTrack";
import { ExternalLinkIcon } from "lucide-react";
import { ExternalLinkIcon, TriangleAlertIcon } from "lucide-react";
import { useTheme } from "next-themes";
import Link from "next/link";
import { forwardRef, useCallback, useMemo, useRef, useState } from "react";
@@ -448,7 +447,7 @@ const MismatchNotice: React.FC<{
<div className="flex flex-col gap-4">
<Heading size="label.lg">
<div className="flex flex-row items-center gap-2">
<Icon boxSize={6} as={AiOutlineWarning} />
<TriangleAlertIcon className="size-6" />
<span>Network Mismatch</span>
</div>
</Heading>
@@ -1,4 +1,4 @@
import { Flex, LinkBox, LinkOverlay, Stack } from "@chakra-ui/react";
import { Flex, LinkBox, LinkOverlay } from "@chakra-ui/react";
import { ChakraNextImage } from "components/Image";
import { Card, Heading, Text } from "tw-components";
@@ -34,14 +34,7 @@ export const CommunityCard: React.FC<CommunityCardProps> = ({
_hover={{ textDecor: "none" }}
as={LinkBox}
>
<Stack
align="center"
justify="center"
w="full"
bg="#181818"
borderTopRadius="xl"
h="11.5rem"
>
<div className="flex h-44 w-full flex-col items-center justify-center gap-2 rounded-t-xl bg-[#181818]">
<ChakraNextImage
pointerEvents="none"
alt={title}
@@ -52,7 +45,7 @@ export const CommunityCard: React.FC<CommunityCardProps> = ({
width={90}
height={90}
/>
</Stack>
</div>
<Flex
w="full"
@@ -1,26 +1,26 @@
import { Icon } from "@chakra-ui/react";
import { SiJavascript } from "@react-icons/all-files/si/SiJavascript";
import { SiReact } from "@react-icons/all-files/si/SiReact";
import { SiUnity } from "@react-icons/all-files/si/SiUnity";
import { JavaScriptIcon } from "components/icons/brand-icons/JavaScriptIcon";
import { ReactIcon } from "components/icons/brand-icons/ReactIcon";
import { UnityIcon } from "components/icons/brand-icons/UnityIcon";
import { useTrack } from "hooks/analytics/useTrack";
import type { Dispatch, ReactNode, SetStateAction } from "react";
import { Button } from "tw-components";
const LOGO_OPTIONS = {
javascript: {
icon: SiJavascript,
icon: JavaScriptIcon,
fill: "yellow",
},
react: {
icon: SiReact,
icon: ReactIcon,
fill: "#61dafb",
},
"react-native": {
icon: SiReact,
icon: ReactIcon,
fill: "#61dafb",
},
unity: {
icon: SiUnity,
icon: UnityIcon,
fill: "#ffffff",
},
} as const;
@@ -1,79 +0,0 @@
import { Flex, Link, useDisclosure } from "@chakra-ui/react";
import { THIRDWEB_DOMAIN } from "constants/urls";
import { useAllChainsData } from "hooks/chains/allChains";
import { Drawer, Heading, Text } from "tw-components";
interface AddressesModalProps {
chainAddressRecord: Record<string, string>;
buttonTitle: string;
modalTitle: string;
}
export const AddressesModal: React.FC<AddressesModalProps> = ({
chainAddressRecord,
buttonTitle,
modalTitle,
}) => {
const { idToChain } = useAllChainsData();
const { isOpen, onOpen, onClose } = useDisclosure();
return (
<>
<Text size="body.md" lineHeight={1.2}>
<Link
onClick={onOpen}
_dark={{
color: "blue.400",
_hover: { color: "blue.500" },
}}
_light={{
color: "blue.500",
_hover: { color: "blue.500" },
}}
>
{buttonTitle}
</Link>
</Text>
<Drawer
size="lg"
allowPinchZoom
isOpen={isOpen}
onClose={onClose}
header={{
children: (
<Heading as="h3" size="title.sm">
{modalTitle}
</Heading>
),
}}
>
<Flex flexDir="column" gap={{ base: 2, md: 1 }}>
{Object.entries(chainAddressRecord).map(([chainId, address]) =>
!address ? null : (
<Flex
key={chainId}
flexDir={{ base: "column", md: "row" }}
alignItems={{ base: "auto", md: "center" }}
justifyContent="space-between"
>
<Text size="body.md">
{idToChain.get(Number.parseInt(chainId))?.name}
</Text>
<Link
href={`${THIRDWEB_DOMAIN}/${chainId}/${address}`}
isExternal
color="blue.500"
>
<Text color="inherit" fontFamily="mono">
{address}
</Text>
</Link>
</Flex>
),
)}
</Flex>
</Drawer>
</>
);
};
@@ -29,7 +29,6 @@ import {
usePublishedContractFunctions,
} from "../hooks";
import { PublisherHeader } from "../publisher/publisher-header";
import { AddressesModal } from "./addresses-modal";
import { MarkdownRenderer } from "./markdown-renderer";
interface ExtendedPublishedContract extends PublishedContractWithVersion {
@@ -235,23 +234,6 @@ export const PublishedContract: React.FC<PublishedContractProps> = ({
: "Proxy"}{" "}
Enabled
</Heading>
{implementationAddresses &&
hasImplementationAddresses ? (
<AddressesModal
chainAddressRecord={implementationAddresses}
buttonTitle="Implementations"
modalTitle="Implementation Addresses"
/>
) : null}
{factoryAddresses &&
hasFactoryAddresses &&
publishedContract?.isDeployableViaFactory ? (
<AddressesModal
chainAddressRecord={factoryAddresses}
buttonTitle="Factories"
modalTitle="Factory Addresses"
/>
) : null}
</Flex>
</Flex>
</ListItem>
@@ -1,12 +1,12 @@
import { ButtonGroup, type ButtonGroupProps, Icon } from "@chakra-ui/react";
import { SiDiscord } from "@react-icons/all-files/si/SiDiscord";
import { SiFacebook } from "@react-icons/all-files/si/SiFacebook";
import { SiGithub } from "@react-icons/all-files/si/SiGithub";
import { SiLinkedin } from "@react-icons/all-files/si/SiLinkedin";
import { SiMedium } from "@react-icons/all-files/si/SiMedium";
import { SiReddit } from "@react-icons/all-files/si/SiReddit";
import { SiTelegram } from "@react-icons/all-files/si/SiTelegram";
import { SiTwitter } from "@react-icons/all-files/si/SiTwitter";
import { DiscordIcon } from "components/icons/brand-icons/DiscordIcon";
import { GithubIcon } from "components/icons/brand-icons/GithubIcon";
import { LinkedInIcon } from "components/icons/brand-icons/LinkedinIcon";
import { MediumIcon } from "components/icons/brand-icons/MediumIcon";
import { MetaIcon } from "components/icons/brand-icons/MetaIcon";
import { RedditIcon } from "components/icons/brand-icons/RedditIcon";
import { TelegramIcon } from "components/icons/brand-icons/TelegramIcon";
import { XIcon } from "components/icons/brand-icons/XIcon";
import type { ProfileMetadata } from "constants/schemas";
import { FiGlobe } from "react-icons/fi";
import { LinkButton, TrackedIconButton } from "tw-components";
@@ -37,7 +37,7 @@ export const PublisherSocials: React.FC<PublisherSocialsProps> = ({
}
bg="transparent"
aria-label="twitter"
icon={<Icon as={SiTwitter} />}
icon={<XIcon className="size-4" />}
category={TRACKING_CATEGORY}
label="twitter"
/>
@@ -50,7 +50,7 @@ export const PublisherSocials: React.FC<PublisherSocialsProps> = ({
href={publisherProfile.discord}
bg="transparent"
aria-label="discord"
icon={<Icon as={SiDiscord} />}
icon={<DiscordIcon className="size-4" />}
category={TRACKING_CATEGORY}
label="discord"
/>
@@ -67,7 +67,7 @@ export const PublisherSocials: React.FC<PublisherSocialsProps> = ({
}
bg="transparent"
aria-label="github"
icon={<Icon as={SiGithub} />}
icon={<GithubIcon className="size-4" />}
category={TRACKING_CATEGORY}
label="github"
/>
@@ -98,7 +98,7 @@ export const PublisherSocials: React.FC<PublisherSocialsProps> = ({
}
bg="transparent"
aria-label="medium"
icon={<Icon as={SiMedium} />}
icon={<MediumIcon className="size-4" />}
category={TRACKING_CATEGORY}
label="medium"
/>
@@ -115,7 +115,7 @@ export const PublisherSocials: React.FC<PublisherSocialsProps> = ({
}
bg="transparent"
aria-label="telegram"
icon={<Icon as={SiTelegram} />}
icon={<TelegramIcon className="size-4" />}
category={TRACKING_CATEGORY}
label="telegram"
/>
@@ -128,7 +128,7 @@ export const PublisherSocials: React.FC<PublisherSocialsProps> = ({
href={publisherProfile.facebook}
bg="transparent"
aria-label="facebook"
icon={<Icon as={SiFacebook} />}
icon={<MetaIcon className="size-4" />}
category={TRACKING_CATEGORY}
label="facebook"
/>
@@ -145,7 +145,7 @@ export const PublisherSocials: React.FC<PublisherSocialsProps> = ({
}
bg="transparent"
aria-label="reddit"
icon={<Icon as={SiReddit} />}
icon={<RedditIcon className="size-4" />}
category={TRACKING_CATEGORY}
label="reddit"
/>
@@ -162,7 +162,7 @@ export const PublisherSocials: React.FC<PublisherSocialsProps> = ({
}
bg="transparent"
aria-label="linkedin"
icon={<Icon as={SiLinkedin} />}
icon={<LinkedInIcon className="size-4" />}
category={TRACKING_CATEGORY}
label="linkedin"
/>
@@ -9,10 +9,10 @@ import {
Textarea,
useDisclosure,
} from "@chakra-ui/react";
import { SiDiscord } from "@react-icons/all-files/si/SiDiscord";
import { SiGithub } from "@react-icons/all-files/si/SiGithub";
import { SiTwitter } from "@react-icons/all-files/si/SiTwitter";
import { useQueryClient } from "@tanstack/react-query";
import { DiscordIcon } from "components/icons/brand-icons/DiscordIcon";
import { GithubIcon } from "components/icons/brand-icons/GithubIcon";
import { XIcon } from "components/icons/brand-icons/XIcon";
import { FileInput } from "components/shared/FileInput";
import {
DASHBOARD_ENGINE_RELAYER_URL,
@@ -214,7 +214,7 @@ export const EditProfile: React.FC<EditProfileProps> = ({
<FormControl isInvalid={!!errors.github}>
<FormLabel>
<div className="flex flex-row gap-2">
<Icon as={SiGithub} boxSize={4} />
<GithubIcon className="size-4" />
GitHub
</div>
</FormLabel>
@@ -227,7 +227,7 @@ export const EditProfile: React.FC<EditProfileProps> = ({
<FormControl isInvalid={!!errors.twitter}>
<FormLabel>
<div className="flex flex-row gap-2">
<Icon as={SiTwitter} boxSize={4} />
<XIcon className="size-4" />
Twitter
</div>
</FormLabel>
@@ -253,7 +253,7 @@ export const EditProfile: React.FC<EditProfileProps> = ({
<FormControl isInvalid={!!errors.discord}>
<FormLabel>
<div className="flex flex-row gap-2">
<Icon as={SiDiscord} boxSize={4} />
<DiscordIcon className="size-4" />
Discord
</div>
</FormLabel>
@@ -69,7 +69,7 @@ export const PropertiesFormControl = <
}, [fields, append]);
return (
<Stack spacing={4}>
<div className="flex flex-col gap-4">
<Flex justify="space-between" align="center" direction="row">
<FormLabel m={0}>Attributes</FormLabel>
<Button
@@ -93,7 +93,7 @@ export const PropertiesFormControl = <
const isInvalid = !!(keyError || valueError);
return (
<Stack key={field.id} align="center" direction="row">
<div className="flex flex-row items-center gap-2" key={field.id}>
<FormControl
isInvalid={isInvalid}
as={Stack}
@@ -177,10 +177,10 @@ export const PropertiesFormControl = <
size="xs"
icon={<Icon as={FiX} />}
/>
</Stack>
</div>
);
})}
<Stack direction="row">
<div className="flex flex-row gap-2">
<Button
leftIcon={<Icon as={FiPlus} />}
colorScheme="purple"
@@ -192,7 +192,7 @@ export const PropertiesFormControl = <
>
Add Row
</Button>
</Stack>
</Stack>
</div>
</div>
);
};
@@ -1,8 +1,8 @@
import { ButtonGroup, Flex, Icon, Stack } from "@chakra-ui/react";
import { SiJavascript } from "@react-icons/all-files/si/SiJavascript";
import { SiReact } from "@react-icons/all-files/si/SiReact";
import { SiTypescript } from "@react-icons/all-files/si/SiTypescript";
import { SiUnity } from "@react-icons/all-files/si/SiUnity";
import { ButtonGroup, Flex, Icon } from "@chakra-ui/react";
import { JavaScriptIcon } from "components/icons/brand-icons/JavaScriptIcon";
import { ReactIcon } from "components/icons/brand-icons/ReactIcon";
import { TypeScriptIcon } from "components/icons/brand-icons/TypeScriptIcon";
import { UnityIcon } from "components/icons/brand-icons/UnityIcon";
import { type Dispatch, type SetStateAction, useMemo } from "react";
import { Button, CodeBlock } from "tw-components";
import type { ComponentWithChildren } from "types/component-with-children";
@@ -16,31 +16,31 @@ const Environments: SupportedEnvironment[] = [
{
environment: "javascript",
title: "JavaScript",
icon: SiJavascript,
icon: JavaScriptIcon,
colorScheme: "yellow",
},
{
environment: "typescript",
title: "TypeScript",
icon: SiTypescript,
icon: TypeScriptIcon,
colorScheme: "blue",
},
{
environment: "react",
title: "React",
icon: SiReact,
icon: ReactIcon,
colorScheme: "purple",
},
{
environment: "react-native",
title: "React Native",
icon: SiReact,
icon: ReactIcon,
colorScheme: "purple",
},
{
environment: "unity",
title: "Unity",
icon: SiUnity,
icon: UnityIcon,
colorScheme: "purple",
},
];
@@ -88,7 +88,7 @@ export const CodeSegment: React.FC<CodeSegmentProps> = ({
);
return (
<Stack spacing={2}>
<div className="flex flex-col gap-2">
{!hideTabs && (
<Flex justify="space-between" align="flex-end">
<Flex direction="column" gap={4}>
@@ -133,7 +133,7 @@ export const CodeSegment: React.FC<CodeSegmentProps> = ({
/>
</>
)}
</Stack>
</div>
);
};
@@ -63,7 +63,7 @@ export const EngineCorsConfig: React.FC<EngineCorsConfigProps> = ({
</Flex>
<Textarea
placeholder="https://example.com\nhttp://localhost:3000"
placeholder={"https://example.com\nhttp://localhost:3000"}
rows={4}
{...form.register("raw")}
/>
@@ -12,7 +12,6 @@ import {
ModalFooter,
ModalHeader,
ModalOverlay,
Stack,
Tooltip,
useDisclosure,
} from "@chakra-ui/react";
@@ -171,7 +170,7 @@ export const WebhooksTable: React.FC<WebhooksTableProps> = ({
<ModalCloseButton />
<ModalBody>
{webhookToRevoke && (
<Stack gap={4}>
<div className="flex flex-col gap-4">
<Text>Are you sure you want to delete this webook?</Text>
<FormControl>
<FormLabel>Name</FormLabel>
@@ -190,7 +189,7 @@ export const WebhooksTable: React.FC<WebhooksTableProps> = ({
)}
</Text>
</FormControl>
</Stack>
</div>
)}
</ModalBody>
@@ -23,7 +23,6 @@ import {
Radio,
RadioGroup,
Spinner,
Stack,
type UseDisclosureReturn,
useDisclosure,
} from "@chakra-ui/react";
@@ -185,7 +184,7 @@ const ModalBodyInputContract = ({
return (
<>
<ModalBody>
<Stack spacing={4}>
<div className="flex flex-col gap-4">
<Text>
Add a contract subscription to process real-time onchain data.
</Text>
@@ -252,7 +251,7 @@ const ModalBodyInputContract = ({
{form.getFieldState("webhookUrl", form.formState).error?.message}
</FormErrorMessage>
</FormControl>
</Stack>
</div>
</ModalBody>
<ModalFooter>
@@ -304,7 +303,7 @@ const ModalBodyInputData = ({
return (
<>
<ModalBody>
<Stack spacing={4}>
<div className="flex flex-col gap-4">
<Text>
Select the data type to process.
<br />
@@ -316,7 +315,7 @@ const ModalBodyInputData = ({
<FormControl>
<FormLabel>Processed Data</FormLabel>
<Stack>
<div className="flex flex-col gap-2">
<Checkbox
{...form.register("processEventLogs")}
checked={form.getValues("processEventLogs")}
@@ -334,7 +333,7 @@ const ModalBodyInputData = ({
</Checkbox>
{/* Shows all/specific events if processing event logs */}
<Collapse in={processEventLogsDisclosure.isOpen}>
<Stack px={4}>
<div className="flex flex-col gap-2 px-4">
<RadioGroup
defaultValue="false"
onChange={(val: "false" | "true") => {
@@ -346,7 +345,7 @@ const ModalBodyInputData = ({
}
}}
>
<Stack>
<div className="flex flex-col gap-2">
<Radio value="false">
<Text>All events</Text>
</Radio>
@@ -368,9 +367,9 @@ const ModalBodyInputData = ({
}
/>
</Collapse>
</Stack>
</div>
</RadioGroup>
</Stack>
</div>
</Collapse>
<Checkbox
@@ -390,7 +389,7 @@ const ModalBodyInputData = ({
</Checkbox>
{/* Shows all/specific functions if processing transaction receipts */}
<Collapse in={processTransactionReceiptsDisclosure.isOpen}>
<Stack px={4}>
<div className="flex flex-col gap-2 px-4">
<RadioGroup
defaultValue="false"
onChange={(val: "false" | "true") => {
@@ -402,7 +401,7 @@ const ModalBodyInputData = ({
}
}}
>
<Stack>
<div className="flex flex-col gap-2">
<Radio value="false">
<Text>All functions</Text>
</Radio>
@@ -424,13 +423,13 @@ const ModalBodyInputData = ({
}
/>
</Collapse>
</Stack>
</div>
</RadioGroup>
</Stack>
</div>
</Collapse>
</Stack>
</div>
</FormControl>
</Stack>
</div>
</ModalBody>
<ModalFooter as={Flex} gap={3}>
@@ -537,7 +536,7 @@ const FilterSelector = ({
Cannot resolve the contract definition. Filters are unavailable.
</Text>
) : (
<Stack maxH={300} overflowY="auto">
<div className="flex max-h-[300px] flex-col gap-2 overflow-y-auto">
<CheckboxGroup
value={filter}
onChange={(selected: string[]) => setFilter(selected)}
@@ -548,7 +547,7 @@ const FilterSelector = ({
</Checkbox>
))}
</CheckboxGroup>
</Stack>
</div>
)}
</Card>
);
@@ -378,7 +378,7 @@ const RemoveModal = ({
<ModalHeader>Remove Contract Subscription</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Stack spacing={4}>
<div className="flex flex-col gap-4">
<Text>
This action will delete all stored data for this contract
subscription.
@@ -429,7 +429,7 @@ const RemoveModal = ({
)}
</FormControl>
</Card>
</Stack>
</div>
</ModalBody>
<ModalFooter as={Flex} gap={3}>
<Button type="button" onClick={disclosure.onClose} variant="ghost">
@@ -2,7 +2,7 @@
import { Button } from "@/components/ui/button";
import type { EngineTier } from "@3rdweb-sdk/react/hooks/useEngine";
import { Flex, Spacer, Stack } from "@chakra-ui/react";
import { Flex, Spacer } from "@chakra-ui/react";
import { CheckIcon } from "lucide-react";
interface EngineTierCardConfig {
@@ -97,7 +97,7 @@ export const EngineTierCard = ({
</Flex>
{/* Features */}
<Stack spacing={3}>
<div className="flex flex-col gap-3">
{previousTier && (
<p>
<span className="font-semibold"> All of {previousTier}</span>, plus:
@@ -109,7 +109,7 @@ export const EngineTierCard = ({
<p className="text-muted-foreground text-sm">{feature}</p>
</Flex>
))}
</Stack>
</div>
<Spacer />
@@ -21,7 +21,7 @@ import {
useEngineRemoveCloudHosted,
useEngineRemoveFromDashboard,
} from "@3rdweb-sdk/react/hooks/useEngine";
import { FormControl, Radio, RadioGroup, Stack } from "@chakra-ui/react";
import { FormControl, Radio, RadioGroup } from "@chakra-ui/react";
import { DialogDescription } from "@radix-ui/react-dialog";
import { createColumnHelper } from "@tanstack/react-table";
import { TWTable } from "components/shared/TWTable";
@@ -440,7 +440,7 @@ function CancelSubscriptionModalContent(props: {
Please share any feedback to help us improve
</FormLabel>
<RadioGroup>
<Stack>
<div className="flex flex-col gap-2">
<Radio
value="USING_SELF_HOSTED"
{...form.register("reason", { required: true })}
@@ -465,7 +465,7 @@ function CancelSubscriptionModalContent(props: {
>
<span className="text-sm"> Other </span>
</Radio>
</Stack>
</div>
</RadioGroup>
</FormControl>
@@ -21,7 +21,6 @@ import {
ModalHeader,
ModalOverlay,
Select,
Stack,
type UseDisclosureReturn,
useDisclosure,
} from "@chakra-ui/react";
@@ -273,7 +272,7 @@ const EditModal = ({
<ModalHeader>Update Backend Wallet</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Stack spacing={4}>
<div className="flex flex-col gap-4">
<FormControl>
<FormLabel>Wallet Address</FormLabel>
<Text>{backendWallet.address}</Text>
@@ -287,7 +286,7 @@ const EditModal = ({
placeholder="Enter a description for this backend wallet"
/>
</FormControl>
</Stack>
</div>
</ModalBody>
<ModalFooter as={Flex} gap={3}>
@@ -424,7 +423,7 @@ const SendFundsModal = ({
<ModalHeader>Send Funds</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Stack spacing={4}>
<div className="flex flex-col gap-4">
<FormControl>
<FormLabel>From</FormLabel>
<Text fontFamily="mono">{fromWallet.address}</Text>
@@ -495,7 +494,7 @@ const SendFundsModal = ({
<Text>{chain?.name}</Text>
</Flex>
</FormControl>
</Stack>
</div>
</ModalBody>
<ModalFooter as={Flex} gap={3}>
@@ -14,7 +14,6 @@ import {
ModalFooter,
ModalHeader,
ModalOverlay,
Stack,
useDisclosure,
} from "@chakra-ui/react";
import { useTrack } from "hooks/analytics/useTrack";
@@ -84,7 +83,7 @@ export const CreateBackendWalletButton: React.FC<
<ModalHeader>Create {walletType} wallet</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Stack spacing={4}>
<div className="flex flex-col gap-4">
<FormControl>
<FormLabel>Wallet Type</FormLabel>
<Text>{walletType}</Text>
@@ -97,7 +96,7 @@ export const CreateBackendWalletButton: React.FC<
{...form.register("label")}
/>
</FormControl>
</Stack>
</div>
</ModalBody>
<ModalFooter as={Flex} gap={3}>
@@ -10,7 +10,6 @@ import {
ModalFooter,
ModalHeader,
ModalOverlay,
Stack,
Step,
StepIndicator,
StepSeparator,
@@ -137,14 +136,14 @@ export const TransactionTimeline = ({
<StepStatus complete={<FiCheck />} active={<FiCheck />} />
</StepIndicator>
<Flex justify="space-between" w="full" mt={-1}>
<Stack>
<div className="flex flex-col gap-2">
{isFilled ? (
<Text>{step.step}</Text>
) : (
<Text color="gray.600">{step.step}</Text>
)}
{step.cta}
</Stack>
</div>
{step.date && (
<Text fontSize="small">
{prettyPrintTimestamp(step.date, index === 0)}
@@ -219,7 +218,7 @@ const CancelTransactionButton = ({
<ModalContent className="!bg-background rounded-lg border border-border">
<ModalHeader>Cancel Transaction</ModalHeader>
<ModalBody>
<Stack gap={4}>
<div className="flex flex-col gap-4">
<Text>Are you sure you want to cancel this transaction?</Text>
<FormControl>
<FormLabel>Queue ID</FormLabel>
@@ -255,7 +254,7 @@ const CancelTransactionButton = ({
If this transaction is already submitted, it may complete before
the cancellation is submitted.
</Text>
</Stack>
</div>
</ModalBody>
<ModalFooter as={Flex} gap={3}>
@@ -9,7 +9,6 @@ import {
DrawerHeader,
Flex,
FormControl,
Stack,
Tooltip,
type UseDisclosureReturn,
useDisclosure,
@@ -373,7 +372,7 @@ const TransactionDetailsDrawer = ({
),
}}
>
<Stack spacing={4}>
<div className="flex flex-col gap-4">
<FormControl>
<FormLabel>Queue ID</FormLabel>
<Text>{transaction.queueId}</Text>
@@ -524,7 +523,7 @@ const TransactionDetailsDrawer = ({
</FormControl>
<Collapse in={advancedTxDetailsDisclosure.isOpen}>
<Stack spacing={4}>
<div className="flex flex-col gap-4">
{transaction.nonce && (
<FormControl>
<div className="flex flex-row">
@@ -588,7 +587,7 @@ const TransactionDetailsDrawer = ({
</LinkButton>
</FormControl>
)}
</Stack>
</div>
</Collapse>
<Button
@@ -604,7 +603,7 @@ const TransactionDetailsDrawer = ({
</Button>
</>
)}
</Stack>
</div>
</Drawer>
);
};
@@ -15,7 +15,6 @@ import {
ModalFooter,
ModalHeader,
ModalOverlay,
Stack,
type UseDisclosureReturn,
useDisclosure,
} from "@chakra-ui/react";
@@ -190,7 +189,7 @@ const EditModal = ({
<ModalHeader>Update Access Token</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Stack spacing={4}>
<div className="flex flex-col gap-4">
<FormControl>
<FormLabel>Access Token</FormLabel>
<Text>{accessToken.tokenMask}</Text>
@@ -204,7 +203,7 @@ const EditModal = ({
placeholder="Enter a description for this access token"
/>
</FormControl>
</Stack>
</div>
</ModalBody>
<ModalFooter as={Flex} gap={3}>
@@ -273,7 +272,7 @@ const RemoveModal = ({
<ModalHeader>Delete Access Token</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Stack spacing={4}>
<div className="flex flex-col gap-4">
<Text>Are you sure you want to delete this access token?</Text>
<FormControl>
<FormLabel>Access Token</FormLabel>
@@ -283,7 +282,7 @@ const RemoveModal = ({
<FormLabel>Label</FormLabel>
<Text>{accessToken.label ?? <em>N/A</em>}</Text>
</FormControl>
</Stack>
</div>
</ModalBody>
<ModalFooter as={Flex} gap={3}>
@@ -8,7 +8,6 @@ import {
ModalFooter,
ModalHeader,
ModalOverlay,
Stack,
useDisclosure,
} from "@chakra-ui/react";
import { useTrack } from "hooks/analytics/useTrack";
@@ -83,7 +82,7 @@ export const AddAccessTokenButton: React.FC<AddAccessTokenButtonProps> = ({
<ModalContent className="!bg-background rounded-lg border border-border">
<ModalHeader>Access token</ModalHeader>
<ModalBody as={Flex} flexDir="column" gap={4}>
<Stack spacing={4}>
<div className="flex flex-col gap-4">
<CodeBlock code={accessToken} />
<Text color="red.500">
This access token will not be shown again.
@@ -94,7 +93,7 @@ export const AddAccessTokenButton: React.FC<AddAccessTokenButtonProps> = ({
>
I have securely stored this access token.
</Checkbox>
</Stack>
</div>
</ModalBody>
<ModalFooter as={Flex} gap={3}>
@@ -14,7 +14,6 @@ import {
ModalFooter,
ModalHeader,
ModalOverlay,
Stack,
useDisclosure,
} from "@chakra-ui/react";
import { useTrack } from "hooks/analytics/useTrack";
@@ -93,7 +92,7 @@ export const AddAdminButton: React.FC<AddAdminButtonProps> = ({
<ModalHeader>Add Admin</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Stack spacing={4}>
<div className="flex flex-col gap-4">
<FormControl isRequired>
<FormLabel>Wallet Address</FormLabel>
<Input
@@ -110,7 +109,7 @@ export const AddAdminButton: React.FC<AddAdminButtonProps> = ({
{...form.register("label")}
/>
</FormControl>
</Stack>
</div>
</ModalBody>
<ModalFooter as={Flex} gap={3}>
@@ -15,7 +15,6 @@ import {
ModalHeader,
ModalOverlay,
Select,
Stack,
Textarea,
useDisclosure,
} from "@chakra-ui/react";
@@ -129,7 +128,7 @@ export const AddKeypairButton: React.FC<AddKeypairButtonProps> = ({
<ModalHeader>Add Public Key</ModalHeader>
<ModalBody as={Flex} flexDir="column" gap={8}>
<FormControl>
<Stack>
<div className="flex flex-col gap-2">
<div className="flex flex-row gap-2">
<Text>Create a private key using:</Text>
<Select
@@ -179,7 +178,7 @@ export const AddKeypairButton: React.FC<AddKeypairButtonProps> = ({
code="cat public.key"
language="solidity"
/>
</Stack>
</div>
</FormControl>
<FormControl isRequired>
@@ -206,14 +205,14 @@ export const AddKeypairButton: React.FC<AddKeypairButtonProps> = ({
</FormControl>
<Alert variant="left-accent">
<Stack>
<div className="flex flex-col gap-2">
<Text>
<strong>Keep your private key secure!</strong>
<br />
Your backend will sign access tokens with this private key
which Engine verifies with this public key.
</Text>
</Stack>
</div>
</Alert>
</ModalBody>
@@ -16,7 +16,6 @@ import {
ModalFooter,
ModalHeader,
ModalOverlay,
Stack,
type UseDisclosureReturn,
useDisclosure,
} from "@chakra-ui/react";
@@ -178,7 +177,7 @@ const EditModal = ({
<ModalHeader>Update Admin</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Stack spacing={4}>
<div className="flex flex-col gap-4">
<FormControl>
<FormLabel>Wallet Address</FormLabel>
<Text>{admin.walletAddress}</Text>
@@ -192,7 +191,7 @@ const EditModal = ({
placeholder="Enter a description for this admin"
/>
</FormControl>
</Stack>
</div>
</ModalBody>
<ModalFooter as={Flex} gap={3}>
@@ -261,7 +260,7 @@ const RemoveModal = ({
<ModalHeader>Remove Admin</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Stack spacing={4}>
<div className="flex flex-col gap-4">
<Text>Are you sure you want to remove this admin?</Text>
<FormControl>
<FormLabel>Wallet Address</FormLabel>
@@ -271,7 +270,7 @@ const RemoveModal = ({
<FormLabel>Label</FormLabel>
<Text>{admin.label ?? <em>N/A</em>}</Text>
</FormControl>
</Stack>
</div>
</ModalBody>
<ModalFooter as={Flex} gap={3}>
@@ -13,7 +13,6 @@ import {
ModalFooter,
ModalHeader,
ModalOverlay,
Stack,
type UseDisclosureReturn,
useDisclosure,
} from "@chakra-ui/react";
@@ -162,7 +161,7 @@ const RemoveModal = ({
<ModalHeader>Remove Keypair</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Stack spacing={4}>
<div className="flex flex-col gap-4">
<Text>Are you sure you want to remove this keypair?</Text>
<Text>
Access tokens signed by the private key for this keypair will no
@@ -187,7 +186,7 @@ const RemoveModal = ({
</Text>
</Flex>
</FormControl>
</Stack>
</div>
</ModalBody>
<ModalFooter as={Flex} gap={3}>
@@ -17,7 +17,6 @@ import {
ModalHeader,
ModalOverlay,
Select,
Stack,
Textarea,
type UseDisclosureReturn,
useDisclosure,
@@ -269,7 +268,7 @@ const EditModal = ({
<ModalHeader>Update Relayer</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Stack spacing={4}>
<div className="flex flex-col gap-4">
<FormControl>
<FormLabel>Chain</FormLabel>
<NetworkDropdown
@@ -316,7 +315,7 @@ const EditModal = ({
/>
<FormHelperText>Allow all forwarders if omitted.</FormHelperText>
</FormControl>
</Stack>
</div>
</ModalBody>
<ModalFooter as={Flex} gap={3}>
@@ -384,7 +383,7 @@ const RemoveModal = ({
<ModalHeader>Remove Relayer</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Stack spacing={4}>
<div className="flex flex-col gap-4">
<Text>Are you sure you want to remove this relayer?</Text>
<FormControl>
<FormLabel>Chain</FormLabel>
@@ -408,7 +407,7 @@ const RemoveModal = ({
<FormLabel>Label</FormLabel>
<Text>{relayer.name ?? <em>N/A</em>}</Text>
</FormControl>
</Stack>
</div>
</ModalBody>
<ModalFooter as={Flex} gap={3}>
<Button type="button" onClick={disclosure.onClose} variant="ghost">
@@ -6,7 +6,7 @@ import {
useEngineQueueMetrics,
useEngineSystemMetrics,
} from "@3rdweb-sdk/react/hooks/useEngine";
import { Flex, Icon, Stack } from "@chakra-ui/react";
import { Flex, Icon } from "@chakra-ui/react";
import { FaChartArea } from "react-icons/fa";
import { IoIosInformationCircleOutline } from "react-icons/io";
import { Card, Heading, Text, TrackedLink } from "tw-components";
@@ -28,7 +28,7 @@ export const EngineSystemMetrics: React.FC<EngineStatusProps> = ({
if (!systemMetricsQuery.data || systemMetricsQuery.isError) {
systemMetricsPanel = (
<Card p={8}>
<Stack spacing={4}>
<div className="flex flex-col gap-4">
<Flex gap={2} align="center">
<Icon as={IoIosInformationCircleOutline} />
<Heading size="title.xs">
@@ -48,13 +48,13 @@ export const EngineSystemMetrics: React.FC<EngineStatusProps> = ({
</TrackedLink>{" "}
to view these metrics.
</Text>
</Stack>
</div>
</Card>
);
} else {
systemMetricsPanel = (
<Card p={16}>
<Stack spacing={4}>
<div className="flex flex-col gap-4">
<Flex gap={2} align="center" pb={-2}>
<Icon as={FaChartArea} />
<Heading size="title.md">System Metrics</Heading>
@@ -65,7 +65,7 @@ export const EngineSystemMetrics: React.FC<EngineStatusProps> = ({
</div>
<StatusCodes datapoints={systemMetricsQuery.data.data.statusCodes} />
<ErrorRate datapoints={systemMetricsQuery.data.data.errorRate} />
</Stack>
</div>
</Card>
);
}
@@ -93,7 +93,7 @@ export const EngineSystemMetrics: React.FC<EngineStatusProps> = ({
queueMetricsPanel = (
<Card p={8}>
<Stack spacing={6}>
<div className="flex flex-col gap-6">
<Flex gap={2} align="center">
<Heading size="title.md">Queue Metrics</Heading>
</Flex>
@@ -127,7 +127,7 @@ export const EngineSystemMetrics: React.FC<EngineStatusProps> = ({
</div>
)}
</div>
</Stack>
</div>
</Card>
);
}
@@ -1,10 +1,10 @@
import { SiDiscord } from "@react-icons/all-files/si/SiDiscord";
import { SiGithub } from "@react-icons/all-files/si/SiGithub";
import { SiInstagram } from "@react-icons/all-files/si/SiInstagram";
import { SiLinkedin } from "@react-icons/all-files/si/SiLinkedin";
import { SiTiktok } from "@react-icons/all-files/si/SiTiktok";
import { SiTwitter } from "@react-icons/all-files/si/SiTwitter";
import { SiYoutube } from "@react-icons/all-files/si/SiYoutube";
import { DiscordIcon } from "components/icons/brand-icons/DiscordIcon";
import { GithubIcon } from "components/icons/brand-icons/GithubIcon";
import { InstagramIcon } from "components/icons/brand-icons/InstagramIcon";
import { LinkedInIcon } from "components/icons/brand-icons/LinkedinIcon";
import { TiktokIcon } from "components/icons/brand-icons/TiktokIcon";
import { XIcon } from "components/icons/brand-icons/XIcon";
import { YoutubeIcon } from "components/icons/brand-icons/YoutubeIcon";
interface socialLinkInfo {
link: string;
@@ -13,16 +13,14 @@ interface socialLinkInfo {
label?: string;
}
const socialIconSize = "1.25rem";
export const SOCIALS: socialLinkInfo[] = [
{
link: "https://twitter.com/thirdweb",
icon: <SiTwitter fontSize={socialIconSize} />,
icon: <XIcon className="size-5" />,
ariaLabel: "Twitter",
},
{
icon: <SiDiscord fontSize={socialIconSize} />,
icon: <DiscordIcon className="size-5" />,
ariaLabel: "Discord",
label: "discord",
link: "https://discord.gg/thirdweb",
@@ -31,30 +29,30 @@ export const SOCIALS: socialLinkInfo[] = [
link: "https://www.youtube.com/channel/UCdzMx7Zhy5va5End1-XJFbA",
ariaLabel: "YouTube",
label: "youtube",
icon: <SiYoutube fontSize={socialIconSize} />,
icon: <YoutubeIcon className="size-5" />,
},
{
link: "https://www.linkedin.com/company/third-web/",
ariaLabel: "LinkedIn",
label: "linkedin",
icon: <SiLinkedin fontSize={socialIconSize} />,
icon: <LinkedInIcon className="size-5" />,
},
{
link: "https://www.instagram.com/thirdweb/",
ariaLabel: "Instagram",
label: "instagram",
icon: <SiInstagram fontSize={socialIconSize} />,
icon: <InstagramIcon className="size-5" />,
},
{
link: "https://www.tiktok.com/@thirdweb",
ariaLabel: "TikTok",
label: "tiktok",
icon: <SiTiktok fontSize={socialIconSize} />,
icon: <TiktokIcon className="size-5" />,
},
{
link: "https://github.com/thirdweb-dev",
ariaLabel: "GitHub",
label: "gitHub",
icon: <SiGithub fontSize={socialIconSize} />,
icon: <GithubIcon className="size-5" />,
},
];
@@ -1,7 +1,7 @@
import { Flex, Icon } from "@chakra-ui/react";
import { ImMagicWand } from "@react-icons/all-files/im/ImMagicWand";
import { ChakraNextImage } from "components/Image";
import { useTrack } from "hooks/analytics/useTrack";
import { WandIcon } from "lucide-react";
import { FiSearch } from "react-icons/fi";
import { Heading, LinkButton } from "tw-components";
@@ -60,7 +60,7 @@ export const HackathonEarnFooter = ({
h="68px"
w={{ base: "90%", md: 80 }}
fontSize="20px"
leftIcon={<Icon as={ImMagicWand} />}
leftIcon={<WandIcon className="size-6" />}
color="black"
flexShrink={0}
background="rgba(255,255,255,1)"
@@ -1,7 +1,7 @@
import { Flex, Icon } from "@chakra-ui/react";
import { ImMagicWand } from "@react-icons/all-files/im/ImMagicWand";
import { ChakraNextImage } from "components/Image";
import { useTrack } from "hooks/analytics/useTrack";
import { WandIcon } from "lucide-react";
import { FiSearch } from "react-icons/fi";
import { Heading, LinkButton } from "tw-components";
@@ -56,7 +56,7 @@ export const HackathonFooter = ({
h="68px"
w={{ base: "90%", md: 80 }}
fontSize="20px"
leftIcon={<Icon as={ImMagicWand} />}
leftIcon={<WandIcon className="size-6" />}
color="black"
flexShrink={0}
background="rgba(255,255,255,1)"
@@ -1,6 +1,6 @@
import { Flex, Icon, LinkBox, LinkOverlay } from "@chakra-ui/react";
import { BiRightArrowAlt } from "@react-icons/all-files/bi/BiRightArrowAlt";
import { Flex, LinkBox, LinkOverlay } from "@chakra-ui/react";
import { useTrack } from "hooks/analytics/useTrack";
import { MoveRightIcon } from "lucide-react";
import { Badge, Heading, LinkButton, Text } from "tw-components";
interface ScheduleItem {
@@ -108,14 +108,7 @@ export const ScheduleSection: React.FC<ScheduleSectionProps> = ({
</Flex>
</LinkOverlay>
</Flex>
<Icon
as={BiRightArrowAlt}
boxSize={6}
color="gray.300"
_groupHover={{ color: "#e984f3" }}
display={{ base: "none", md: "block" }}
marginLeft="auto"
/>
<MoveRightIcon className="ml-auto hidden size-4 text-gray-300 md:block" />
</Flex>
))}
</Flex>
@@ -1,34 +0,0 @@
import { Icon, IconButton } from "@chakra-ui/react";
import { IoMdCheckmark } from "@react-icons/all-files/io/IoMdCheckmark";
import { useTrack } from "hooks/analytics/useTrack";
import { useClipboard } from "hooks/useClipboard";
import { FiCopy } from "react-icons/fi";
export const CopyButton: React.FC<{ text: string }> = ({ text }) => {
const { onCopy, hasCopied } = useClipboard(text);
const trackEvent = useTrack();
return (
<IconButton
ml="auto"
borderRadius="md"
variant="ghost"
colorScheme="whiteAlpha"
size="sm"
aria-label="Copy npx command"
onClick={() => {
onCopy();
trackEvent({
category: "hero-section",
action: "copy",
label: "npx command",
});
}}
icon={
<Icon
as={hasCopied ? IoMdCheckmark : FiCopy}
fill={hasCopied ? "green.500" : undefined}
/>
}
/>
);
};
@@ -8,12 +8,11 @@ import {
useColorModeValue,
useTheme,
} from "@chakra-ui/react";
import { IoMdCheckmark } from "@react-icons/all-files/io/IoMdCheckmark";
import { useClipboard } from "hooks/useClipboard";
import { CheckIcon, CopyIcon } from "lucide-react";
import { Highlight, themes } from "prism-react-renderer";
import { useEffect, useRef, useState } from "react";
import { BsLightning } from "react-icons/bs";
import { FiCopy } from "react-icons/fi";
import { useInView } from "react-intersection-observer";
import { Text } from "tw-components";
@@ -162,10 +161,11 @@ export const HomePageCodeBlock: React.FC<CodeBlockProps> = ({
colorScheme="gray"
size="sm"
icon={
<Icon
as={hasCopied ? IoMdCheckmark : FiCopy}
fill={hasCopied ? "green.500" : undefined}
/>
hasCopied ? (
<CheckIcon className="size-4 text-green-500" />
) : (
<CopyIcon className="size-4" />
)
}
/>
)}
@@ -49,7 +49,7 @@ export const NewsletterSection = () => {
>
<Stack direction="row" spacing={5} align="center">
<Icon boxSize={8} color="white" as={MdMarkEmailRead} />
<Stack>
<div className="flex flex-col gap-2">
<Text color="white" size="label.lg">
Sign up for our newsletter
</Text>
@@ -57,7 +57,7 @@ export const NewsletterSection = () => {
Join 70,000+ builders and stay up to date with our latest
updates and news.
</Text>
</Stack>
</div>
</Stack>
<form onSubmit={(e) => e.preventDefault()}>
@@ -0,0 +1,18 @@
import type { SVGProps } from "react";
export const DiscordIcon = (props: SVGProps<SVGSVGElement>) => {
return (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
width={24}
height={24}
{...props}
>
<title>Discord</title>
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z" />
</svg>
);
};
@@ -0,0 +1,18 @@
import type { SVGProps } from "react";
export const GithubIcon = (props: SVGProps<SVGSVGElement>) => {
return (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
width={24}
height={24}
{...props}
>
<title>GitHub</title>
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
</svg>
);
};
@@ -0,0 +1,18 @@
import type { SVGProps } from "react";
export const InstagramIcon = (props: SVGProps<SVGSVGElement>) => {
return (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
width={24}
height={24}
{...props}
>
<title>Instagram</title>
<path d="M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077" />
</svg>
);
};
@@ -0,0 +1,18 @@
import type { SVGProps } from "react";
export const JavaScriptIcon = (props: SVGProps<SVGSVGElement>) => {
return (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
width={24}
height={24}
{...props}
>
<title>JavaScript</title>
<path d="M0 0h24v24H0V0zm22.034 18.276c-.175-1.095-.888-2.015-3.003-2.873-.736-.345-1.554-.585-1.797-1.14-.091-.33-.105-.51-.046-.705.15-.646.915-.84 1.515-.66.39.12.75.42.976.9 1.034-.676 1.034-.676 1.755-1.125-.27-.42-.404-.601-.586-.78-.63-.705-1.469-1.065-2.834-1.034l-.705.089c-.676.165-1.32.525-1.71 1.005-1.14 1.291-.811 3.541.569 4.471 1.365 1.02 3.361 1.244 3.616 2.205.24 1.17-.87 1.545-1.966 1.41-.811-.18-1.26-.586-1.755-1.336l-1.83 1.051c.21.48.45.689.81 1.109 1.74 1.756 6.09 1.666 6.871-1.004.029-.09.24-.705.074-1.65l.046.067zm-8.983-7.245h-2.248c0 1.938-.009 3.864-.009 5.805 0 1.232.063 2.363-.138 2.711-.33.689-1.18.601-1.566.48-.396-.196-.597-.466-.83-.855-.063-.105-.11-.196-.127-.196l-1.825 1.125c.305.63.75 1.172 1.324 1.517.855.51 2.004.675 3.207.405.783-.226 1.458-.691 1.811-1.411.51-.93.402-2.07.397-3.346.012-2.054 0-4.109 0-6.179l.004-.056z" />
</svg>
);
};
@@ -0,0 +1,18 @@
import type { SVGProps } from "react";
export const LinkedInIcon = (props: SVGProps<SVGSVGElement>) => {
return (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
width={24}
height={24}
{...props}
>
<title>LinkedIn</title>
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
</svg>
);
};
@@ -0,0 +1,18 @@
import type { SVGProps } from "react";
export const MediumIcon = (props: SVGProps<SVGSVGElement>) => {
return (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
width={24}
height={24}
{...props}
>
<title>Medium</title>
<path d="M13.54 12a6.8 6.8 0 01-6.77 6.82A6.8 6.8 0 010 12a6.8 6.8 0 016.77-6.82A6.8 6.8 0 0113.54 12zM20.96 12c0 3.54-1.51 6.42-3.38 6.42-1.87 0-3.39-2.88-3.39-6.42s1.52-6.42 3.39-6.42 3.38 2.88 3.38 6.42M24 12c0 3.17-.53 5.75-1.19 5.75-.66 0-1.19-2.58-1.19-5.75s.53-5.75 1.19-5.75C23.47 6.25 24 8.83 24 12z" />
</svg>
);
};
@@ -0,0 +1,18 @@
import type { SVGProps } from "react";
export const MetaIcon = (props: SVGProps<SVGSVGElement>) => {
return (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
width={24}
height={24}
{...props}
>
<title>Meta</title>
<path d="M6.915 4.03c-1.968 0-3.683 1.28-4.871 3.113C.704 9.208 0 11.883 0 14.449c0 .706.07 1.369.21 1.973a6.624 6.624 0 0 0 .265.86 5.297 5.297 0 0 0 .371.761c.696 1.159 1.818 1.927 3.593 1.927 1.497 0 2.633-.671 3.965-2.444.76-1.012 1.144-1.626 2.663-4.32l.756-1.339.186-.325c.061.1.121.196.183.3l2.152 3.595c.724 1.21 1.665 2.556 2.47 3.314 1.046.987 1.992 1.22 3.06 1.22 1.075 0 1.876-.355 2.455-.843a3.743 3.743 0 0 0 .81-.973c.542-.939.861-2.127.861-3.745 0-2.72-.681-5.357-2.084-7.45-1.282-1.912-2.957-2.93-4.716-2.93-1.047 0-2.088.467-3.053 1.308-.652.57-1.257 1.29-1.82 2.05-.69-.875-1.335-1.547-1.958-2.056-1.182-.966-2.315-1.303-3.454-1.303zm10.16 2.053c1.147 0 2.188.758 2.992 1.999 1.132 1.748 1.647 4.195 1.647 6.4 0 1.548-.368 2.9-1.839 2.9-.58 0-1.027-.23-1.664-1.004-.496-.601-1.343-1.878-2.832-4.358l-.617-1.028a44.908 44.908 0 0 0-1.255-1.98c.07-.109.141-.224.211-.327 1.12-1.667 2.118-2.602 3.358-2.602zm-10.201.553c1.265 0 2.058.791 2.675 1.446.307.327.737.871 1.234 1.579l-1.02 1.566c-.757 1.163-1.882 3.017-2.837 4.338-1.191 1.649-1.81 1.817-2.486 1.817-.524 0-1.038-.237-1.383-.794-.263-.426-.464-1.13-.464-2.046 0-2.221.63-4.535 1.66-6.088.454-.687.964-1.226 1.533-1.533a2.264 2.264 0 0 1 1.088-.285z" />
</svg>
);
};
@@ -0,0 +1,18 @@
import type { SVGProps } from "react";
export const ReactIcon = (props: SVGProps<SVGSVGElement>) => {
return (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
width={24}
height={24}
{...props}
>
<title>React</title>
<path d="M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236 2.236 2.236 0 0 1-2.236-2.236 2.236 2.236 0 0 1 2.235-2.236 2.236 2.236 0 0 1 2.236 2.236zm2.648-10.69c-1.346 0-3.107.96-4.888 2.622-1.78-1.653-3.542-2.602-4.887-2.602-.41 0-.783.093-1.106.278-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03-.704 3.113-.39 5.588.988 6.38.32.187.69.275 1.102.275 1.345 0 3.107-.96 4.888-2.624 1.78 1.654 3.542 2.603 4.887 2.603.41 0 .783-.09 1.106-.275 1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032.704-3.11.39-5.587-.988-6.38-.318-.184-.688-.277-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127.666.382.955 1.835.73 3.704-.054.46-.142.945-.25 1.44-.96-.236-2.006-.417-3.107-.534-.66-.905-1.345-1.727-2.035-2.447 1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28-.686.72-1.37 1.537-2.02 2.442-1.107.117-2.154.298-3.113.538-.112-.49-.195-.964-.254-1.42-.23-1.868.054-3.32.714-3.707.19-.09.4-.127.563-.132zm4.882 3.05c.455.468.91.992 1.36 1.564-.44-.02-.89-.034-1.345-.034-.46 0-.915.01-1.36.034.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093.406.582.802 1.203 1.183 1.86.372.64.71 1.29 1.018 1.946-.308.655-.646 1.31-1.013 1.95-.38.66-.773 1.288-1.18 1.87-.728.063-1.466.098-2.21.098-.74 0-1.477-.035-2.202-.093-.406-.582-.802-1.204-1.183-1.86-.372-.64-.71-1.29-1.018-1.946.303-.657.646-1.313 1.013-1.954.38-.66.773-1.286 1.18-1.868.728-.064 1.466-.098 2.21-.098zm-3.635.254c-.24.377-.48.763-.704 1.16-.225.39-.435.782-.635 1.174-.265-.656-.49-1.31-.676-1.947.64-.15 1.315-.283 2.015-.386zm7.26 0c.695.103 1.365.23 2.006.387-.18.632-.405 1.282-.66 1.933-.2-.39-.41-.783-.64-1.174-.225-.392-.465-.774-.705-1.146zm3.063.675c.484.15.944.317 1.375.498 1.732.74 2.852 1.708 2.852 2.476-.005.768-1.125 1.74-2.857 2.475-.42.18-.88.342-1.355.493-.28-.958-.646-1.956-1.1-2.98.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98-.45 1.017-.812 2.01-1.086 2.964-.484-.15-.944-.318-1.37-.5-1.732-.737-2.852-1.706-2.852-2.474 0-.768 1.12-1.742 2.852-2.476.42-.18.88-.342 1.356-.494zm11.678 4.28c.265.657.49 1.312.676 1.948-.64.157-1.316.29-2.016.39.24-.375.48-.762.705-1.158.225-.39.435-.788.636-1.18zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143-.695-.102-1.365-.23-2.006-.386.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423.23 1.868-.054 3.32-.714 3.708-.147.09-.338.128-.563.128-1.012 0-2.514-.807-4.11-2.28.686-.72 1.37-1.536 2.02-2.44 1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532.66.905 1.345 1.727 2.035 2.446-1.595 1.483-3.092 2.295-4.11 2.295-.22-.005-.406-.05-.553-.132-.666-.38-.955-1.834-.73-3.703.054-.46.142-.944.25-1.438zm4.56.64c.44.02.89.034 1.345.034.46 0 .915-.01 1.36-.034-.44.572-.895 1.095-1.345 1.565-.455-.47-.91-.993-1.36-1.565z" />
</svg>
);
};
@@ -0,0 +1,18 @@
import type { SVGProps } from "react";
export const RedditIcon = (props: SVGProps<SVGSVGElement>) => {
return (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
width={24}
height={24}
{...props}
>
<title>Reddit</title>
<path d="M12 0C5.373 0 0 5.373 0 12c0 3.314 1.343 6.314 3.515 8.485l-2.286 2.286C.775 23.225 1.097 24 1.738 24H12c6.627 0 12-5.373 12-12S18.627 0 12 0Zm4.388 3.199c1.104 0 1.999.895 1.999 1.999 0 1.105-.895 2-1.999 2-.946 0-1.739-.657-1.947-1.539v.002c-1.147.162-2.032 1.15-2.032 2.341v.007c1.776.067 3.4.567 4.686 1.363.473-.363 1.064-.58 1.707-.58 1.547 0 2.802 1.254 2.802 2.802 0 1.117-.655 2.081-1.601 2.531-.088 3.256-3.637 5.876-7.997 5.876-4.361 0-7.905-2.617-7.998-5.87-.954-.447-1.614-1.415-1.614-2.538 0-1.548 1.255-2.802 2.803-2.802.645 0 1.239.218 1.712.585 1.275-.79 2.881-1.291 4.64-1.365v-.01c0-1.663 1.263-3.034 2.88-3.207.188-.911.993-1.595 1.959-1.595Zm-8.085 8.376c-.784 0-1.459.78-1.506 1.797-.047 1.016.64 1.429 1.426 1.429.786 0 1.371-.369 1.418-1.385.047-1.017-.553-1.841-1.338-1.841Zm7.406 0c-.786 0-1.385.824-1.338 1.841.047 1.017.634 1.385 1.418 1.385.785 0 1.473-.413 1.426-1.429-.046-1.017-.721-1.797-1.506-1.797Zm-3.703 4.013c-.974 0-1.907.048-2.77.135-.147.015-.241.168-.183.305.483 1.154 1.622 1.964 2.953 1.964 1.33 0 2.47-.81 2.953-1.964.057-.137-.037-.29-.184-.305-.863-.087-1.795-.135-2.769-.135Z" />
</svg>
);
};
@@ -0,0 +1,18 @@
import type { SVGProps } from "react";
export const TelegramIcon = (props: SVGProps<SVGSVGElement>) => {
return (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
width={24}
height={24}
{...props}
>
<title>Telegram</title>
<path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z" />
</svg>
);
};
@@ -0,0 +1,18 @@
import type { SVGProps } from "react";
export const TiktokIcon = (props: SVGProps<SVGSVGElement>) => {
return (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
width={24}
height={24}
{...props}
>
<title>TikTok</title>
<path d="M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z" />
</svg>
);
};
@@ -0,0 +1,18 @@
import type { SVGProps } from "react";
export const TypeScriptIcon = (props: SVGProps<SVGSVGElement>) => {
return (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
width={24}
height={24}
{...props}
>
<title>TypeScript</title>
<path d="M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zm17.363 9.75c.612 0 1.154.037 1.627.111a6.38 6.38 0 0 1 1.306.34v2.458a3.95 3.95 0 0 0-.643-.361 5.093 5.093 0 0 0-.717-.26 5.453 5.453 0 0 0-1.426-.2c-.3 0-.573.028-.819.086a2.1 2.1 0 0 0-.623.242c-.17.104-.3.229-.393.374a.888.888 0 0 0-.14.49c0 .196.053.373.156.529.104.156.252.304.443.444s.423.276.696.41c.273.135.582.274.926.416.47.197.892.407 1.266.628.374.222.695.473.963.753.268.279.472.598.614.957.142.359.214.776.214 1.253 0 .657-.125 1.21-.373 1.656a3.033 3.033 0 0 1-1.012 1.085 4.38 4.38 0 0 1-1.487.596c-.566.12-1.163.18-1.79.18a9.916 9.916 0 0 1-1.84-.164 5.544 5.544 0 0 1-1.512-.493v-2.63a5.033 5.033 0 0 0 3.237 1.2c.333 0 .624-.03.872-.09.249-.06.456-.144.623-.25.166-.108.29-.234.373-.38a1.023 1.023 0 0 0-.074-1.089 2.12 2.12 0 0 0-.537-.5 5.597 5.597 0 0 0-.807-.444 27.72 27.72 0 0 0-1.007-.436c-.918-.383-1.602-.852-2.053-1.405-.45-.553-.676-1.222-.676-2.005 0-.614.123-1.141.369-1.582.246-.441.58-.804 1.004-1.089a4.494 4.494 0 0 1 1.47-.629 7.536 7.536 0 0 1 1.77-.201zm-15.113.188h9.563v2.166H9.506v9.646H6.789v-9.646H3.375z" />
</svg>
);
};
@@ -0,0 +1,18 @@
import type { SVGProps } from "react";
export const UnityIcon = (props: SVGProps<SVGSVGElement>) => {
return (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
width={24}
height={24}
{...props}
>
<title>Unity</title>
<path d="m12.9288 4.2939 3.7997 2.1929c.1366.077.1415.2905 0 .3675l-4.515 2.6076a.4192.4192 0 0 1-.4246 0L7.274 6.8543c-.139-.0745-.1415-.293 0-.3675l3.7972-2.193V0L1.3758 5.5977V16.793l3.7177-2.1456v-4.3858c-.0025-.1565.1813-.2682.318-.1838l4.5148 2.6076a.4252.4252 0 0 1 .2136.3676v5.2127c.0025.1565-.1813.2682-.3179.1838l-3.7996-2.1929-3.7178 2.1457L12 24l9.6954-5.5977-3.7178-2.1457-3.7996 2.1929c-.1341.082-.3229-.0248-.3179-.1838V13.053c0-.1565.087-.2956.2136-.3676l4.5149-2.6076c.134-.082.3228.0224.3179.1838v4.3858l3.7177 2.1456V5.5977L12.9288 0Z" />
</svg>
);
};
@@ -0,0 +1,18 @@
import type { SVGProps } from "react";
export const XIcon = (props: SVGProps<SVGSVGElement>) => {
return (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
width={24}
height={24}
{...props}
>
<title>X</title>
<path d="M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z" />
</svg>
);
};
@@ -0,0 +1,18 @@
import type { SVGProps } from "react";
export const YoutubeIcon = (props: SVGProps<SVGSVGElement>) => {
return (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
width={24}
height={24}
{...props}
>
<title>YouTube</title>
<path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" />
</svg>
);
};
+3 -3
View File
@@ -1,5 +1,5 @@
import { cn } from "@/lib/utils";
import { Stack, VisuallyHidden } from "@chakra-ui/react";
import { VisuallyHidden } from "@chakra-ui/react";
export const IconLogo: React.FC<{ extraClass?: string; color?: string }> = ({
color,
@@ -95,7 +95,7 @@ export const Logo: React.FC<ILogoProps> = ({
color = "var(--chakra-colors-wordmark)",
}) => {
return (
<Stack as="h2" align="center" direction="row">
<h2 className="flex flex-row items-center gap-2">
{hideIcon ?? <IconLogo extraClass="w-9 md:w-10 flex-shrink-0" />}
{(hideWordmark && !forceShowWordMark) ?? (
<div
@@ -117,6 +117,6 @@ export const Logo: React.FC<ILogoProps> = ({
</div>
)}
<VisuallyHidden>thirdweb</VisuallyHidden>
</Stack>
</h2>
);
};
@@ -1,7 +1,7 @@
import { Icon } from "@chakra-ui/react";
import { SiJavascript } from "@react-icons/all-files/si/SiJavascript";
import { SiReact } from "@react-icons/all-files/si/SiReact";
import { SiUnity } from "@react-icons/all-files/si/SiUnity";
import { JavaScriptIcon } from "components/icons/brand-icons/JavaScriptIcon";
import { ReactIcon } from "components/icons/brand-icons/ReactIcon";
import { UnityIcon } from "components/icons/brand-icons/UnityIcon";
import { useTrack } from "hooks/analytics/useTrack";
import type { Dispatch, SetStateAction } from "react";
import { flushSync } from "react-dom";
@@ -9,19 +9,19 @@ import { Button, type ButtonProps } from "tw-components";
export const LOGO_OPTIONS = {
javascript: {
icon: SiJavascript,
icon: JavaScriptIcon,
fill: "yellow",
},
react: {
icon: SiReact,
icon: ReactIcon,
fill: "#61dafb",
},
"react-native": {
icon: SiReact,
icon: ReactIcon,
fill: "#61dafb",
},
unity: {
icon: SiUnity,
icon: UnityIcon,
fill: "#ffffff",
},
} as const;
@@ -1,5 +1,5 @@
import { Box, Flex, useBreakpointValue } from "@chakra-ui/react";
import { SiGithub } from "@react-icons/all-files/si/SiGithub";
import { GithubIcon } from "components/icons/brand-icons/GithubIcon";
import { useTrack } from "hooks/analytics/useTrack";
import NextImage, { type StaticImageData } from "next/image";
import { IoGameControllerOutline } from "react-icons/io5";
@@ -96,7 +96,7 @@ export const GameCard: React.FC<GameCardProps> = ({
}}
size="sm"
variant="ghost"
icon={<SiGithub />}
icon={<GithubIcon className="size-4" />}
aria-label="GitHub"
category={TRACK_CATEGORY}
label={game.id}
@@ -1,4 +1,4 @@
import { Box, Fade, SimpleGrid, Stack, useDisclosure } from "@chakra-ui/react";
import { Box, Fade, SimpleGrid, useDisclosure } from "@chakra-ui/react";
import { useEffect, useRef, useState } from "react";
import { Card, Text } from "tw-components";
import { NavCard } from "./NavCard";
@@ -76,14 +76,12 @@ export const HoverMenu: React.FC<HoverMenuProps> = ({
borderRadius="8px"
width={width}
>
<div className="flex flex-row">
<Stack>
<SimpleGrid columns={columns} gap={2}>
{items.map((item) => (
<NavCard key={item.label} {...item} />
))}
</SimpleGrid>
</Stack>
<div className="flex flex-col gap-2">
<SimpleGrid columns={columns} gap={2}>
{items.map((item) => (
<NavCard key={item.label} {...item} />
))}
</SimpleGrid>
</div>
</Card>
</Fade>
@@ -1,7 +1,6 @@
import {
Flex,
type FlexProps,
Icon,
IconButton,
ListItem,
UnorderedList,
@@ -106,7 +105,7 @@ const MobileNavSection: React.FC<MobileNavSectionProps> = ({
size="label.md"
onClick={onItemClick}
>
{link.icon ? (
{link.icon && (
<ChakraNextImage
alt=""
boxSize={5}
@@ -114,9 +113,7 @@ const MobileNavSection: React.FC<MobileNavSectionProps> = ({
sizes="40px"
w={5}
/>
) : link.iconType ? (
<Icon as={link.iconType} h={4} w={5} />
) : null}
)}
{link.name}
</Heading>
</ListItem>
@@ -1,4 +1,4 @@
import { Box, Icon, Stack } from "@chakra-ui/react";
import { Box } from "@chakra-ui/react";
import { ChakraNextImage } from "components/Image";
import { Text, TrackedLink } from "tw-components";
import type { SectionItemProps, SectionProps } from "./types";
@@ -9,7 +9,6 @@ export const NavCard: React.FC<SectionItemProps | SectionProps> = ({
label,
link,
icon,
iconType,
comingSoon,
}) => {
return (
@@ -22,18 +21,10 @@ export const NavCard: React.FC<SectionItemProps | SectionProps> = ({
pointerEvents={comingSoon ? "none" : "auto"}
>
<Box _hover={{ bg: "whiteAlpha.50" }} p="8px" borderRadius="md">
<Stack direction="row" align="center" spacing={4}>
<div className="flex flex-row items-center gap-4">
{icon && (
<ChakraNextImage boxSize={7} mb="-4px" src={icon} alt="icon" />
)}
{iconType && (
<Icon
as={iconType}
color={comingSoon ? "whiteAlpha.400" : "white"}
boxSize={6}
mr={2}
/>
)}
<div className="flex flex-col">
<Text
fontWeight="bold"
@@ -43,7 +34,7 @@ export const NavCard: React.FC<SectionItemProps | SectionProps> = ({
</Text>
<Text color="whiteAlpha.500">{description}</Text>
</div>
</Stack>
</div>
</Box>
</TrackedLink>
);
@@ -142,7 +142,7 @@ export const NestedHoverMenu: React.FC<NestedHoverMenuProps> = ({
))}
</Flex>
<Flex p={4} bg="#0E0F11">
<Stack width="328px">
<div className="flex w-[328px] flex-col gap-2">
<SimpleGrid columns={1} gap={2}>
{items
.filter((item) => item.section === hoveredSection)
@@ -150,7 +150,7 @@ export const NestedHoverMenu: React.FC<NestedHoverMenuProps> = ({
<NavCard key={item.label} {...item} />
))}
</SimpleGrid>
</Stack>
</div>
</Flex>
</div>
</Card>
@@ -1,5 +1,4 @@
import { Box, Stack } from "@chakra-ui/react";
import type { IconType } from "@react-icons/all-files";
import { Box } from "@chakra-ui/react";
import type { StaticImageData } from "next/image";
import { Text } from "tw-components";
@@ -8,7 +7,6 @@ interface ProductSectionItemProps {
description: string;
href?: string;
icon?: StaticImageData;
iconType?: IconType;
comingSoon?: boolean;
selected: boolean;
}
@@ -26,7 +24,7 @@ export const ProductNavCard: React.FC<ProductSectionItemProps> = ({
p={6}
cursor="default"
>
<Stack direction="row" align="center" spacing={3}>
<div className="flex flex-row items-center gap-3">
<div className="flex flex-col">
<Text
fontWeight="bold"
@@ -36,7 +34,7 @@ export const ProductNavCard: React.FC<ProductSectionItemProps> = ({
</Text>
<Text color="whiteAlpha.500">{description}</Text>
</div>
</Stack>
</div>
</Box>
);
};
@@ -1,4 +1,3 @@
import type { IconType } from "@react-icons/all-files";
import type { StaticImageData } from "next/image";
export interface SectionProps {
@@ -8,7 +7,6 @@ export interface SectionProps {
icon?: StaticImageData;
link?: string;
section?: "contracts-v2" | "connect-v2" | "engine-v2";
iconType?: IconType;
comingSoon?: boolean;
}
@@ -20,7 +18,6 @@ export interface SectionItemProps {
link: string;
dashboardLink?: string;
icon?: StaticImageData;
iconType?: IconType;
comingSoon?: boolean;
inLandingPage?: boolean;
section:
@@ -11,7 +11,6 @@ import {
AlertTitle,
Flex,
IconButton,
Stack,
useDisclosure,
} from "@chakra-ui/react";
import { OnboardingModal } from "components/onboarding/Modal";
@@ -283,7 +282,7 @@ export const BillingAlerts = () => {
if (alerts.length === 0) {
return null;
}
return <Stack className="py-6">{alerts}</Stack>;
return <div className="flex flex-col gap-2 py-6">{alerts}</div>;
};
type BillingAlertNotificationProps = {
@@ -1,6 +1,6 @@
import { SiReact } from "@react-icons/all-files/si/SiReact";
import { SiTypescript } from "@react-icons/all-files/si/SiTypescript";
import { SiUnity } from "@react-icons/all-files/si/SiUnity";
import { ReactIcon } from "components/icons/brand-icons/ReactIcon";
import { TypeScriptIcon } from "components/icons/brand-icons/TypeScriptIcon";
import { UnityIcon } from "components/icons/brand-icons/UnityIcon";
import Link from "next/link";
import { SiUnrealengine } from "react-icons/si";
import { SiDotnet } from "react-icons/si";
@@ -21,22 +21,22 @@ export function ConnectSDKCard({
<div className="grid max-w-[500px] grid-cols-1 gap-6 md:grid-cols-2 md:gap-4 lg:grid-cols-3">
<DocLink
link="https://portal.thirdweb.com/typescript/v5/getting-started"
icon={SiTypescript}
icon={TypeScriptIcon}
label="TypeScript SDK"
/>
<DocLink
link="https://portal.thirdweb.com/react/v5/getting-started"
icon={SiReact}
icon={ReactIcon}
label="React SDK"
/>
<DocLink
link="https://portal.thirdweb.com/react-native/v5/getting-started"
icon={SiReact}
icon={ReactIcon}
label="React Native SDK"
/>
<DocLink
link="https://portal.thirdweb.com/unity/v4/getting-started"
icon={SiUnity}
icon={UnityIcon}
label="Unity SDK"
/>
<DocLink

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