mirror of
https://github.com/hanzo-apps/dataroom.git
synced 2026-10-30 14:47:36 +00:00
227 lines
6.3 KiB
TypeScript
227 lines
6.3 KiB
TypeScript
import { useRouter } from "next/router";
|
|
|
|
import {
|
|
Dispatch,
|
|
SetStateAction,
|
|
useCallback,
|
|
useEffect,
|
|
useMemo,
|
|
useRef,
|
|
useState,
|
|
} from "react";
|
|
|
|
import { useTeam } from "@/context/team-context";
|
|
import { toast } from "sonner";
|
|
import { mutate } from "swr";
|
|
|
|
import { useAnalytics } from "@/lib/analytics";
|
|
import { LinkWithViews } from "@/lib/types";
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
import { DialogDescription, DialogTitle } from "@/components/ui/dialog";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Modal } from "@/components/ui/modal";
|
|
|
|
function DeleteLinkModal({
|
|
showDeleteLinkModal,
|
|
setShowDeleteLinkModal,
|
|
link,
|
|
targetType,
|
|
}: {
|
|
showDeleteLinkModal: boolean;
|
|
setShowDeleteLinkModal: Dispatch<SetStateAction<boolean>>;
|
|
link: LinkWithViews | null;
|
|
targetType: "DOCUMENT" | "DATAROOM";
|
|
}) {
|
|
const router = useRouter();
|
|
const teamInfo = useTeam();
|
|
const teamId = teamInfo?.currentTeam?.id;
|
|
const analytics = useAnalytics();
|
|
|
|
const [deleting, setDeleting] = useState(false);
|
|
const [isValid, setIsValid] = useState(false);
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
|
|
// Reset validation and clear input when modal opens
|
|
useEffect(() => {
|
|
if (showDeleteLinkModal) {
|
|
setIsValid(false);
|
|
if (inputRef.current) {
|
|
inputRef.current.value = "";
|
|
}
|
|
}
|
|
}, [showDeleteLinkModal]);
|
|
|
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const { value } = e.target;
|
|
// Check if the input matches the pattern
|
|
if (value === "permanently delete") {
|
|
setIsValid(true);
|
|
} else {
|
|
setIsValid(false);
|
|
}
|
|
};
|
|
|
|
async function deleteLink(linkId: string) {
|
|
setDeleting(true);
|
|
|
|
try {
|
|
const response = await fetch(
|
|
`/api/teams/${teamInfo?.currentTeam?.id}/links/${linkId}`,
|
|
{
|
|
method: "DELETE",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
},
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(
|
|
error.error || error.message || "Failed to delete link",
|
|
);
|
|
}
|
|
|
|
analytics.capture("Link Deleted", {
|
|
team: teamInfo?.currentTeam?.id,
|
|
linkId,
|
|
linkType: link?.linkType,
|
|
viewCount: link?._count.views || 0,
|
|
});
|
|
|
|
// Mutate the links cache based on target type
|
|
const endpointTargetType = `${targetType.toLowerCase()}s`; // "documents" or "datarooms"
|
|
const targetId = link?.documentId || link?.dataroomId;
|
|
|
|
await mutate(
|
|
`/api/teams/${teamInfo?.currentTeam?.id}/${endpointTargetType}/${encodeURIComponent(
|
|
targetId ?? "",
|
|
)}/links`,
|
|
);
|
|
|
|
// If this is a group link, also mutate the group-specific links cache
|
|
if (link?.groupId) {
|
|
await mutate(
|
|
`/api/teams/${teamInfo?.currentTeam?.id}/${endpointTargetType}/${encodeURIComponent(
|
|
targetId ?? "",
|
|
)}/groups/${link.groupId}/links`,
|
|
);
|
|
}
|
|
|
|
setShowDeleteLinkModal(false);
|
|
} finally {
|
|
setDeleting(false);
|
|
}
|
|
}
|
|
|
|
if (!link) return null;
|
|
|
|
const viewCount = link._count.views || 0;
|
|
|
|
return (
|
|
<Modal
|
|
showModal={showDeleteLinkModal}
|
|
setShowModal={setShowDeleteLinkModal}
|
|
noBackdropBlur
|
|
>
|
|
<div className="flex flex-col items-center justify-center space-y-3 border-b border-border bg-white px-4 py-4 pt-8 dark:border-gray-900 dark:bg-gray-900 sm:px-8">
|
|
<DialogTitle className="text-2xl">Delete Link</DialogTitle>
|
|
<DialogDescription className="space-y-2">
|
|
<p>
|
|
<strong>Warning</strong>: This will delete the link{" "}
|
|
<span className="font-semibold">
|
|
{link.name || `Link #${link.id.slice(-5)}`}
|
|
</span>
|
|
. The link will no longer be accessible.
|
|
</p>
|
|
<p>
|
|
All link data including{" "}
|
|
<strong>
|
|
{viewCount} view{viewCount !== 1 ? "s" : ""}
|
|
</strong>{" "}
|
|
and visitor analytics will be preserved for historical reporting.
|
|
The link will be marked as deleted and archived.
|
|
</p>
|
|
</DialogDescription>
|
|
</div>
|
|
|
|
<form
|
|
onSubmit={async (e) => {
|
|
e.preventDefault();
|
|
toast.promise(deleteLink(link.id), {
|
|
loading: "Deleting link...",
|
|
success: "Link deleted successfully!",
|
|
error: (err) => err,
|
|
});
|
|
}}
|
|
className="flex flex-col space-y-6 bg-muted px-4 py-8 text-left dark:bg-gray-900 sm:px-8"
|
|
>
|
|
<div>
|
|
<label
|
|
htmlFor="verification"
|
|
className="block text-sm text-muted-foreground"
|
|
>
|
|
To confirm deletion, type{" "}
|
|
<span className="font-semibold text-foreground">
|
|
permanently delete
|
|
</span>{" "}
|
|
below
|
|
</label>
|
|
<div className="relative mt-1 rounded-md shadow-sm">
|
|
<Input
|
|
ref={inputRef}
|
|
type="text"
|
|
name="verification"
|
|
id="verification"
|
|
pattern="permanently delete"
|
|
required
|
|
autoComplete="off"
|
|
className="bg-white dark:border-gray-500 dark:bg-gray-800 focus:dark:bg-transparent"
|
|
onInput={handleInputChange}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<Button
|
|
variant="destructive"
|
|
type="submit"
|
|
loading={deleting}
|
|
disabled={!isValid}
|
|
>
|
|
Confirm delete link
|
|
</Button>
|
|
</form>
|
|
</Modal>
|
|
);
|
|
}
|
|
|
|
export function useDeleteLinkModal({
|
|
link,
|
|
targetType,
|
|
}: {
|
|
link: LinkWithViews | null;
|
|
targetType: "DOCUMENT" | "DATAROOM";
|
|
}) {
|
|
const [showDeleteLinkModal, setShowDeleteLinkModal] = useState(false);
|
|
|
|
const DeleteLinkModalCallback = useCallback(() => {
|
|
return (
|
|
<DeleteLinkModal
|
|
showDeleteLinkModal={showDeleteLinkModal}
|
|
setShowDeleteLinkModal={setShowDeleteLinkModal}
|
|
link={link}
|
|
targetType={targetType}
|
|
/>
|
|
);
|
|
}, [showDeleteLinkModal, setShowDeleteLinkModal, link, targetType]);
|
|
|
|
return useMemo(
|
|
() => ({
|
|
setShowDeleteLinkModal,
|
|
DeleteLinkModal: DeleteLinkModalCallback,
|
|
}),
|
|
[setShowDeleteLinkModal, DeleteLinkModalCallback],
|
|
);
|
|
}
|