From 7a0f106bc3b74615b0c81e5fb9123a9ee0565f40 Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Wed, 18 Mar 2026 10:49:06 +0100 Subject: [PATCH] feat: add DeleteSiteModal with soft-delete and permanent-delete options --- components/sites/DeleteSiteModal.tsx | 221 +++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 components/sites/DeleteSiteModal.tsx diff --git a/components/sites/DeleteSiteModal.tsx b/components/sites/DeleteSiteModal.tsx new file mode 100644 index 0000000..33c6c4e --- /dev/null +++ b/components/sites/DeleteSiteModal.tsx @@ -0,0 +1,221 @@ +'use client' + +import { useState } from 'react' +import { createPortal } from 'react-dom' +import { motion, AnimatePresence } from 'framer-motion' +import { toast } from '@ciphera-net/ui' +import { getAuthErrorMessage } from '@ciphera-net/ui' +import { AlertTriangleIcon, XIcon } from '@ciphera-net/ui' +import { deleteSite, permanentDeleteSite } from '@/lib/api/sites' + +interface DeleteSiteModalProps { + open: boolean + onClose: () => void + onDeleted: () => void + siteName: string + siteDomain: string + siteId: string +} + +export default function DeleteSiteModal({ open, onClose, onDeleted, siteName, siteDomain, siteId }: DeleteSiteModalProps) { + const [deleteConfirm, setDeleteConfirm] = useState('') + const [isDeleting, setIsDeleting] = useState(false) + const [showPermanent, setShowPermanent] = useState(false) + const [permanentConfirm, setPermanentConfirm] = useState('') + const [isPermanentDeleting, setIsPermanentDeleting] = useState(false) + + const handleClose = () => { + setDeleteConfirm('') + setShowPermanent(false) + setPermanentConfirm('') + setIsDeleting(false) + setIsPermanentDeleting(false) + onClose() + } + + const handleSoftDelete = async () => { + if (deleteConfirm !== 'DELETE') return + setIsDeleting(true) + try { + await deleteSite(siteId) + toast.success('Site scheduled for deletion. You have 7 days to restore it.') + handleClose() + onDeleted() + } catch (error: unknown) { + toast.error(getAuthErrorMessage(error) || 'Failed to delete site') + } finally { + setIsDeleting(false) + } + } + + const handlePermanentDelete = async () => { + if (permanentConfirm !== siteDomain) return + setIsPermanentDeleting(true) + try { + await permanentDeleteSite(siteId) + toast.success('Site permanently deleted') + handleClose() + onDeleted() + } catch (error: unknown) { + toast.error(getAuthErrorMessage(error) || 'Failed to permanently delete site') + } finally { + setIsPermanentDeleting(false) + } + } + + if (typeof document === 'undefined') return null + + return createPortal( + + {open && ( + + +
+

Delete {siteName || 'Site'}?

+ +
+ + {!showPermanent ? ( + <> +

+ This site will be scheduled for deletion with a 7-day grace period. You can restore it at any time during this period. +

+ +
+
+ + + Site will stop collecting data immediately + +
+
+ + + All data will be purged after 7 days + +
+
+ +
+
+ + setDeleteConfirm(e.target.value)} + autoComplete="off" + className="w-full px-3 py-2 text-sm border border-neutral-300 dark:border-neutral-700 rounded-lg bg-white dark:bg-neutral-800 text-neutral-900 dark:text-white placeholder-neutral-400 focus:outline-none focus:ring-2 focus:ring-red-500 dark:focus:ring-red-400" + placeholder="DELETE" + /> +
+ +
+ + +
+ + +
+ + ) : ( + <> +

+ This action is irreversible. The site and all its data will be permanently deleted immediately. +

+ +
+
+ + + All analytics data will be permanently lost + +
+
+ + + This cannot be undone + +
+
+ +
+
+ + setPermanentConfirm(e.target.value)} + autoComplete="off" + className="w-full px-3 py-2 text-sm border border-neutral-300 dark:border-neutral-700 rounded-lg bg-white dark:bg-neutral-800 text-neutral-900 dark:text-white placeholder-neutral-400 focus:outline-none focus:ring-2 focus:ring-red-500 dark:focus:ring-red-400" + placeholder={siteDomain} + /> +
+ +
+ + +
+
+ + )} +
+
+ )} +
, + document.body + ) +}