Skip to content
Merged
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
Expand Up @@ -8,6 +8,7 @@ import {
ExternalLinkIcon,
} from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
Expand All @@ -17,7 +18,6 @@ import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
Expand Down Expand Up @@ -70,6 +70,7 @@ export function ImportEngineButton(props: {
teamSlug: string;
projectSlug: string;
}) {
const [isOpen, setIsOpen] = useState(false);
const router = useDashboardRouter();
const form = useForm<ImportEngineParams>({
resolver: zodResolver(formSchema),
Expand All @@ -82,14 +83,15 @@ export function ImportEngineButton(props: {
const importMutation = useMutation({
mutationFn: async (importParams: ImportEngineParams) => {
await importEngine({ ...importParams, teamIdOrSlug: props.teamSlug });
router.push(`/team/${props.teamSlug}/${props.projectSlug}/engine`);
router.refresh();
},
});
Comment on lines 83 to 88
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Prevent duplicate submissions; disable UI while pending.

Multiple clicks can create duplicate imports. Guard submit and disable inputs/button during mutation.

-  const importMutation = useMutation({
+  const importMutation = useMutation({
     mutationFn: async (importParams: ImportEngineParams) => {
       await importEngine({ ...importParams, teamIdOrSlug: props.teamSlug });
       router.refresh();
     },
   });

-  const onSubmit = async (data: ImportEngineParams) => {
+  const onSubmit = async (data: ImportEngineParams) => {
+    if (importMutation.isPending) return;
     try {
       await importMutation.mutateAsync(data);
       toast.success("Engine imported successfully");
       setIsOpen(false);
     } catch (e) {
       const message = e instanceof Error ? e.message : undefined;
       toast.error(
         "Error importing Engine. Please check if the details are correct.",
         {
           description: message,
         },
       );
     }
   };
-                        <Input
+                        <Input
                           className="bg-card"
+                          disabled={importMutation.isPending}
                           autoFocus
                           placeholder="Enter a descriptive label"
                           {...field}
                         />
-                        <Input
+                        <Input
                           className="bg-card"
+                          disabled={importMutation.isPending}
                           placeholder="Enter your Engine URL"
                           type="url"
                           {...field}
                         />
-              <Button className="gap-2 rounded-full" type="submit">
+              <Button
+                className="gap-2 rounded-full"
+                type="submit"
+                disabled={importMutation.isPending}
+                aria-busy={importMutation.isPending}
+              >

Also applies to: 90-104, 148-153, 167-173, 187-196

🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/(general)/import/import-engine-dialog.tsx
around lines 83-88 (and similarly for 90-104, 148-153, 167-173, 187-196): the
mutation allows multiple clicks causing duplicate imports; use the useMutation
status (isLoading/isPending) to guard submissions and disable the form controls
and submit button while the mutation is in flight. Update handlers to return
early if the mutation is already loading, await mutateAsync (or check isLoading)
to ensure a single in-flight request, and pass the mutation state to the UI so
inputs/buttons have disabled={isLoading} and show a loading indicator.


const onSubmit = async (data: ImportEngineParams) => {
try {
await importMutation.mutateAsync(data);
toast.success("Engine imported successfully");
setIsOpen(false);
} catch (e) {
const message = e instanceof Error ? e.message : undefined;
toast.error(
Expand All @@ -102,42 +104,40 @@ export function ImportEngineButton(props: {
};

return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<Dialog>
<DialogTrigger asChild>
<Button
className="gap-2 rounded-full bg-card"
size="sm"
variant="outline"
>
<ArrowDownToLineIcon className="size-3.5" />
Import Engine
</Button>
</DialogTrigger>
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button
className="gap-2 rounded-full bg-card"
size="sm"
variant="outline"
>
<ArrowDownToLineIcon className="size-3.5" />
Import Engine
</Button>
</DialogTrigger>

<DialogContent>
<DialogHeader>
<DialogContent className="p-0 rounded-lg overflow-hidden">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<DialogHeader className="p-4 lg:p-6">
<DialogTitle>Import Engine Instance</DialogTitle>
<DialogDescription>
Import an Engine instance hosted on your infrastructure.
</DialogDescription>
</DialogHeader>

<div>
<div>
<Link
className="flex items-center justify-between gap-2 rounded-lg border border-border bg-card p-3 text-sm hover:bg-accent"
href="https://portal.thirdweb.com/infrastructure/engine/get-started"
rel="noopener noreferrer"
target="_blank"
>
Get help setting up Engine for free
<ExternalLinkIcon className="size-4 text-muted-foreground" />
</Link>
</div>
<div className="px-4 lg:px-6 pb-6">
<Link
className="mb-4 flex items-center justify-between gap-2 rounded-full border border-border bg-card p-3 text-sm hover:bg-accent"
href="https://portal.thirdweb.com/engine/v2/get-started"
rel="noopener noreferrer"
target="_blank"
>
Get help setting up Engine for free
<ExternalLinkIcon className="size-4 text-muted-foreground" />
</Link>

<div className="mt-6 flex flex-col gap-4">
<div className="space-y-5">
<FormField
control={form.control}
name="name"
Expand All @@ -146,6 +146,7 @@ export function ImportEngineButton(props: {
<FormLabel>Name</FormLabel>
<FormControl>
<Input
className="bg-card"
autoFocus
placeholder="Enter a descriptive label"
{...field}
Expand All @@ -164,6 +165,7 @@ export function ImportEngineButton(props: {
<FormLabel>URL</FormLabel>
<FormControl>
<Input
className="bg-card"
placeholder="Enter your Engine URL"
type="url"
{...field}
Expand All @@ -182,19 +184,19 @@ export function ImportEngineButton(props: {
</div>
</div>

<DialogFooter>
<Button className="min-w-28 gap-1.5 rounded-full" type="submit">
<div className="border-t bg-card p-6 flex justify-end">
<Button className="gap-2 rounded-full" type="submit">
{importMutation.isPending ? (
<Spinner className="size-4" />
) : (
<ArrowDownToLineIcon className="size-4" />
)}
Import
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</form>
</Form>
</div>
</form>
</Form>
</DialogContent>
</Dialog>
);
}
Loading