Skip to content

Commit 5c7d005

Browse files
committed
feat: delete integration
1 parent 07cbb32 commit 5c7d005

File tree

11 files changed

+432
-41
lines changed

11 files changed

+432
-41
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"dependencies": {
2323
"@hookform/resolvers": "^4.1.3",
2424
"@prisma/client": "^6.5.0",
25+
"@radix-ui/react-alert-dialog": "^1.1.6",
2526
"@radix-ui/react-avatar": "^1.1.3",
2627
"@radix-ui/react-checkbox": "^1.1.4",
2728
"@radix-ui/react-dialog": "^1.1.6",

pnpm-lock.yaml

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/api/domain/remove/route.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,36 @@ export async function DELETE(request: NextRequest) {
1414

1515
if (reqPayload.incomingAddress === process.env.API_HOST) {
1616
return NextResponse.json(
17-
{ error: "Unauthorized domain deletion" },
17+
{ error: "Unauthorized domain deletion!" },
1818
{ status: 403 }
1919
);
2020
}
21+
const domainDetails = await prisma.domains.findFirst({
22+
where: {
23+
incomingAddress: reqPayload.incomingAddress,
24+
},
25+
});
26+
27+
if (!domainDetails) {
28+
return NextResponse.json(
29+
{ error: "Domain not registered!" },
30+
{ status: 404 }
31+
);
32+
}
33+
if (domainDetails.isLocked) {
34+
return NextResponse.json(
35+
{ error: "Unauthorized domain deletion!" },
36+
{ status: 404 }
37+
);
38+
}
2139

2240
const { currentConfig, hasExistingRoute } = await validateIncomingDomain(
2341
reqPayload.incomingAddress
2442
);
2543

2644
if (!hasExistingRoute) {
2745
return NextResponse.json(
28-
{ error: "Domain not registered" },
46+
{ error: "Domain not registered in caddy!" },
2947
{ status: 404 }
3048
);
3149
}
@@ -36,8 +54,6 @@ export async function DELETE(request: NextRequest) {
3654
route.match.every((ma) => !ma.host.includes(reqPayload.incomingAddress))
3755
);
3856
newConfigPayload.apps.http.servers.main.routes = filteredRoutes;
39-
40-
await loadCaddyConfig(newConfigPayload);
4157

4258
await prisma.$transaction(async (tx) => {
4359
await tx.caddyConfiguration.create({
@@ -53,6 +69,8 @@ export async function DELETE(request: NextRequest) {
5369
});
5470
});
5571

72+
await loadCaddyConfig(newConfigPayload);
73+
5674
return NextResponse.json(
5775
{
5876
message: "Domain deleted successfully!",

src/components/confirm-dialog.tsx

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { cn } from '@/lib/utils'
2+
import {
3+
AlertDialog,
4+
AlertDialogCancel,
5+
AlertDialogContent,
6+
AlertDialogDescription,
7+
AlertDialogFooter,
8+
AlertDialogHeader,
9+
AlertDialogTitle,
10+
} from '@/components/ui/alert-dialog'
11+
import { Button } from '@/components/ui/button'
12+
13+
interface ConfirmDialogProps {
14+
open: boolean
15+
onOpenChange: (open: boolean) => void
16+
title: React.ReactNode
17+
disabled?: boolean
18+
desc: React.JSX.Element | string
19+
cancelBtnText?: string
20+
confirmText?: React.ReactNode
21+
destructive?: boolean
22+
handleConfirm: () => void
23+
isLoading?: boolean
24+
className?: string
25+
children?: React.ReactNode
26+
}
27+
28+
export function ConfirmDialog(props: ConfirmDialogProps) {
29+
const {
30+
title,
31+
desc,
32+
children,
33+
className,
34+
confirmText,
35+
cancelBtnText,
36+
destructive,
37+
isLoading,
38+
disabled = false,
39+
handleConfirm,
40+
...actions
41+
} = props
42+
return (
43+
<AlertDialog {...actions}>
44+
<AlertDialogContent className={cn(className && className)}>
45+
<AlertDialogHeader className='text-left'>
46+
<AlertDialogTitle>{title}</AlertDialogTitle>
47+
<AlertDialogDescription asChild>
48+
<div>{desc}</div>
49+
</AlertDialogDescription>
50+
</AlertDialogHeader>
51+
{children}
52+
<AlertDialogFooter>
53+
<AlertDialogCancel disabled={isLoading}>
54+
{cancelBtnText ?? 'Cancel'}
55+
</AlertDialogCancel>
56+
<Button
57+
variant={destructive ? 'destructive' : 'default'}
58+
onClick={handleConfirm}
59+
disabled={disabled || isLoading}
60+
loading={isLoading}
61+
>
62+
{confirmText ?? 'Continue'}
63+
</Button>
64+
</AlertDialogFooter>
65+
</AlertDialogContent>
66+
</AlertDialog>
67+
)
68+
}

src/components/proxies/add-proxy-dialog.tsx

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -93,30 +93,18 @@ export function AddProxyDialog({ open, onClose }: Props) {
9393
</FormItem>
9494
)}
9595
/>
96-
{/* <FormField
96+
<FormField
9797
control={form.control}
98-
name='enableHttps'
98+
name="enableHttps"
9999
render={({ field: {
100100
value,
101-
onChange
102-
...restFieldValues
101+
onChange,
102+
...restField
103103
} }) => (
104-
<FormItem className='space-y-1 flex items-center justify-start'>
105-
<FormLabel>Enable HTTPS</FormLabel>
106-
<FormControl>
107-
<Checkbox checked={value} {...restFieldValues} />
108-
</FormControl>
109-
<FormMessage />
110-
</FormItem>
111-
)}
112-
/> */}<FormField
113-
control={form.control}
114-
name="enableHttps"
115-
render={({ field }) => (
116104
<FormItem className="space-y-1 flex items-center justify-start">
117105
<FormLabel>Enable HTTPS</FormLabel>
118106
<FormControl>
119-
<Checkbox checked={field.value} onCheckedChange={field.onChange} />
107+
<Checkbox checked={value} onCheckedChange={onChange} {...restField} />
120108
</FormControl>
121109
<FormMessage />
122110
</FormItem>

src/components/proxies/proxies.tsx

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { DomainWithCheckResults } from "@/app/api/domain/domain-types";
2-
import { Check, X } from "lucide-react";
2+
import { Check, Trash, X } from "lucide-react";
33
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
44
import { Badge } from "../ui/badge";
5+
import { Button } from "../ui/button";
6+
import ProxyDeleteConfirm from "./proxy-delete-confirm";
7+
import { useState } from "react";
58

69
type Props = {
710
proxyData: {
@@ -10,10 +13,14 @@ type Props = {
1013
} | undefined
1114
}
1215

13-
type ProxyRecordProps = {
16+
interface ProxyRecordProps {
1417
record: DomainWithCheckResults
1518
}
1619

20+
interface ProxyCheckResults extends ProxyRecordProps {
21+
handleDeleteClick: (selectedProxy: DomainWithCheckResults) => void
22+
}
23+
1724
const ProxyRecord = ({ record }: ProxyRecordProps) => {
1825
console.log(record)
1926
return (
@@ -33,9 +40,9 @@ const ProxyRecord = ({ record }: ProxyRecordProps) => {
3340
)
3441
}
3542

36-
const ProxyRecordCheckResults = ({ record }: ProxyRecordProps) => {
43+
const ProxyRecordCheckResults = ({ record, handleDeleteClick }: ProxyCheckResults) => {
3744
return (
38-
<div className="flex items-center justify-end gap-6">
45+
<div className="flex items-center justify-start gap-6">
3946
<div className="flex items-center justify-start gap-1 text-md text-gray-500">
4047
<Tooltip>
4148
<TooltipTrigger asChild>
@@ -65,25 +72,54 @@ const ProxyRecordCheckResults = ({ record }: ProxyRecordProps) => {
6572
</TooltipContent>
6673
</Tooltip>
6774
</div>
75+
<Button
76+
size='icon'
77+
variant='ghost'
78+
disabled={record.isLocked}
79+
onClick={() => handleDeleteClick(record)}
80+
className="cursor-pointer hover:bg-red-100 text-red-400 hover:text-red-500"
81+
>
82+
<Trash />
83+
</Button>
6884
</div>
6985
)
7086
}
7187

7288
const Proxies = ({ proxyData }: Props) => {
89+
const [openDelete, setOpenDelete] = useState(false);
90+
const [selectedProxy, setSelectedProxy] = useState<DomainWithCheckResults | null>(null)
91+
92+
const handleDeleteCancel = () => {
93+
setOpenDelete(false)
94+
setSelectedProxy(null);
95+
}
96+
97+
const handleDeleteClick = (selectedProxy: DomainWithCheckResults) => {
98+
setSelectedProxy(selectedProxy);
99+
setOpenDelete(true)
100+
}
101+
73102
return (
74-
<div className="space-y-4 mt-2 overflow-y-scroll">
75-
<div>
76-
Found <span className="font-bold">{proxyData?.total}</span> record{proxyData && proxyData?.total > 1 ? 's.' : '.'}
77-
</div>
78-
<div className="space-y-4 pr-4">
79-
{proxyData?.data.map((record, index) => (
80-
<div key={index} className="border-l-4 border-gray-600 pl-4 pr-2 py-1 flex items-center justify-between">
81-
<ProxyRecord record={record} />
82-
<ProxyRecordCheckResults record={record} />
83-
</div>
84-
))}
103+
<>
104+
<div className="space-y-4 mt-2 overflow-y-scroll">
105+
<div>
106+
Found <span className="font-bold">{proxyData?.total}</span> record{proxyData && proxyData?.total > 1 ? 's.' : '.'}
107+
</div>
108+
<div className="space-y-4 pr-4">
109+
{proxyData?.data.map((record, index) => (
110+
<div key={index} className="border-l-4 border-gray-600 pl-4 pr-2 py-1 flex items-center justify-between">
111+
<ProxyRecord record={record} />
112+
<ProxyRecordCheckResults handleDeleteClick={handleDeleteClick} record={record} />
113+
</div>
114+
))}
115+
</div>
85116
</div>
86-
</div>
117+
<ProxyDeleteConfirm
118+
open={openDelete}
119+
onCancel={handleDeleteCancel}
120+
proxy={selectedProxy}
121+
/>
122+
</>
87123
);
88124
};
89125

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { DeleteDomainValues } from '@/app/api/domain/domain-schema';
2+
import { useDeleteDomain } from '@/hooks/domains/domain.hooks';
3+
import { IconAlertTriangle } from '@tabler/icons-react';
4+
import { FC } from 'react'
5+
import { ConfirmDialog } from '../confirm-dialog';
6+
import { DomainWithCheckResults } from '@/app/api/domain/domain-types';
7+
8+
type Props = {
9+
open: boolean;
10+
onCancel: VoidFunction;
11+
proxy: DomainWithCheckResults | null;
12+
}
13+
14+
const ProxyDeleteConfirm: FC<Props> = ({
15+
open,
16+
onCancel,
17+
proxy,
18+
}) => {
19+
if(!proxy) return null;
20+
21+
const deleteDomainMutation = useDeleteDomain()
22+
const handleConfirmDelete = async () => {
23+
await deleteDomainMutation.mutateAsync({
24+
incomingAddress: proxy.incomingAddress
25+
})
26+
onCancel()
27+
}
28+
29+
return (
30+
<ConfirmDialog
31+
key='delete-role-confirm'
32+
open={open}
33+
onOpenChange={onCancel}
34+
handleConfirm={handleConfirmDelete}
35+
isLoading={deleteDomainMutation.isPending}
36+
title={
37+
<span className='text-destructive'>
38+
<IconAlertTriangle
39+
className='mr-1 inline-block stroke-destructive'
40+
size={18}
41+
/>{' '}
42+
Delete Proxy
43+
</span>
44+
}
45+
desc={
46+
<div className='space-y-4'>
47+
<p className='mb-2'>
48+
Are you sure you want to delete{' '}
49+
<span className='font-bold'>{proxy.incomingAddress}</span>?
50+
<br />
51+
This cannot be undone.
52+
</p>
53+
</div>
54+
}
55+
confirmText='Delete'
56+
destructive
57+
/>
58+
)
59+
}
60+
61+
export default ProxyDeleteConfirm

src/components/proxies/view-raw-dialog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export function ViewRawDialog({ open, onClose }: Props) {
3535
<DialogHeader className='text-left'>
3636
<DialogTitle>{'Raw Configuration'}</DialogTitle>
3737
<DialogDescription>
38-
{'Enter the details below. '} Click save when you&apos;re done.
38+
Snippet below shows the raw caddy configuration in JSON format.
3939
</DialogDescription>
4040
</DialogHeader>
4141
<div className='relative h-[560px] bg-gray-900 p-4 rounded-lg overflow-auto'>

0 commit comments

Comments
 (0)