Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
"use client";

import { Button } from "@/components/ui/button";
import { useApiKeys, useCreateApiKey } from "@3rdweb-sdk/react/hooks/useApi";
import { Flex, FormControl, Input, Link, Select } from "@chakra-ui/react";
import { Flex, FormControl, Input, Select } from "@chakra-ui/react";
import { LazyCreateAPIKeyDialog } from "components/settings/ApiKeys/Create/LazyCreateAPIKeyDialog";
import { useTrack } from "hooks/analytics/useTrack";
import { useAllChainsData } from "hooks/chains/allChains";
import { useClipboard } from "hooks/useClipboard";
import { useTxNotifications } from "hooks/useTxNotifications";
import { CheckIcon, CopyIcon } from "lucide-react";
import { useMemo } from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import type { StoredChain } from "stores/chainStores";
import type { ThirdwebContract } from "thirdweb";
import type { ChainMetadata } from "thirdweb/chains";
import { useActiveAccount } from "thirdweb/react";
import {
Button,
Card,
CodeBlock,
FormHelperText,
Expand Down Expand Up @@ -211,10 +214,6 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({

const apiKeys = useApiKeys();
const createKeyMutation = useCreateApiKey();
const { onSuccess, onError } = useTxNotifications(
"API key created",
"Failed to create API key",
);

const validApiKey = (apiKeys.data || []).find(
(apiKey) =>
Expand Down Expand Up @@ -298,14 +297,35 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
);

const { hasCopied, onCopy } = useClipboard(embedCode, 3000);
const [showCreateAPIKeyModal, setShowCreateAPIKeyModal] = useState(false);
const activeAccount = useActiveAccount();
const pathname = usePathname();

return (
<Flex gap={8} direction="column">
<LazyCreateAPIKeyDialog
wording="api-key"
prefill={{
name: "Embed API Key",
domains: "embed.ipfscdn.io",
}}
open={showCreateAPIKeyModal}
onOpenChange={setShowCreateAPIKeyModal}
onCreateAndComplete={() => {
trackEvent({
category: "api-keys",
action: "create",
label: "success",
fromEmbed: true,
});
apiKeys.refetch();
}}
/>

<Flex gap={8} direction={{ base: "column", md: "row" }}>
<Card className="flex w-full flex-col gap-2 md:w-1/2">
<Heading size="title.sm" mb={4}>
Configuration
</Heading>
<Card className="flex w-full flex-col gap-5 md:w-1/2">
<Heading size="title.sm">Configuration</Heading>

{ercOrMarketplace === "marketplace" ? (
<FormControl>
<FormLabel>Listing ID</FormLabel>
Expand All @@ -315,6 +335,7 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
</FormHelperText>
</FormControl>
) : null}

{ercOrMarketplace === "marketplace-v3" ? (
<FormControl>
<FormLabel>Listing type</FormLabel>
Expand All @@ -327,6 +348,7 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
</FormHelperText>
</FormControl>
) : null}

{ercOrMarketplace === "marketplace-v3" &&
watch("listingType") === "direct-listing" ? (
<FormControl>
Expand All @@ -337,6 +359,7 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
</FormHelperText>
</FormControl>
) : null}

{ercOrMarketplace === "marketplace-v3" &&
watch("listingType") === "english-auction" ? (
<FormControl>
Expand All @@ -347,6 +370,7 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
</FormHelperText>
</FormControl>
) : null}

{ercOrMarketplace === "erc1155" ? (
<FormControl>
<FormLabel>Token ID</FormLabel>
Expand All @@ -356,21 +380,28 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
</FormHelperText>
</FormControl>
) : null}

<FormControl>
<FormLabel>Client ID</FormLabel>
{validApiKey ? (
{!activeAccount ? (
<Button asChild className="w-full">
<Link
href={`/login${
pathname ? `?next=${encodeURIComponent(pathname)}` : ""
}`}
>
Sign in to create a client ID
</Link>
</Button>
) : validApiKey ? (
<Input
readOnly
disabled
value={`${validApiKey?.name} - ${validApiKey?.key}`}
/>
) : (
<Button
bgColor="bgBlack"
color="bgWhite"
_hover={{
opacity: 0.8,
}}
className="w-full"
onClick={() => {
trackEvent({
category: "api-keys",
Expand All @@ -379,44 +410,7 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
fromEmbed: true,
});

createKeyMutation.mutate(
{
name: "Embed API key",
domains: ["embed.ipfscdn.io"],
services: [
{
name: "rpc",
targetAddresses: ["*"],
},
{
name: "storage",
targetAddresses: ["*"],
actions: ["read"],
},
],
},
{
onSuccess: () => {
onSuccess();
trackEvent({
category: "api-keys",
action: "create",
label: "success",
fromEmbed: true,
});
},
onError: (err) => {
onError(err);
trackEvent({
category: "api-keys",
action: "create",
label: "error",
error: err,
fromEmbed: true,
});
},
},
);
setShowCreateAPIKeyModal(true);
}}
disabled={createKeyMutation.isPending}
>
Expand All @@ -428,14 +422,15 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
You need a client ID to use embeds.{" "}
<Link
href="https://portal.thirdweb.com/account/api-keys"
color="primary.500"
isExternal
className="text-link-foreground hover:text-foreground"
target="_blank"
>
Learn more
</Link>
.
</FormHelperText>
</FormControl>

<FormControl>
<FormLabel>RPC Url</FormLabel>
<Input type="url" {...register("rpcUrl")} />
Expand Down Expand Up @@ -477,15 +472,16 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
A relayer can be used to make the transaction gasless for the
end user.{" "}
<Link
isExternal
color="blue.500"
target="_blank"
className="text-link-foreground hover:text-foreground"
href="https://blog.thirdweb.com/guides/setup-gasless-transactions"
>
Learn more
</Link>
</FormHelperText>
</FormControl>
)}

<FormControl>
<Heading size="title.sm" my={4}>
Customization
Expand All @@ -501,6 +497,7 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
on the user system&apos;s preferences.
</FormHelperText>
</FormControl>

<FormControl>
<FormLabel>Primary Color</FormLabel>
<Select {...register("primaryColor")}>
Expand All @@ -517,6 +514,7 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
Used for the main actions button backgrounds.
</FormHelperText>
</FormControl>

{ercOrMarketplace === "marketplace" ||
ercOrMarketplace === "marketplace-v3" ? (
<FormControl>
Expand All @@ -535,6 +533,7 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
</FormControl>
) : null}
</Card>

<Card className="flex w-full flex-col gap-2 md:w-1/2">
<Heading size="title.sm">Embed Code</Heading>
<CodeBlock
Expand All @@ -545,8 +544,7 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
language="markup"
/>
<Button
colorScheme="purple"
w="auto"
className="w-auto gap-2"
variant="outline"
onClick={() => {
onCopy();
Expand All @@ -558,14 +556,12 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
chainId,
});
}}
leftIcon={
hasCopied ? (
<CheckIcon className="size-4" />
) : (
<CopyIcon className="size-4" />
)
}
>
{hasCopied ? (
<CheckIcon className="size-4" />
) : (
<CopyIcon className="size-4" />
)}
{hasCopied ? "Copied!" : "Copy to clipboard"}
</Button>
</Card>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { Meta, StoryObj } from "@storybook/react";
import { useMutation } from "@tanstack/react-query";
import { useState } from "react";
import { Toaster } from "sonner";
import { CreateAPIKeyDialogUI } from ".";
import { CreateAPIKeyDialogUI, type CreateAPIKeyPrefillOptions } from ".";
import { createApiKeyStub } from "../../../../stories/stubs";
import { mobileViewport } from "../../../../stories/utils";

Expand Down Expand Up @@ -51,8 +51,19 @@ export const MobileAPIKeyWording: Story = {
},
};

export const DesktopAPIKeyWordingWithPrefill: Story = {
args: {
wording: "api-key",
prefill: {
name: "Foo",
domains: "foo.bar",
},
},
};

function Story(props: {
wording: "project" | "api-key";
prefill?: CreateAPIKeyPrefillOptions;
}) {
const [isOpen, setIsOpen] = useState(true);
const mutation = useMutation({
Expand All @@ -72,6 +83,7 @@ function Story(props: {
open={isOpen}
onOpenChange={setIsOpen}
createKeyMutation={mutation}
prefill={props.prefill}
/>

<Button
Expand Down
13 changes: 11 additions & 2 deletions apps/dashboard/src/components/settings/ApiKeys/Create/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,17 @@ import {
apiKeyCreateValidationSchema,
} from "../validations";

export type CreateAPIKeyPrefillOptions = {
name?: string;
domains?: string;
};

export type CreateAPIKeyDialogProps = {
open: boolean;
onOpenChange: (open: boolean) => void;
wording: "project" | "api-key";
onCreateAndComplete?: () => void;
prefill?: CreateAPIKeyPrefillOptions;
};

const CreateAPIKeyDialog = (props: CreateAPIKeyDialogProps) => {
Expand All @@ -71,6 +77,7 @@ export const CreateAPIKeyDialogUI = (props: {
CreateKeyInput,
unknown
>;
prefill?: CreateAPIKeyPrefillOptions;
}) => {
const [screen, setScreen] = useState<
{ id: "create" } | { id: "api-details"; key: ApiKey }
Expand Down Expand Up @@ -102,6 +109,7 @@ export const CreateAPIKeyDialogUI = (props: {
onAPIKeyCreated={(key) => {
setScreen({ id: "api-details", key });
}}
prefill={props.prefill}
/>
)}

Expand Down Expand Up @@ -131,6 +139,7 @@ function CreateAPIKeyForm(props: {
unknown
>;
onAPIKeyCreated: (key: ApiKey) => void;
prefill?: CreateAPIKeyPrefillOptions;
}) {
const { wording } = props;
const [showAlert, setShowAlert] = useState<"no-domain" | "any-domain">();
Expand All @@ -141,8 +150,8 @@ function CreateAPIKeyForm(props: {
const form = useForm<ApiKeyCreateValidationSchema>({
resolver: zodResolver(apiKeyCreateValidationSchema),
defaultValues: {
name: "",
domains: "",
name: props.prefill?.name || "",
domains: props.prefill?.domains || "",
},
});

Expand Down
Loading