Skip to content

Commit 8247448

Browse files
committed
feat: Engine delete modal now prompts to permanently delete, calls new route (#4998)
<!-- start pr-codex --> ## PR-Codex overview This PR focuses on renaming and modifying the functionality related to deleting cloud-hosted engines in the `useEngine` hook and its usage in the `EngineInstancesTable`. The changes include updates to input types, function names, and UI elements for clarity and consistency. ### Detailed summary - Renamed `RemoveCloudHostedInput` to `DeleteCloudHostedInput`. - Changed `instanceId` to `deploymentId` in the input type. - Renamed `useEngineRemoveCloudHosted` to `useEngineDeleteCloudHosted`. - Updated API endpoint in the mutation function. - Changed UI labels from "Remove" to "Delete" and "Cancel Engine Subscription" to "Permanently Delete Engine". - Added feedback textarea in the delete confirmation modal. - Enhanced alert messages for irreversible actions. - Updated handling of deletion confirmation acknowledgment. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 958bedd commit 8247448

File tree

2 files changed

+94
-76
lines changed

2 files changed

+94
-76
lines changed

apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -261,27 +261,26 @@ export function useEngineRemoveFromDashboard() {
261261
});
262262
}
263263

264-
export interface RemoveCloudHostedInput {
265-
instanceId: string;
264+
export interface DeleteCloudHostedInput {
265+
deploymentId: string;
266266
reason: "USING_SELF_HOSTED" | "TOO_EXPENSIVE" | "MISSING_FEATURES" | "OTHER";
267267
feedback: string;
268268
}
269269

270-
export function useEngineRemoveCloudHosted() {
270+
export function useEngineDeleteCloudHosted() {
271271
const { user } = useLoggedInUser();
272272
const queryClient = useQueryClient();
273273

274274
return useMutation({
275275
mutationFn: async ({
276-
instanceId,
276+
deploymentId,
277277
reason,
278278
feedback,
279-
}: RemoveCloudHostedInput) => {
279+
}: DeleteCloudHostedInput) => {
280280
const res = await fetch(
281-
`${THIRDWEB_API_HOST}/v1/engine/${instanceId}/remove-cloud-hosted`,
281+
`${THIRDWEB_API_HOST}/v2/engine/deployments/${deploymentId}/infrastructure/delete`,
282282
{
283283
method: "POST",
284-
285284
headers: {
286285
"Content-Type": "application/json",
287286
},

apps/dashboard/src/components/engine/engine-instances-table.tsx

Lines changed: 88 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Spinner } from "@/components/ui/Spinner/Spinner";
22
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
33
import { Badge } from "@/components/ui/badge";
44
import { Button } from "@/components/ui/button";
5+
import { Checkbox, CheckboxWithLabel } from "@/components/ui/checkbox";
56
import {
67
Dialog,
78
DialogContent,
@@ -13,12 +14,12 @@ import { Input } from "@/components/ui/input";
1314
import { Textarea } from "@/components/ui/textarea";
1415
import { ToolTipLabel } from "@/components/ui/tooltip";
1516
import {
17+
type DeleteCloudHostedInput,
1618
type EditEngineInstanceInput,
1719
type EngineInstance,
18-
type RemoveCloudHostedInput,
20+
useEngineDeleteCloudHosted,
1921
useEngineEditInstance,
2022
type useEngineInstances,
21-
useEngineRemoveCloudHosted,
2223
useEngineRemoveFromDashboard,
2324
} from "@3rdweb-sdk/react/hooks/useEngine";
2425
import { FormControl, Radio, RadioGroup } from "@chakra-ui/react";
@@ -28,15 +29,16 @@ import { TWTable } from "components/shared/TWTable";
2829
import { useTrack } from "hooks/analytics/useTrack";
2930
import {
3031
CircleAlertIcon,
32+
InfoIcon,
3133
PencilIcon,
32-
SendIcon,
3334
Trash2Icon,
3435
TriangleAlertIcon,
3536
} from "lucide-react";
3637
import Link from "next/link";
3738
import { type ReactNode, useState } from "react";
3839
import { useForm } from "react-hook-form";
3940
import { toast } from "sonner";
41+
import invariant from "tiny-invariant";
4042
import { FormLabel } from "tw-components";
4143

4244
interface EngineInstancesTableProps {
@@ -132,7 +134,7 @@ export const EngineInstancesTable: React.FC<EngineInstancesTableProps> = ({
132134
return (
133135
<>
134136
<TWTable
135-
title="engine instances"
137+
title="Your Engines"
136138
data={instances}
137139
columns={columns}
138140
isFetched={isFetched}
@@ -153,7 +155,7 @@ export const EngineInstancesTable: React.FC<EngineInstancesTableProps> = ({
153155
},
154156
{
155157
icon: <Trash2Icon className="size-4" />,
156-
text: "Remove",
158+
text: "Delete",
157159
onClick: (instance) => {
158160
trackEvent({
159161
category: "engine",
@@ -257,7 +259,7 @@ const EditModal = (props: {
257259

258260
<DialogFooter className="mt-10 gap-2">
259261
<Button onClick={() => onOpenChange(false)} variant="outline">
260-
Cancel
262+
Close
261263
</Button>
262264
<Button
263265
type="submit"
@@ -294,7 +296,7 @@ const RemoveModal = (props: {
294296
close={() => onOpenChange(false)}
295297
/>
296298
) : (
297-
<CancelSubscriptionModalContent
299+
<DeleteSubscriptionModalContent
298300
refetch={refetch}
299301
instance={instance}
300302
close={() => onOpenChange(false)}
@@ -317,18 +319,22 @@ function RemoveFromDashboardModalContent(props: {
317319
<>
318320
<DialogHeader>
319321
<DialogTitle className="mb-3 font-semibold text-2xl tracking-tight">
320-
Remove Engine Instance
322+
Remove Engine
321323
</DialogTitle>
322324
<DialogDescription className="text-muted-foreground">
323325
<span className="mb-2 block">
324-
Are you sure you want to remove
325-
<em className="font-semibold not-italic">"{instance.name}"</em> from
326+
Are you sure you want to remove{" "}
327+
<em className="font-semibold not-italic">{instance.name}</em> from
326328
your dashboard?
327329
</span>
328-
<span className="block">
329-
This action does not modify your Engine infrastructure. You can
330-
re-add it at any time.
331-
</span>
330+
331+
<Alert variant="info">
332+
<InfoIcon className="size-5" />
333+
<AlertTitle>
334+
This action does not modify your Engine infrastructure.
335+
</AlertTitle>
336+
<AlertDescription>You can re-add it at any time.</AlertDescription>
337+
</Alert>
332338
</DialogDescription>
333339
</DialogHeader>
334340

@@ -366,73 +372,67 @@ function RemoveFromDashboardModalContent(props: {
366372
);
367373
}
368374

369-
function CancelSubscriptionModalContent(props: {
375+
function DeleteSubscriptionModalContent(props: {
370376
refetch: () => void;
371377
instance: EngineInstance;
372378
close: () => void;
373379
}) {
374380
const { refetch, instance, close } = props;
375-
const removeCloudHosted = useEngineRemoveCloudHosted();
381+
invariant(
382+
instance.deploymentId,
383+
"Instance must have a deploymentId to be cancelled.",
384+
);
376385

377-
const form = useForm<RemoveCloudHostedInput>({
386+
const deleteCloudHosted = useEngineDeleteCloudHosted();
387+
const [ackDeletion, setAckDeletion] = useState(false);
388+
const form = useForm<DeleteCloudHostedInput>({
378389
defaultValues: {
379-
instanceId: instance.id,
390+
deploymentId: instance.deploymentId,
380391
},
381392
reValidateMode: "onChange",
382393
});
383394

395+
const onSubmit = (data: DeleteCloudHostedInput) => {
396+
deleteCloudHosted.mutate(data, {
397+
onSuccess: () => {
398+
toast.success("Deleting Engine. Please check again in a few minutes.", {
399+
dismissible: true,
400+
duration: 10000,
401+
});
402+
403+
refetch();
404+
close();
405+
},
406+
onError: () => {
407+
toast.error(
408+
"Error deleting Engine. Please visit https://thirdweb.com/support.",
409+
);
410+
},
411+
});
412+
};
413+
384414
return (
385415
<div>
386416
<DialogHeader>
387417
<DialogTitle className="mb-1 font-semibold text-2xl tracking-tight">
388-
Cancel Engine Subscription
418+
Permanently Delete Engine
389419
</DialogTitle>
390-
<DialogDescription className="text-muted-foreground">
391-
Complete this form to request to cancel your Engine subscription. This
392-
may take up to 2 business days.
393-
</DialogDescription>
394420
</DialogHeader>
395421

396-
<div className="h-3" />
397-
398-
<Alert variant="destructive">
399-
<TriangleAlertIcon className="!text-destructive-text size-5" />
400-
<AlertTitle>This action is irreversible!</AlertTitle>
401-
<AlertDescription>
402-
You will no longer be able to access this Engine's local backend
403-
wallets. <strong>Any remaining mainnet funds will be lost.</strong>
404-
</AlertDescription>
405-
</Alert>
406-
407-
<div className="h-5" />
408-
409-
<form
410-
onSubmit={form.handleSubmit((data) =>
411-
removeCloudHosted.mutate(data, {
412-
onSuccess: () => {
413-
toast.success(
414-
"Submitted a request to cancel your Engine subscription. This may take up to 2 business days.",
415-
{
416-
dismissible: true,
417-
duration: 10000,
418-
},
419-
);
420-
421-
refetch();
422-
close();
423-
},
424-
onError: () => {
425-
toast.error(
426-
"Error requesting to cancel your Engine subscription",
427-
);
428-
},
429-
}),
430-
)}
431-
>
432-
{/* Form */}
422+
<div className="h-4" />
423+
424+
<p className="text-muted-foreground">
425+
This step will cancel your monthly subscription and immediately delete
426+
all data and infrastructure for this Engine.
427+
</p>
428+
429+
<div className="h-4" />
430+
431+
<form onSubmit={form.handleSubmit(onSubmit)}>
432+
{/* Reason */}
433433
<FormControl isRequired>
434434
<FormLabel className="!text-base">
435-
Please share any feedback to help us improve
435+
Please share your feedback to help us improve Engine.
436436
</FormLabel>
437437
<RadioGroup>
438438
<div className="flex flex-col gap-2">
@@ -466,13 +466,32 @@ function CancelSubscriptionModalContent(props: {
466466

467467
<div className="h-2" />
468468

469+
{/* Feedback */}
469470
<Textarea
470471
className="mt-3"
471472
placeholder="Provide additional feedback"
472473
{...form.register("feedback")}
473474
/>
474475

475-
<div className="h-8" />
476+
<div className="h-4" />
477+
478+
<Alert variant="destructive">
479+
<TriangleAlertIcon className="!text-destructive-text size-5" />
480+
<AlertTitle>This action is irreversible!</AlertTitle>
481+
482+
<AlertDescription className="!pl-0 pt-2">
483+
<CheckboxWithLabel>
484+
<Checkbox
485+
checked={ackDeletion}
486+
onCheckedChange={(checked) => setAckDeletion(!!checked)}
487+
/>
488+
I understand that access to my local backend wallets and any
489+
remaining funds will be lost.
490+
</CheckboxWithLabel>
491+
</AlertDescription>
492+
</Alert>
493+
494+
<div className="h-4" />
476495

477496
<DialogFooter className="gap-2">
478497
<Button onClick={close} variant="outline">
@@ -481,15 +500,15 @@ function CancelSubscriptionModalContent(props: {
481500
<Button
482501
type="submit"
483502
variant="destructive"
484-
disabled={!form.formState.isValid}
503+
disabled={
504+
!ackDeletion ||
505+
deleteCloudHosted.isPending ||
506+
!form.formState.isValid
507+
}
485508
className="gap-2"
486509
>
487-
{removeCloudHosted.isPending ? (
488-
<Spinner className="size-4" />
489-
) : (
490-
<SendIcon className="size-4" />
491-
)}
492-
Request to cancel
510+
{deleteCloudHosted.isPending && <Spinner className="size-4" />}
511+
Permanently Delete Engine
493512
</Button>
494513
</DialogFooter>
495514
</form>

0 commit comments

Comments
 (0)