Skip to content

Commit cd6fd9a

Browse files
committed
Dashboard: Migrate engine/configuration page from chakra to tailwind, UI improvements (#7716)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on refactoring and improving the UI of various configuration components for the Engine dashboard, enhancing layout, styling, and user experience while updating some logic related to wallet configurations. ### Detailed summary - Updated layout classes for `local-config.tsx`, `engine-configuration.tsx`, and `system.tsx`. - Added `UnderlineLink` component for links in `engine-wallet-config.tsx`, `circle-config.tsx`, and others. - Improved styling for alerts and forms in various components. - Enhanced error handling and user feedback with toast notifications in `cors.tsx`, `ip-allowlist.tsx`, and wallet config components. - Refactored props handling in `EngineWalletConfig`, `EngineCorsConfig`, and `EngineIpAllowlistConfig` to use inline type definitions. - Added `SaveIcon` and `Spinner` components for better loading and saving indicators in forms. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 12c42c0 commit cd6fd9a

File tree

9 files changed

+480
-439
lines changed

9 files changed

+480
-439
lines changed

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/configuration/components/circle-config.tsx

Lines changed: 52 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Link from "next/link";
1+
import { SaveIcon } from "lucide-react";
22
import { useId } from "react";
33
import { useForm } from "react-hook-form";
44
import { toast } from "sonner";
@@ -7,6 +7,7 @@ import { Button } from "@/components/ui/button";
77
import { Form } from "@/components/ui/form";
88
import { Input } from "@/components/ui/input";
99
import { Spinner } from "@/components/ui/Spinner/Spinner";
10+
import { UnderlineLink } from "@/components/ui/UnderlineLink";
1011
import {
1112
type EngineInstance,
1213
type SetWalletConfigInput,
@@ -58,55 +59,61 @@ export const CircleConfig: React.FC<CircleConfigProps> = ({
5859
const circleApiKeyId = useId();
5960

6061
return (
61-
<div className="flex flex-col gap-6">
62-
<div className="flex flex-col gap-2">
63-
<p className="text-muted-foreground">
64-
Circle wallets require an API Key from your Circle account with
65-
sufficient permissions. Created wallets are stored in your AWS
66-
account. Configure your Circle API Key to use Circle wallets. Learn
67-
more about{" "}
68-
<Link
69-
className="text-link-foreground hover:text-foreground"
70-
href="https://portal.thirdweb.com/engine/features/backend-wallets#circle-wallet"
71-
rel="noopener noreferrer"
72-
target="_blank"
73-
>
74-
how to get an API Key
75-
</Link>
76-
.
77-
</p>
78-
</div>
79-
62+
<div className="bg-card rounded-lg border mb-8">
8063
<Form {...form}>
81-
<form
82-
className="flex flex-col gap-4"
83-
onSubmit={form.handleSubmit(onSubmit)}
84-
>
85-
<FormFieldSetup
86-
errorMessage={
87-
form.getFieldState("circleApiKey", form.formState).error?.message
88-
}
89-
htmlFor={circleApiKeyId}
90-
isRequired
91-
label="Circle API Key"
92-
tooltip={null}
93-
>
94-
<Input
95-
autoComplete="off"
96-
id={circleApiKeyId}
97-
placeholder="TEST_API_KEY:..."
98-
type="password"
99-
{...form.register("circleApiKey")}
100-
/>
101-
</FormFieldSetup>
64+
<form onSubmit={form.handleSubmit(onSubmit)}>
65+
<div className="p-4 lg:p-6">
66+
<div className="mb-4">
67+
<h2 className="text-lg font-semibold mb-1">Credentials</h2>
68+
69+
<p className="text-muted-foreground text-sm">
70+
Circle wallets require an API Key from your Circle account with
71+
sufficient permissions. <br /> Created wallets are stored in
72+
your AWS account. Configure your Circle API Key to use Circle
73+
wallets. Learn more about{" "}
74+
<UnderlineLink
75+
href="https://portal.thirdweb.com/engine/features/backend-wallets#circle-wallet"
76+
rel="noopener noreferrer"
77+
target="_blank"
78+
>
79+
how to get an API Key
80+
</UnderlineLink>
81+
.
82+
</p>
83+
</div>
84+
85+
<FormFieldSetup
86+
errorMessage={
87+
form.getFieldState("circleApiKey", form.formState).error
88+
?.message
89+
}
90+
htmlFor={circleApiKeyId}
91+
isRequired
92+
label="Circle API Key"
93+
tooltip={null}
94+
>
95+
<Input
96+
autoComplete="off"
97+
id={circleApiKeyId}
98+
placeholder="TEST_API_KEY:..."
99+
type="password"
100+
{...form.register("circleApiKey")}
101+
/>
102+
</FormFieldSetup>
103+
</div>
102104

103-
<div className="flex items-center justify-end gap-4">
105+
<div className="flex items-center justify-end gap-4 border-t border-dashed px-4 py-4 lg:px-6">
104106
<Button
105-
className="min-w-28 gap-2"
106-
disabled={isPending}
107+
className="gap-2"
108+
disabled={isPending || !form.formState.isDirty}
109+
size="sm"
107110
type="submit"
108111
>
109-
{isPending && <Spinner className="size-4" />}
112+
{isPending ? (
113+
<Spinner className="size-4" />
114+
) : (
115+
<SaveIcon className="size-4" />
116+
)}
110117
Save
111118
</Button>
112119
</div>

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/configuration/components/cors.tsx

Lines changed: 51 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,36 @@
1-
import { Flex, Textarea } from "@chakra-ui/react";
2-
import { Button } from "chakra/button";
3-
import { Heading } from "chakra/heading";
4-
import { Text } from "chakra/text";
1+
import { SaveIcon } from "lucide-react";
52
import { useForm } from "react-hook-form";
3+
import { toast } from "sonner";
4+
import { Button } from "@/components/ui/button";
65
import { InlineCode } from "@/components/ui/inline-code";
6+
import { Spinner } from "@/components/ui/Spinner/Spinner";
7+
import { Textarea } from "@/components/ui/textarea";
78
import {
89
useEngineCorsConfiguration,
910
useEngineSetCorsConfiguration,
1011
} from "@/hooks/useEngine";
11-
import { useTxNotifications } from "@/hooks/useTxNotifications";
12+
import { parseError } from "@/utils/errorParser";
1213

13-
interface EngineCorsConfigProps {
14-
instanceUrl: string;
15-
authToken: string;
16-
}
17-
18-
interface CorsForm {
14+
type CorsForm = {
1915
raw: string;
20-
}
16+
};
2117

22-
export const EngineCorsConfig: React.FC<EngineCorsConfigProps> = ({
18+
export function EngineCorsConfig({
2319
instanceUrl,
2420
authToken,
25-
}) => {
21+
}: {
22+
instanceUrl: string;
23+
authToken: string;
24+
}) {
2625
const { data: existingUrls } = useEngineCorsConfiguration({
2726
authToken,
2827
instanceUrl,
2928
});
30-
const { mutateAsync: setCorsConfig } = useEngineSetCorsConfiguration({
29+
const setCorsConfigMutation = useEngineSetCorsConfiguration({
3130
authToken,
3231
instanceUrl,
3332
});
3433

35-
const { onSuccess, onError } = useTxNotifications(
36-
"CORS URLs updated successfully.",
37-
"Failed to update CORS URLs.",
38-
);
39-
4034
const form = useForm<CorsForm>({
4135
values: { raw: existingUrls?.join("\n") ?? "" },
4236
});
@@ -47,52 +41,57 @@ export const EngineCorsConfig: React.FC<EngineCorsConfigProps> = ({
4741
// Assert all URLs are well formed and strip the path.
4842
const sanitized = urls.map(parseOriginFromUrl);
4943

50-
await setCorsConfig({ urls: sanitized });
51-
onSuccess();
44+
await setCorsConfigMutation.mutateAsync({ urls: sanitized });
45+
toast.success("CORS URLs updated successfully.");
5246
} catch (error) {
53-
onError(error);
47+
toast.error("Failed to update CORS URLs.", {
48+
description: parseError(error),
49+
});
5450
}
5551
};
5652

5753
return (
58-
<Flex
59-
as="form"
60-
flexDir="column"
61-
gap={4}
62-
onSubmit={form.handleSubmit(onSubmit)}
63-
>
64-
<Flex flexDir="column" gap={2}>
65-
<Heading size="title.md">Allowlisted Domains</Heading>
66-
<Text>
54+
<div className="bg-card rounded-lg border">
55+
<div className="px-4 lg:px-6 pt-4 lg:pt-6">
56+
<h2 className="text-lg font-semibold mb-1">Allowlisted Domains</h2>
57+
<p className="text-sm text-muted-foreground">
6758
Specify the origins that can call Engine (
6859
<InlineCode code="https://example.com" />
6960
).
7061
<br />
7162
Enter one origin per line, or leave this list empty to disallow calls
7263
from web clients.
73-
</Text>
74-
</Flex>
64+
</p>
65+
</div>
7566

76-
<Textarea
77-
placeholder={"https://example.com\nhttp://localhost:3000"}
78-
rows={4}
79-
{...form.register("raw")}
80-
/>
67+
<form onSubmit={form.handleSubmit(onSubmit)}>
68+
<div className="p-4 lg:px-6">
69+
<Textarea
70+
placeholder={"https://example.com\nhttp://localhost:3000"}
71+
rows={4}
72+
{...form.register("raw")}
73+
/>
74+
</div>
8175

82-
<Flex alignItems="center" gap={4} justifyContent="end">
83-
<Button
84-
colorScheme="primary"
85-
isDisabled={!form.formState.isDirty}
86-
px={12}
87-
type="submit"
88-
w={{ base: "full", md: "inherit" }}
89-
>
90-
{form.formState.isSubmitting ? "Saving..." : "Save"}
91-
</Button>
92-
</Flex>
93-
</Flex>
76+
<div className="flex justify-end border-t border-dashed px-4 py-4 lg:px-6">
77+
<Button
78+
className="gap-2"
79+
size="sm"
80+
type="submit"
81+
disabled={!form.formState.isDirty || form.formState.isSubmitting}
82+
>
83+
{setCorsConfigMutation.isPending ? (
84+
<Spinner className="size-4" />
85+
) : (
86+
<SaveIcon className="size-4" />
87+
)}
88+
Save
89+
</Button>
90+
</div>
91+
</form>
92+
</div>
9493
);
95-
};
94+
}
9695

9796
const parseOriginFromUrl = (url: string) => {
9897
try {

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/configuration/components/engine-configuration.tsx

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,25 @@ export const EngineConfiguration: React.FC<EngineConfigurationProps> = ({
2020
authToken,
2121
}) => {
2222
return (
23-
<div className="flex flex-col gap-12">
23+
<div>
2424
<EngineWalletConfig
2525
authToken={authToken}
2626
instance={instance}
2727
projectSlug={projectSlug}
2828
teamSlug={teamSlug}
2929
/>
30-
<EngineCorsConfig authToken={authToken} instanceUrl={instance.url} />
31-
<EngineIpAllowlistConfig
32-
authToken={authToken}
33-
instanceUrl={instance.url}
34-
/>
35-
<EngineSystem
36-
instance={instance}
37-
projectSlug={projectSlug}
38-
teamIdOrSlug={teamSlug}
39-
/>
30+
<div className="space-y-8">
31+
<EngineCorsConfig authToken={authToken} instanceUrl={instance.url} />
32+
<EngineIpAllowlistConfig
33+
authToken={authToken}
34+
instanceUrl={instance.url}
35+
/>
36+
<EngineSystem
37+
instance={instance}
38+
projectSlug={projectSlug}
39+
teamIdOrSlug={teamSlug}
40+
/>
41+
</div>
4042
</div>
4143
);
4244
};

0 commit comments

Comments
 (0)