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
13 changes: 6 additions & 7 deletions apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,27 +261,26 @@ export function useEngineRemoveFromDashboard() {
});
}

export interface RemoveCloudHostedInput {
instanceId: string;
export interface DeleteCloudHostedInput {
deploymentId: string;
reason: "USING_SELF_HOSTED" | "TOO_EXPENSIVE" | "MISSING_FEATURES" | "OTHER";
feedback: string;
}

export function useEngineRemoveCloudHosted() {
export function useEngineDeleteCloudHosted() {
const { user } = useLoggedInUser();
const queryClient = useQueryClient();

return useMutation({
mutationFn: async ({
instanceId,
deploymentId,
reason,
feedback,
}: RemoveCloudHostedInput) => {
}: DeleteCloudHostedInput) => {
const res = await fetch(
`${THIRDWEB_API_HOST}/v1/engine/${instanceId}/remove-cloud-hosted`,
`${THIRDWEB_API_HOST}/v2/engine/deployments/${deploymentId}/infrastructure/delete`,
{
method: "POST",

headers: {
"Content-Type": "application/json",
},
Expand Down
157 changes: 88 additions & 69 deletions apps/dashboard/src/components/engine/engine-instances-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Checkbox, CheckboxWithLabel } from "@/components/ui/checkbox";
import {
Dialog,
DialogContent,
Expand All @@ -13,12 +14,12 @@ import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { ToolTipLabel } from "@/components/ui/tooltip";
import {
type DeleteCloudHostedInput,
type EditEngineInstanceInput,
type EngineInstance,
type RemoveCloudHostedInput,
useEngineDeleteCloudHosted,
useEngineEditInstance,
type useEngineInstances,
useEngineRemoveCloudHosted,
useEngineRemoveFromDashboard,
} from "@3rdweb-sdk/react/hooks/useEngine";
import { FormControl, Radio, RadioGroup } from "@chakra-ui/react";
Expand All @@ -28,15 +29,16 @@ import { TWTable } from "components/shared/TWTable";
import { useTrack } from "hooks/analytics/useTrack";
import {
CircleAlertIcon,
InfoIcon,
PencilIcon,
SendIcon,
Trash2Icon,
TriangleAlertIcon,
} from "lucide-react";
import Link from "next/link";
import { type ReactNode, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import invariant from "tiny-invariant";
import { FormLabel } from "tw-components";

interface EngineInstancesTableProps {
Expand Down Expand Up @@ -132,7 +134,7 @@ export const EngineInstancesTable: React.FC<EngineInstancesTableProps> = ({
return (
<>
<TWTable
title="engine instances"
title="Your Engines"
data={instances}
columns={columns}
isFetched={isFetched}
Expand All @@ -153,7 +155,7 @@ export const EngineInstancesTable: React.FC<EngineInstancesTableProps> = ({
},
{
icon: <Trash2Icon className="size-4" />,
text: "Remove",
text: "Delete",
onClick: (instance) => {
trackEvent({
category: "engine",
Expand Down Expand Up @@ -257,7 +259,7 @@ const EditModal = (props: {

<DialogFooter className="mt-10 gap-2">
<Button onClick={() => onOpenChange(false)} variant="outline">
Cancel
Close
</Button>
<Button
type="submit"
Expand Down Expand Up @@ -294,7 +296,7 @@ const RemoveModal = (props: {
close={() => onOpenChange(false)}
/>
) : (
<CancelSubscriptionModalContent
<DeleteSubscriptionModalContent
refetch={refetch}
instance={instance}
close={() => onOpenChange(false)}
Expand All @@ -317,18 +319,22 @@ function RemoveFromDashboardModalContent(props: {
<>
<DialogHeader>
<DialogTitle className="mb-3 font-semibold text-2xl tracking-tight">
Remove Engine Instance
Remove Engine
</DialogTitle>
<DialogDescription className="text-muted-foreground">
<span className="mb-2 block">
Are you sure you want to remove
<em className="font-semibold not-italic">"{instance.name}"</em> from
Are you sure you want to remove{" "}
<em className="font-semibold not-italic">{instance.name}</em> from
your dashboard?
</span>
<span className="block">
This action does not modify your Engine infrastructure. You can
re-add it at any time.
</span>

<Alert variant="info">
<InfoIcon className="size-5" />
<AlertTitle>
This action does not modify your Engine infrastructure.
</AlertTitle>
<AlertDescription>You can re-add it at any time.</AlertDescription>
</Alert>
</DialogDescription>
</DialogHeader>

Expand Down Expand Up @@ -366,73 +372,67 @@ function RemoveFromDashboardModalContent(props: {
);
}

function CancelSubscriptionModalContent(props: {
function DeleteSubscriptionModalContent(props: {
refetch: () => void;
instance: EngineInstance;
close: () => void;
}) {
const { refetch, instance, close } = props;
const removeCloudHosted = useEngineRemoveCloudHosted();
invariant(
instance.deploymentId,
"Instance must have a deploymentId to be cancelled.",
);

const form = useForm<RemoveCloudHostedInput>({
const deleteCloudHosted = useEngineDeleteCloudHosted();
const [ackDeletion, setAckDeletion] = useState(false);
const form = useForm<DeleteCloudHostedInput>({
defaultValues: {
instanceId: instance.id,
deploymentId: instance.deploymentId,
},
reValidateMode: "onChange",
});

const onSubmit = (data: DeleteCloudHostedInput) => {
deleteCloudHosted.mutate(data, {
onSuccess: () => {
toast.success("Deleting Engine. Please check again in a few minutes.", {
dismissible: true,
duration: 10000,
});

refetch();
close();
},
onError: () => {
toast.error(
"Error deleting Engine. Please visit https://thirdweb.com/support.",
);
},
});
};

return (
<div>
<DialogHeader>
<DialogTitle className="mb-1 font-semibold text-2xl tracking-tight">
Cancel Engine Subscription
Permanently Delete Engine
</DialogTitle>
<DialogDescription className="text-muted-foreground">
Complete this form to request to cancel your Engine subscription. This
may take up to 2 business days.
</DialogDescription>
</DialogHeader>

<div className="h-3" />

<Alert variant="destructive">
<TriangleAlertIcon className="!text-destructive-text size-5" />
<AlertTitle>This action is irreversible!</AlertTitle>
<AlertDescription>
You will no longer be able to access this Engine's local backend
wallets. <strong>Any remaining mainnet funds will be lost.</strong>
</AlertDescription>
</Alert>

<div className="h-5" />

<form
onSubmit={form.handleSubmit((data) =>
removeCloudHosted.mutate(data, {
onSuccess: () => {
toast.success(
"Submitted a request to cancel your Engine subscription. This may take up to 2 business days.",
{
dismissible: true,
duration: 10000,
},
);

refetch();
close();
},
onError: () => {
toast.error(
"Error requesting to cancel your Engine subscription",
);
},
}),
)}
>
{/* Form */}
<div className="h-4" />

<p className="text-muted-foreground">
This step will cancel your monthly subscription and immediately delete
all data and infrastructure for this Engine.
</p>

<div className="h-4" />

<form onSubmit={form.handleSubmit(onSubmit)}>
{/* Reason */}
<FormControl isRequired>
<FormLabel className="!text-base">
Please share any feedback to help us improve
Please share your feedback to help us improve Engine.
</FormLabel>
<RadioGroup>
<div className="flex flex-col gap-2">
Expand Down Expand Up @@ -466,13 +466,32 @@ function CancelSubscriptionModalContent(props: {

<div className="h-2" />

{/* Feedback */}
<Textarea
className="mt-3"
placeholder="Provide additional feedback"
{...form.register("feedback")}
/>

<div className="h-8" />
<div className="h-4" />

<Alert variant="destructive">
<TriangleAlertIcon className="!text-destructive-text size-5" />
<AlertTitle>This action is irreversible!</AlertTitle>

<AlertDescription className="!pl-0 pt-2">
<CheckboxWithLabel>
<Checkbox
checked={ackDeletion}
onCheckedChange={(checked) => setAckDeletion(!!checked)}
/>
I understand that access to my local backend wallets and any
remaining funds will be lost.
</CheckboxWithLabel>
</AlertDescription>
</Alert>

<div className="h-4" />

<DialogFooter className="gap-2">
<Button onClick={close} variant="outline">
Expand All @@ -481,15 +500,15 @@ function CancelSubscriptionModalContent(props: {
<Button
type="submit"
variant="destructive"
disabled={!form.formState.isValid}
disabled={
!ackDeletion ||
deleteCloudHosted.isPending ||
!form.formState.isValid
}
className="gap-2"
>
{removeCloudHosted.isPending ? (
<Spinner className="size-4" />
) : (
<SendIcon className="size-4" />
)}
Request to cancel
{deleteCloudHosted.isPending && <Spinner className="size-4" />}
Permanently Delete Engine
</Button>
</DialogFooter>
</form>
Expand Down
Loading