add delete resource modal (#280)
This commit is contained in:
@@ -1,57 +1,31 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@usesend/ui/src/button";
|
import { Button } from "@usesend/ui/src/button";
|
||||||
import { Input } from "@usesend/ui/src/input";
|
import { DeleteResource } from "~/components/DeleteResource";
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@usesend/ui/src/dialog";
|
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import React, { useState } from "react";
|
import { Campaign } from "@prisma/client";
|
||||||
import { toast } from "@usesend/ui/src/toaster";
|
import { toast } from "@usesend/ui/src/toaster";
|
||||||
import { Trash2 } from "lucide-react";
|
import { Trash2 } from "lucide-react";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormDescription,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from "@usesend/ui/src/form";
|
|
||||||
import { Campaign } from "@prisma/client";
|
|
||||||
|
|
||||||
const campaignSchema = z.object({
|
|
||||||
name: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const DeleteCampaign: React.FC<{
|
export const DeleteCampaign: React.FC<{
|
||||||
campaign: Partial<Campaign> & { id: string };
|
campaign: Partial<Campaign> & { id: string };
|
||||||
}> = ({ campaign }) => {
|
}> = ({ campaign }) => {
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const deleteCampaignMutation = api.campaign.deleteCampaign.useMutation();
|
const deleteCampaignMutation = api.campaign.deleteCampaign.useMutation();
|
||||||
|
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
||||||
const campaignForm = useForm<z.infer<typeof campaignSchema>>({
|
const campaignSchema = z
|
||||||
resolver: zodResolver(campaignSchema),
|
.object({
|
||||||
});
|
confirmation: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Please type the campaign name to confirm"),
|
||||||
|
})
|
||||||
|
.refine((data) => data.confirmation === campaign.name, {
|
||||||
|
message: "Campaign name does not match",
|
||||||
|
path: ["confirmation"],
|
||||||
|
});
|
||||||
|
|
||||||
async function onCampaignDelete(values: z.infer<typeof campaignSchema>) {
|
async function onCampaignDelete(values: z.infer<typeof campaignSchema>) {
|
||||||
if (values.name !== campaign.name) {
|
|
||||||
campaignForm.setError("name", {
|
|
||||||
message: "Name does not match",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteCampaignMutation.mutate(
|
deleteCampaignMutation.mutate(
|
||||||
{
|
{
|
||||||
campaignId: campaign.id,
|
campaignId: campaign.id,
|
||||||
@@ -59,77 +33,26 @@ export const DeleteCampaign: React.FC<{
|
|||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
utils.campaign.getCampaigns.invalidate();
|
utils.campaign.getCampaigns.invalidate();
|
||||||
setOpen(false);
|
|
||||||
toast.success(`Campaign deleted`);
|
toast.success(`Campaign deleted`);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = campaignForm.watch("name");
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<DeleteResource
|
||||||
open={open}
|
title="Delete Campaign"
|
||||||
onOpenChange={(_open) => (_open !== open ? setOpen(_open) : null)}
|
resourceName={campaign.name || ""}
|
||||||
>
|
schema={campaignSchema}
|
||||||
<DialogTrigger asChild>
|
isLoading={deleteCampaignMutation.isPending}
|
||||||
|
onConfirm={onCampaignDelete}
|
||||||
|
trigger={
|
||||||
<Button variant="ghost" size="sm" className="p-0 hover:bg-transparent">
|
<Button variant="ghost" size="sm" className="p-0 hover:bg-transparent">
|
||||||
<Trash2 className="h-[18px] w-[18px] text-red/80" />
|
<Trash2 className="h-[18px] w-[18px] text-red/80" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
}
|
||||||
<DialogContent>
|
confirmLabel="Delete Campaign"
|
||||||
<DialogHeader>
|
/>
|
||||||
<DialogTitle>Delete Campaign</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Are you sure you want to delete{" "}
|
|
||||||
<span className="font-semibold text-foreground">
|
|
||||||
{campaign.name}
|
|
||||||
</span>
|
|
||||||
? You can't reverse this.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="py-2">
|
|
||||||
<Form {...campaignForm}>
|
|
||||||
<form
|
|
||||||
onSubmit={campaignForm.handleSubmit(onCampaignDelete)}
|
|
||||||
className="space-y-4"
|
|
||||||
>
|
|
||||||
<FormField
|
|
||||||
control={campaignForm.control}
|
|
||||||
name="name"
|
|
||||||
render={({ field, formState }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>name</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field} />
|
|
||||||
</FormControl>
|
|
||||||
{formState.errors.name ? (
|
|
||||||
<FormMessage />
|
|
||||||
) : (
|
|
||||||
<FormDescription className=" text-transparent">
|
|
||||||
.
|
|
||||||
</FormDescription>
|
|
||||||
)}
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant="destructive"
|
|
||||||
disabled={
|
|
||||||
deleteCampaignMutation.isPending || campaign.name !== name
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{deleteCampaignMutation.isPending ? "Deleting..." : "Delete"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,57 +1,29 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@usesend/ui/src/button";
|
import { Button } from "@usesend/ui/src/button";
|
||||||
import { Input } from "@usesend/ui/src/input";
|
import { DeleteResource } from "~/components/DeleteResource";
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@usesend/ui/src/dialog";
|
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import React, { useState } from "react";
|
import { Contact } from "@prisma/client";
|
||||||
import { toast } from "@usesend/ui/src/toaster";
|
import { toast } from "@usesend/ui/src/toaster";
|
||||||
import { Trash2 } from "lucide-react";
|
import { Trash2 } from "lucide-react";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormDescription,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from "@usesend/ui/src/form";
|
|
||||||
import { Contact } from "@prisma/client";
|
|
||||||
|
|
||||||
const contactSchema = z.object({
|
|
||||||
email: z.string().email(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const DeleteContact: React.FC<{
|
export const DeleteContact: React.FC<{
|
||||||
contact: Partial<Contact> & { id: string; contactBookId: string };
|
contact: Partial<Contact> & { id: string; contactBookId: string };
|
||||||
}> = ({ contact }) => {
|
}> = ({ contact }) => {
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const deleteContactMutation = api.contacts.deleteContact.useMutation();
|
const deleteContactMutation = api.contacts.deleteContact.useMutation();
|
||||||
|
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
||||||
const contactForm = useForm<z.infer<typeof contactSchema>>({
|
const contactSchema = z
|
||||||
resolver: zodResolver(contactSchema),
|
.object({
|
||||||
});
|
confirmation: z.string().email("Please enter a valid email address"),
|
||||||
|
})
|
||||||
|
.refine((data) => data.confirmation === contact.email, {
|
||||||
|
message: "Email does not match",
|
||||||
|
path: ["confirmation"],
|
||||||
|
});
|
||||||
|
|
||||||
async function onContactDelete(values: z.infer<typeof contactSchema>) {
|
async function onContactDelete(values: z.infer<typeof contactSchema>) {
|
||||||
if (values.email !== contact.email) {
|
|
||||||
contactForm.setError("email", {
|
|
||||||
message: "Email does not match",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteContactMutation.mutate(
|
deleteContactMutation.mutate(
|
||||||
{
|
{
|
||||||
contactId: contact.id,
|
contactId: contact.id,
|
||||||
@@ -60,7 +32,6 @@ export const DeleteContact: React.FC<{
|
|||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
utils.contacts.contacts.invalidate();
|
utils.contacts.contacts.invalidate();
|
||||||
setOpen(false);
|
|
||||||
toast.success(`Contact deleted`);
|
toast.success(`Contact deleted`);
|
||||||
},
|
},
|
||||||
onError: (e) => {
|
onError: (e) => {
|
||||||
@@ -70,70 +41,20 @@ export const DeleteContact: React.FC<{
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const email = contactForm.watch("email");
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<DeleteResource
|
||||||
open={open}
|
title="Delete Contact"
|
||||||
onOpenChange={(_open) => (_open !== open ? setOpen(_open) : null)}
|
resourceName={contact.email || ""}
|
||||||
>
|
schema={contactSchema}
|
||||||
<DialogTrigger asChild>
|
isLoading={deleteContactMutation.isPending}
|
||||||
|
onConfirm={onContactDelete}
|
||||||
|
trigger={
|
||||||
<Button variant="ghost" size="sm">
|
<Button variant="ghost" size="sm">
|
||||||
<Trash2 className="h-4 w-4 text-red/80" />
|
<Trash2 className="h-4 w-4 text-red/80" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
}
|
||||||
<DialogContent>
|
confirmLabel="Delete Contact"
|
||||||
<DialogHeader>
|
/>
|
||||||
<DialogTitle>Delete Contact</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Are you sure you want to delete{" "}
|
|
||||||
<span className="font-semibold text-foreground">
|
|
||||||
{contact.email}
|
|
||||||
</span>
|
|
||||||
? You can't reverse this.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="py-2">
|
|
||||||
<Form {...contactForm}>
|
|
||||||
<form
|
|
||||||
onSubmit={contactForm.handleSubmit(onContactDelete)}
|
|
||||||
className="space-y-4"
|
|
||||||
>
|
|
||||||
<FormField
|
|
||||||
control={contactForm.control}
|
|
||||||
name="email"
|
|
||||||
render={({ field, formState }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Email</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field} />
|
|
||||||
</FormControl>
|
|
||||||
{formState.errors.email ? (
|
|
||||||
<FormMessage />
|
|
||||||
) : (
|
|
||||||
<FormDescription className=" text-transparent">
|
|
||||||
.
|
|
||||||
</FormDescription>
|
|
||||||
)}
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant="destructive"
|
|
||||||
disabled={
|
|
||||||
deleteContactMutation.isPending || contact.email !== email
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{deleteContactMutation.isPending ? "Deleting..." : "Delete"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,60 +1,34 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@usesend/ui/src/button";
|
import { Button } from "@usesend/ui/src/button";
|
||||||
import { Input } from "@usesend/ui/src/input";
|
import { DeleteResource } from "~/components/DeleteResource";
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@usesend/ui/src/dialog";
|
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import React, { useState } from "react";
|
import { ContactBook } from "@prisma/client";
|
||||||
import { toast } from "@usesend/ui/src/toaster";
|
import { toast } from "@usesend/ui/src/toaster";
|
||||||
import { Trash2 } from "lucide-react";
|
import { Trash2 } from "lucide-react";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormDescription,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from "@usesend/ui/src/form";
|
|
||||||
import { ContactBook } from "@prisma/client";
|
|
||||||
|
|
||||||
const contactBookSchema = z.object({
|
|
||||||
name: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const DeleteContactBook: React.FC<{
|
export const DeleteContactBook: React.FC<{
|
||||||
contactBook: Partial<ContactBook> & { id: string };
|
contactBook: Partial<ContactBook> & { id: string };
|
||||||
}> = ({ contactBook }) => {
|
}> = ({ contactBook }) => {
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const deleteContactBookMutation =
|
const deleteContactBookMutation =
|
||||||
api.contacts.deleteContactBook.useMutation();
|
api.contacts.deleteContactBook.useMutation();
|
||||||
|
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
||||||
const contactBookForm = useForm<z.infer<typeof contactBookSchema>>({
|
const contactBookSchema = z
|
||||||
resolver: zodResolver(contactBookSchema),
|
.object({
|
||||||
});
|
confirmation: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Please type the contact book name to confirm"),
|
||||||
|
})
|
||||||
|
.refine((data) => data.confirmation === contactBook.name, {
|
||||||
|
message: "Contact book name does not match",
|
||||||
|
path: ["confirmation"],
|
||||||
|
});
|
||||||
|
|
||||||
async function onContactBookDelete(
|
async function onContactBookDelete(
|
||||||
values: z.infer<typeof contactBookSchema>,
|
values: z.infer<typeof contactBookSchema>,
|
||||||
) {
|
) {
|
||||||
if (values.name !== contactBook.name) {
|
|
||||||
contactBookForm.setError("name", {
|
|
||||||
message: "Name does not match",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteContactBookMutation.mutate(
|
deleteContactBookMutation.mutate(
|
||||||
{
|
{
|
||||||
contactBookId: contactBook.id,
|
contactBookId: contactBook.id,
|
||||||
@@ -62,80 +36,26 @@ export const DeleteContactBook: React.FC<{
|
|||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
utils.contacts.getContactBooks.invalidate();
|
utils.contacts.getContactBooks.invalidate();
|
||||||
setOpen(false);
|
|
||||||
toast.success(`Contact book deleted`);
|
toast.success(`Contact book deleted`);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = contactBookForm.watch("name");
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<DeleteResource
|
||||||
open={open}
|
title="Delete Contact Book"
|
||||||
onOpenChange={(_open) => (_open !== open ? setOpen(_open) : null)}
|
resourceName={contactBook.name || ""}
|
||||||
>
|
schema={contactBookSchema}
|
||||||
<DialogTrigger asChild>
|
isLoading={deleteContactBookMutation.isPending}
|
||||||
|
onConfirm={onContactBookDelete}
|
||||||
|
trigger={
|
||||||
<Button variant="ghost" size="sm" className="p-0 hover:bg-transparent ">
|
<Button variant="ghost" size="sm" className="p-0 hover:bg-transparent ">
|
||||||
<Trash2 className="h-[18px] w-[18px] text-red/80 hover:text-red/70" />
|
<Trash2 className="h-[18px] w-[18px] text-red/80 hover:text-red/70" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
}
|
||||||
<DialogContent>
|
confirmLabel="Delete Contact Book"
|
||||||
<DialogHeader>
|
/>
|
||||||
<DialogTitle>Delete Contact Book</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Are you sure you want to delete{" "}
|
|
||||||
<span className="font-semibold text-foreground">
|
|
||||||
{contactBook.name}
|
|
||||||
</span>
|
|
||||||
? You can't reverse this.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="py-2">
|
|
||||||
<Form {...contactBookForm}>
|
|
||||||
<form
|
|
||||||
onSubmit={contactBookForm.handleSubmit(onContactBookDelete)}
|
|
||||||
className="space-y-4"
|
|
||||||
>
|
|
||||||
<FormField
|
|
||||||
control={contactBookForm.control}
|
|
||||||
name="name"
|
|
||||||
render={({ field, formState }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Contact book name</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field} />
|
|
||||||
</FormControl>
|
|
||||||
{formState.errors.name ? (
|
|
||||||
<FormMessage />
|
|
||||||
) : (
|
|
||||||
<FormDescription className=" text-transparent">
|
|
||||||
.
|
|
||||||
</FormDescription>
|
|
||||||
)}
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant="destructive"
|
|
||||||
disabled={
|
|
||||||
deleteContactBookMutation.isPending ||
|
|
||||||
contactBook.name !== name
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{deleteContactBookMutation.isPending
|
|
||||||
? "Deleting..."
|
|
||||||
: "Delete"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,57 +1,31 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@usesend/ui/src/button";
|
import { Button } from "@usesend/ui/src/button";
|
||||||
import { Input } from "@usesend/ui/src/input";
|
import { DeleteResource } from "~/components/DeleteResource";
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@usesend/ui/src/dialog";
|
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import React, { useState } from "react";
|
|
||||||
import { ApiKey } from "@prisma/client";
|
import { ApiKey } from "@prisma/client";
|
||||||
import { toast } from "@usesend/ui/src/toaster";
|
import { toast } from "@usesend/ui/src/toaster";
|
||||||
import { Trash2 } from "lucide-react";
|
import { Trash2 } from "lucide-react";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormDescription,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from "@usesend/ui/src/form";
|
|
||||||
|
|
||||||
const apiKeySchema = z.object({
|
|
||||||
name: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const DeleteApiKey: React.FC<{
|
export const DeleteApiKey: React.FC<{
|
||||||
apiKey: Partial<ApiKey> & { id: number };
|
apiKey: Partial<ApiKey> & { id: number };
|
||||||
}> = ({ apiKey }) => {
|
}> = ({ apiKey }) => {
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const deleteApiKeyMutation = api.apiKey.deleteApiKey.useMutation();
|
const deleteApiKeyMutation = api.apiKey.deleteApiKey.useMutation();
|
||||||
|
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
||||||
const apiKeyForm = useForm<z.infer<typeof apiKeySchema>>({
|
const apiKeySchema = z
|
||||||
resolver: zodResolver(apiKeySchema),
|
.object({
|
||||||
});
|
confirmation: z
|
||||||
|
.string()
|
||||||
async function onDomainDelete(values: z.infer<typeof apiKeySchema>) {
|
.min(1, "Please type the API key name to confirm"),
|
||||||
if (values.name !== apiKey.name) {
|
})
|
||||||
apiKeyForm.setError("name", {
|
.refine((data) => data.confirmation === apiKey.name, {
|
||||||
message: "Name does not match",
|
message: "API key name does not match",
|
||||||
});
|
path: ["confirmation"],
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
|
async function onApiKeyDelete(values: z.infer<typeof apiKeySchema>) {
|
||||||
deleteApiKeyMutation.mutate(
|
deleteApiKeyMutation.mutate(
|
||||||
{
|
{
|
||||||
id: apiKey.id,
|
id: apiKey.id,
|
||||||
@@ -59,75 +33,26 @@ export const DeleteApiKey: React.FC<{
|
|||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
utils.apiKey.invalidate();
|
utils.apiKey.invalidate();
|
||||||
setOpen(false);
|
|
||||||
toast.success(`API key deleted`);
|
toast.success(`API key deleted`);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = apiKeyForm.watch("name");
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<DeleteResource
|
||||||
open={open}
|
title="Delete API key"
|
||||||
onOpenChange={(_open) => (_open !== open ? setOpen(_open) : null)}
|
resourceName={apiKey.name || ""}
|
||||||
>
|
schema={apiKeySchema}
|
||||||
<DialogTrigger asChild>
|
isLoading={deleteApiKeyMutation.isPending}
|
||||||
|
onConfirm={onApiKeyDelete}
|
||||||
|
trigger={
|
||||||
<Button variant="ghost" size="sm">
|
<Button variant="ghost" size="sm">
|
||||||
<Trash2 className="h-4 w-4 text-red/80" />
|
<Trash2 className="h-4 w-4 text-red/80" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
}
|
||||||
<DialogContent>
|
confirmLabel="Delete API key"
|
||||||
<DialogHeader>
|
/>
|
||||||
<DialogTitle>Delete API key</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Are you sure you want to delete{" "}
|
|
||||||
<span className="font-semibold text-foreground">{apiKey.name}</span>
|
|
||||||
? You can't reverse this.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="py-2">
|
|
||||||
<Form {...apiKeyForm}>
|
|
||||||
<form
|
|
||||||
onSubmit={apiKeyForm.handleSubmit(onDomainDelete)}
|
|
||||||
className="space-y-4"
|
|
||||||
>
|
|
||||||
<FormField
|
|
||||||
control={apiKeyForm.control}
|
|
||||||
name="name"
|
|
||||||
render={({ field, formState }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>name</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field} />
|
|
||||||
</FormControl>
|
|
||||||
{formState.errors.name ? (
|
|
||||||
<FormMessage />
|
|
||||||
) : (
|
|
||||||
<FormDescription className=" text-transparent">
|
|
||||||
.
|
|
||||||
</FormDescription>
|
|
||||||
)}
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant="destructive"
|
|
||||||
disabled={
|
|
||||||
deleteApiKeyMutation.isPending || apiKey.name !== name
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{deleteApiKeyMutation.isPending ? "Deleting..." : "Delete"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,60 +1,28 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@usesend/ui/src/button";
|
import { Button } from "@usesend/ui/src/button";
|
||||||
import { Input } from "@usesend/ui/src/input";
|
import { DeleteResource } from "~/components/DeleteResource";
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@usesend/ui/src/dialog";
|
|
||||||
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormDescription,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from "@usesend/ui/src/form";
|
|
||||||
|
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import React, { useState } from "react";
|
|
||||||
import { Domain } from "@prisma/client";
|
import { Domain } from "@prisma/client";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { toast } from "@usesend/ui/src/toaster";
|
import { toast } from "@usesend/ui/src/toaster";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
|
|
||||||
const domainSchema = z.object({
|
|
||||||
domain: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const DeleteDomain: React.FC<{ domain: Domain }> = ({ domain }) => {
|
export const DeleteDomain: React.FC<{ domain: Domain }> = ({ domain }) => {
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const [domainName, setDomainName] = useState("");
|
|
||||||
const deleteDomainMutation = api.domain.deleteDomain.useMutation();
|
const deleteDomainMutation = api.domain.deleteDomain.useMutation();
|
||||||
|
|
||||||
const domainForm = useForm<z.infer<typeof domainSchema>>({
|
|
||||||
resolver: zodResolver(domainSchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
async function onDomainDelete(values: z.infer<typeof domainSchema>) {
|
const domainSchema = z
|
||||||
if (values.domain !== domain.name) {
|
.object({
|
||||||
domainForm.setError("domain", {
|
confirmation: z.string().min(1, "Please type the domain name to confirm"),
|
||||||
message: "Domain name does not match",
|
})
|
||||||
});
|
.refine((data) => data.confirmation === domain.name, {
|
||||||
return;
|
message: "Domain name does not match",
|
||||||
}
|
path: ["confirmation"],
|
||||||
|
});
|
||||||
|
|
||||||
|
async function onDomainDelete(values: z.infer<typeof domainSchema>) {
|
||||||
deleteDomainMutation.mutate(
|
deleteDomainMutation.mutate(
|
||||||
{
|
{
|
||||||
id: domain.id,
|
id: domain.id,
|
||||||
@@ -62,7 +30,6 @@ export const DeleteDomain: React.FC<{ domain: Domain }> = ({ domain }) => {
|
|||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
utils.domain.domains.invalidate();
|
utils.domain.domains.invalidate();
|
||||||
setOpen(false);
|
|
||||||
toast.success(`Domain ${domain.name} deleted`);
|
toast.success(`Domain ${domain.name} deleted`);
|
||||||
router.replace("/domains");
|
router.replace("/domains");
|
||||||
},
|
},
|
||||||
@@ -71,61 +38,19 @@ export const DeleteDomain: React.FC<{ domain: Domain }> = ({ domain }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<DeleteResource
|
||||||
open={open}
|
title="Delete domain"
|
||||||
onOpenChange={(_open) => (_open !== open ? setOpen(_open) : null)}
|
resourceName={domain.name}
|
||||||
>
|
schema={domainSchema}
|
||||||
<DialogTrigger asChild>
|
isLoading={deleteDomainMutation.isPending}
|
||||||
|
onConfirm={onDomainDelete}
|
||||||
|
trigger={
|
||||||
<Button variant="destructive" className="w-[150px]" size="sm">
|
<Button variant="destructive" className="w-[150px]" size="sm">
|
||||||
Delete domain
|
Delete domain
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
}
|
||||||
<DialogContent>
|
confirmLabel="Delete domain"
|
||||||
<DialogHeader>
|
/>
|
||||||
<DialogTitle>Delete domain</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Are you sure you want to delete{" "}
|
|
||||||
<span className="font-semibold text-foreground">{domain.name}</span>
|
|
||||||
? You can't reverse this.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<Form {...domainForm}>
|
|
||||||
<form
|
|
||||||
onSubmit={domainForm.handleSubmit(onDomainDelete)}
|
|
||||||
className="space-y-4"
|
|
||||||
>
|
|
||||||
<FormField
|
|
||||||
control={domainForm.control}
|
|
||||||
name="domain"
|
|
||||||
render={({ field, formState }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Domain</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="subdomain.example.com" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
{formState.errors.domain ? (
|
|
||||||
<FormMessage />
|
|
||||||
) : (
|
|
||||||
<FormDescription className=" text-transparent">
|
|
||||||
.
|
|
||||||
</FormDescription>
|
|
||||||
)}
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant="destructive"
|
|
||||||
disabled={deleteDomainMutation.isPending}
|
|
||||||
>
|
|
||||||
{deleteDomainMutation.isPending ? "Deleting..." : "Delete"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,57 +1,31 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@usesend/ui/src/button";
|
import { Button } from "@usesend/ui/src/button";
|
||||||
import { Input } from "@usesend/ui/src/input";
|
import { DeleteResource } from "~/components/DeleteResource";
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@usesend/ui/src/dialog";
|
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import React, { useState } from "react";
|
import { Template } from "@prisma/client";
|
||||||
import { toast } from "@usesend/ui/src/toaster";
|
import { toast } from "@usesend/ui/src/toaster";
|
||||||
import { Trash2 } from "lucide-react";
|
import { Trash2 } from "lucide-react";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormDescription,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from "@usesend/ui/src/form";
|
|
||||||
import { Template } from "@prisma/client";
|
|
||||||
|
|
||||||
const templateSchema = z.object({
|
|
||||||
name: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const DeleteTemplate: React.FC<{
|
export const DeleteTemplate: React.FC<{
|
||||||
template: Partial<Template> & { id: string };
|
template: Partial<Template> & { id: string };
|
||||||
}> = ({ template }) => {
|
}> = ({ template }) => {
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const deleteTemplateMutation = api.template.deleteTemplate.useMutation();
|
const deleteTemplateMutation = api.template.deleteTemplate.useMutation();
|
||||||
|
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
||||||
const templateForm = useForm<z.infer<typeof templateSchema>>({
|
const templateSchema = z
|
||||||
resolver: zodResolver(templateSchema),
|
.object({
|
||||||
});
|
confirmation: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Please type the template name to confirm"),
|
||||||
|
})
|
||||||
|
.refine((data) => data.confirmation === template.name, {
|
||||||
|
message: "Template name does not match",
|
||||||
|
path: ["confirmation"],
|
||||||
|
});
|
||||||
|
|
||||||
async function onTemplateDelete(values: z.infer<typeof templateSchema>) {
|
async function onTemplateDelete(values: z.infer<typeof templateSchema>) {
|
||||||
if (values.name !== template.name) {
|
|
||||||
templateForm.setError("name", {
|
|
||||||
message: "Name does not match",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteTemplateMutation.mutate(
|
deleteTemplateMutation.mutate(
|
||||||
{
|
{
|
||||||
templateId: template.id,
|
templateId: template.id,
|
||||||
@@ -59,77 +33,26 @@ export const DeleteTemplate: React.FC<{
|
|||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
utils.template.getTemplates.invalidate();
|
utils.template.getTemplates.invalidate();
|
||||||
setOpen(false);
|
|
||||||
toast.success(`Template deleted`);
|
toast.success(`Template deleted`);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = templateForm.watch("name");
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<DeleteResource
|
||||||
open={open}
|
title="Delete Template"
|
||||||
onOpenChange={(_open) => (_open !== open ? setOpen(_open) : null)}
|
resourceName={template.name || ""}
|
||||||
>
|
schema={templateSchema}
|
||||||
<DialogTrigger asChild>
|
isLoading={deleteTemplateMutation.isPending}
|
||||||
|
onConfirm={onTemplateDelete}
|
||||||
|
trigger={
|
||||||
<Button variant="ghost" size="sm" className="p-0 hover:bg-transparent">
|
<Button variant="ghost" size="sm" className="p-0 hover:bg-transparent">
|
||||||
<Trash2 className="h-[18px] w-[18px] text-red/80" />
|
<Trash2 className="h-[18px] w-[18px] text-red/80" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
}
|
||||||
<DialogContent>
|
confirmLabel="Delete Template"
|
||||||
<DialogHeader>
|
/>
|
||||||
<DialogTitle>Delete Template</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Are you sure you want to delete{" "}
|
|
||||||
<span className="font-semibold text-foreground">
|
|
||||||
{template.name}
|
|
||||||
</span>
|
|
||||||
? You can't reverse this.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="py-2">
|
|
||||||
<Form {...templateForm}>
|
|
||||||
<form
|
|
||||||
onSubmit={templateForm.handleSubmit(onTemplateDelete)}
|
|
||||||
className="space-y-4"
|
|
||||||
>
|
|
||||||
<FormField
|
|
||||||
control={templateForm.control}
|
|
||||||
name="name"
|
|
||||||
render={({ field, formState }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>name</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field} />
|
|
||||||
</FormControl>
|
|
||||||
{formState.errors.name ? (
|
|
||||||
<FormMessage />
|
|
||||||
) : (
|
|
||||||
<FormDescription className=" text-transparent">
|
|
||||||
.
|
|
||||||
</FormDescription>
|
|
||||||
)}
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant="destructive"
|
|
||||||
disabled={
|
|
||||||
deleteTemplateMutation.isPending || template.name !== name
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{deleteTemplateMutation.isPending ? "Deleting..." : "Delete"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,201 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button } from "@usesend/ui/src/button";
|
||||||
|
import { Input } from "@usesend/ui/src/input";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@usesend/ui/src/dialog";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@usesend/ui/src/form";
|
||||||
|
|
||||||
|
import { Copy, Check } from "lucide-react";
|
||||||
|
import React, { useState, type ReactNode } from "react";
|
||||||
|
import { toast } from "@usesend/ui/src/toaster";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { useForm, type FieldPath } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
|
||||||
|
const defaultSchema = z.object({
|
||||||
|
confirmation: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
type ConfirmationValues = {
|
||||||
|
confirmation: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ConfirmationSchema = z.ZodType<
|
||||||
|
ConfirmationValues,
|
||||||
|
z.ZodTypeDef,
|
||||||
|
ConfirmationValues
|
||||||
|
>;
|
||||||
|
|
||||||
|
export interface DeleteResourceProps<
|
||||||
|
Schema extends ConfirmationSchema = typeof defaultSchema,
|
||||||
|
> {
|
||||||
|
title: string;
|
||||||
|
resourceName: string;
|
||||||
|
descriptionBody?: ReactNode | string;
|
||||||
|
confirmLabel?: string;
|
||||||
|
isLoading?: boolean;
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
onConfirm: (values: z.infer<Schema>) => void | Promise<void>;
|
||||||
|
open?: boolean;
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
onOpenChange?: (open: boolean) => void;
|
||||||
|
schema?: Schema;
|
||||||
|
trigger?: React.ReactNode;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DeleteResource = <
|
||||||
|
Schema extends ConfirmationSchema = typeof defaultSchema,
|
||||||
|
>({
|
||||||
|
title,
|
||||||
|
resourceName,
|
||||||
|
descriptionBody,
|
||||||
|
confirmLabel = "Delete",
|
||||||
|
isLoading = false,
|
||||||
|
onConfirm,
|
||||||
|
open: controlledOpen,
|
||||||
|
onOpenChange,
|
||||||
|
schema: providedSchema,
|
||||||
|
trigger,
|
||||||
|
children,
|
||||||
|
}: DeleteResourceProps<Schema>) => {
|
||||||
|
const [internalOpen, setInternalOpen] = useState(false);
|
||||||
|
const setOpen = onOpenChange || setInternalOpen;
|
||||||
|
const schema = (providedSchema ?? defaultSchema) as Schema;
|
||||||
|
|
||||||
|
const form = useForm<z.infer<Schema>>({
|
||||||
|
resolver: zodResolver(schema),
|
||||||
|
});
|
||||||
|
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
|
const copyToClipboard = async () => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(resourceName);
|
||||||
|
setCopied(true);
|
||||||
|
|
||||||
|
// Reset copied state after 2 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
setCopied(false);
|
||||||
|
}, 2000);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to copy: ", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (values: z.infer<Schema>) => {
|
||||||
|
await onConfirm(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultDescription = (
|
||||||
|
<>
|
||||||
|
Are you sure you want to delete{" "}
|
||||||
|
<span className="font-semibold text-foreground">{resourceName}</span>? You
|
||||||
|
can't reverse this.
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={controlledOpen !== undefined ? controlledOpen : internalOpen}
|
||||||
|
onOpenChange={setOpen}
|
||||||
|
>
|
||||||
|
{trigger && <DialogTrigger asChild>{trigger}</DialogTrigger>}
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{title}</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
{descriptionBody || defaultDescription}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
onSubmit={form.handleSubmit(handleSubmit)}
|
||||||
|
className="space-y-4"
|
||||||
|
>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
|
Type{" "}
|
||||||
|
<div
|
||||||
|
className="px-1 py-0.5 font-mono border rounded flex gap-1 items-center cursor-pointer hover:bg-muted/30 transition-colors"
|
||||||
|
onClick={copyToClipboard}
|
||||||
|
>
|
||||||
|
<code className="text-sm">{resourceName}</code>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-4 w-4 p-0 hover:bg-transparent"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
copyToClipboard();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{copied ? (
|
||||||
|
<Check className="h-3.5 w-3.5 text-green-600" />
|
||||||
|
) : (
|
||||||
|
<Copy className="h-3.5 w-3.5" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
below
|
||||||
|
</div>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name={"confirmation" as FieldPath<z.infer<Schema>>}
|
||||||
|
render={({ field, formState }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<div className="relative">
|
||||||
|
<Input placeholder={`${resourceName}`} {...field} />
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
{formState.errors.confirmation ? (
|
||||||
|
<FormMessage />
|
||||||
|
) : (
|
||||||
|
<FormDescription className="text-transparent">
|
||||||
|
.
|
||||||
|
</FormDescription>
|
||||||
|
)}
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
<div className="flex justify-end gap-2">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" variant="destructive" disabled={isLoading}>
|
||||||
|
{isLoading ? "Deleting..." : confirmLabel}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeleteResource;
|
||||||
@@ -11,14 +11,14 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/10 disabled:cursor-not-allowed disabled:opacity-50",
|
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground/70 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/10 disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
Input.displayName = "Input";
|
Input.displayName = "Input";
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user