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,4 +1,4 @@
import Link from "next/link";
import { SaveIcon } from "lucide-react";
import { useId } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
Expand All @@ -7,6 +7,7 @@ import { Button } from "@/components/ui/button";
import { Form } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { UnderlineLink } from "@/components/ui/UnderlineLink";
import {
type EngineInstance,
type SetWalletConfigInput,
Expand Down Expand Up @@ -58,55 +59,61 @@ export const CircleConfig: React.FC<CircleConfigProps> = ({
const circleApiKeyId = useId();

return (
<div className="flex flex-col gap-6">
<div className="flex flex-col gap-2">
<p className="text-muted-foreground">
Circle wallets require an API Key from your Circle account with
sufficient permissions. Created wallets are stored in your AWS
account. Configure your Circle API Key to use Circle wallets. Learn
more about{" "}
<Link
className="text-link-foreground hover:text-foreground"
href="https://portal.thirdweb.com/engine/features/backend-wallets#circle-wallet"
rel="noopener noreferrer"
target="_blank"
>
how to get an API Key
</Link>
.
</p>
</div>

<div className="bg-card rounded-lg border mb-8">
<Form {...form}>
<form
className="flex flex-col gap-4"
onSubmit={form.handleSubmit(onSubmit)}
>
<FormFieldSetup
errorMessage={
form.getFieldState("circleApiKey", form.formState).error?.message
}
htmlFor={circleApiKeyId}
isRequired
label="Circle API Key"
tooltip={null}
>
<Input
autoComplete="off"
id={circleApiKeyId}
placeholder="TEST_API_KEY:..."
type="password"
{...form.register("circleApiKey")}
/>
</FormFieldSetup>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className="p-4 lg:p-6">
<div className="mb-4">
<h2 className="text-lg font-semibold mb-1">Credentials</h2>

<p className="text-muted-foreground text-sm">
Circle wallets require an API Key from your Circle account with
sufficient permissions. <br /> Created wallets are stored in
your AWS account. Configure your Circle API Key to use Circle
wallets. Learn more about{" "}
<UnderlineLink
href="https://portal.thirdweb.com/engine/features/backend-wallets#circle-wallet"
rel="noopener noreferrer"
target="_blank"
>
how to get an API Key
</UnderlineLink>
.
</p>
</div>

<FormFieldSetup
errorMessage={
form.getFieldState("circleApiKey", form.formState).error
?.message
}
htmlFor={circleApiKeyId}
isRequired
label="Circle API Key"
tooltip={null}
>
<Input
autoComplete="off"
id={circleApiKeyId}
placeholder="TEST_API_KEY:..."
type="password"
{...form.register("circleApiKey")}
/>
</FormFieldSetup>
</div>

<div className="flex items-center justify-end gap-4">
<div className="flex items-center justify-end gap-4 border-t border-dashed px-4 py-4 lg:px-6">
<Button
className="min-w-28 gap-2"
disabled={isPending}
className="gap-2"
disabled={isPending || !form.formState.isDirty}
size="sm"
type="submit"
>
{isPending && <Spinner className="size-4" />}
{isPending ? (
<Spinner className="size-4" />
) : (
<SaveIcon className="size-4" />
)}
Save
</Button>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,36 @@
import { Flex, Textarea } from "@chakra-ui/react";
import { Button } from "chakra/button";
import { Heading } from "chakra/heading";
import { Text } from "chakra/text";
import { SaveIcon } from "lucide-react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { InlineCode } from "@/components/ui/inline-code";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Textarea } from "@/components/ui/textarea";
import {
useEngineCorsConfiguration,
useEngineSetCorsConfiguration,
} from "@/hooks/useEngine";
import { useTxNotifications } from "@/hooks/useTxNotifications";
import { parseError } from "@/utils/errorParser";

interface EngineCorsConfigProps {
instanceUrl: string;
authToken: string;
}

interface CorsForm {
type CorsForm = {
raw: string;
}
};

export const EngineCorsConfig: React.FC<EngineCorsConfigProps> = ({
export function EngineCorsConfig({
instanceUrl,
authToken,
}) => {
}: {
instanceUrl: string;
authToken: string;
}) {
const { data: existingUrls } = useEngineCorsConfiguration({
authToken,
instanceUrl,
});
const { mutateAsync: setCorsConfig } = useEngineSetCorsConfiguration({
const setCorsConfigMutation = useEngineSetCorsConfiguration({
authToken,
instanceUrl,
});

const { onSuccess, onError } = useTxNotifications(
"CORS URLs updated successfully.",
"Failed to update CORS URLs.",
);

const form = useForm<CorsForm>({
values: { raw: existingUrls?.join("\n") ?? "" },
});
Expand All @@ -47,52 +41,57 @@ export const EngineCorsConfig: React.FC<EngineCorsConfigProps> = ({
// Assert all URLs are well formed and strip the path.
const sanitized = urls.map(parseOriginFromUrl);

await setCorsConfig({ urls: sanitized });
onSuccess();
await setCorsConfigMutation.mutateAsync({ urls: sanitized });
toast.success("CORS URLs updated successfully.");
} catch (error) {
onError(error);
toast.error("Failed to update CORS URLs.", {
description: parseError(error),
});
}
};

return (
<Flex
as="form"
flexDir="column"
gap={4}
onSubmit={form.handleSubmit(onSubmit)}
>
<Flex flexDir="column" gap={2}>
<Heading size="title.md">Allowlisted Domains</Heading>
<Text>
<div className="bg-card rounded-lg border">
<div className="px-4 lg:px-6 pt-4 lg:pt-6">
<h2 className="text-lg font-semibold mb-1">Allowlisted Domains</h2>
<p className="text-sm text-muted-foreground">
Specify the origins that can call Engine (
<InlineCode code="https://example.com" />
).
<br />
Enter one origin per line, or leave this list empty to disallow calls
from web clients.
</Text>
</Flex>
</p>
</div>

<Textarea
placeholder={"https://example.com\nhttp://localhost:3000"}
rows={4}
{...form.register("raw")}
/>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className="p-4 lg:px-6">
<Textarea
placeholder={"https://example.com\nhttp://localhost:3000"}
rows={4}
{...form.register("raw")}
/>
</div>

<Flex alignItems="center" gap={4} justifyContent="end">
<Button
colorScheme="primary"
isDisabled={!form.formState.isDirty}
px={12}
type="submit"
w={{ base: "full", md: "inherit" }}
>
{form.formState.isSubmitting ? "Saving..." : "Save"}
</Button>
</Flex>
</Flex>
<div className="flex justify-end border-t border-dashed px-4 py-4 lg:px-6">
<Button
className="gap-2"
size="sm"
type="submit"
disabled={!form.formState.isDirty || form.formState.isSubmitting}
>
{setCorsConfigMutation.isPending ? (
<Spinner className="size-4" />
) : (
<SaveIcon className="size-4" />
)}
Save
</Button>
</div>
</form>
</div>
);
};
}

const parseOriginFromUrl = (url: string) => {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,25 @@ export const EngineConfiguration: React.FC<EngineConfigurationProps> = ({
authToken,
}) => {
return (
<div className="flex flex-col gap-12">
<div>
<EngineWalletConfig
authToken={authToken}
instance={instance}
projectSlug={projectSlug}
teamSlug={teamSlug}
/>
<EngineCorsConfig authToken={authToken} instanceUrl={instance.url} />
<EngineIpAllowlistConfig
authToken={authToken}
instanceUrl={instance.url}
/>
<EngineSystem
instance={instance}
projectSlug={projectSlug}
teamIdOrSlug={teamSlug}
/>
<div className="space-y-8">
<EngineCorsConfig authToken={authToken} instanceUrl={instance.url} />
<EngineIpAllowlistConfig
authToken={authToken}
instanceUrl={instance.url}
/>
<EngineSystem
instance={instance}
projectSlug={projectSlug}
teamIdOrSlug={teamSlug}
/>
</div>
</div>
);
};
Loading
Loading