mirror of
https://github.com/hanzo-apps/dataroom.git
synced 2026-10-30 14:47:36 +00:00
Papermark dual-licenses: AGPL-3.0 for the tree, a separate commercial
licence for ee/. We hold the first and not the second, so none of the
second may live here.
ee/ was already absent from main, but it was never deleted. 98f0da7fa
("ci: amd64-only build") renamed 150 files out of ee/ byte-identical
into features/, lib/billing/legacy/ (ee/stripe), lib/billing/limits/
(ee/limits), and flattened app/(ee)/ into app/ — putting commercially
licensed code under our AGPL root LICENSE. A rename is not a deletion.
Every path that commit moved is now gone, edited-since or not.
Deleted, with their routes and UI entry points: features/ai and
app/api/ai, features/workflows and app/api/{workflows,workflow-entry},
features/conversations and the Q&A/FAQ surface, features/security/sso
with app/api/auth/saml + app/api/scim + lib/jackson.ts (we use Hanzo
IAM OIDC natively), features/dataroom-invitations, features/templates,
features/access-notifications, features/conversions, features/
permissions. Five of these were gated by lib/featureFlags, which reads
@vercel/edge-config and returns false for every flag when EDGE_CONFIG
is unset — every non-Vercel deployment we run. Those flags are gone too.
Deleted the commercial gating outright rather than reimplementing it:
lib/billing/legacy (ee/stripe) and lib/billing/limits (ee/limits), the
upgrade modals and banners, UpgradeButton, FeaturePreview, PlanBadge,
the trial and blocking modals, /settings/upgrade, /settings/billing and
the billing checkout/seat/pause/cancel API. usePlan() no longer returns
capability booleans — the plan tag describes provisioning, it is not a
permission — and useLimits() is gone with every quota check it fed.
Subscription pause went the same way. Also dropped the npm dependencies
that existed only for the removed features (@boxyhq/saml-jackson, the
@ai-sdk packages, openai, streamdown, use-stick-to-bottom,
@trigger.dev/python) and the Jackson-specific next.config webpack shims.
Rewritten as original Hanzo code where a capability was load-bearing:
- lib/utils/get-file-size-limits.ts — one UPLOAD_LIMITS set plus
getFileSizeLimit(contentType), env-overridable. Operational limits,
not commercial ones, so the tus and browser-upload paths still refuse
oversized files without consulting a plan matrix.
- components/links/link-sheet/permissions-sheet.tsx — a flat, depth
annotated item list with view/download switches over the same
/permission-groups API. Two states, not three. ItemPermission now has
a single definition in lib/types.ts.
- app/api/links/[id]/upload/route.ts — visitor uploads, built around one
authorize() helper shared by GET and POST, and a notification debounce
keyed per (link, viewer) instead of listing and cancelling runs.
There is now exactly one link sheet: the ee DataroomLinkSheet duplicated
the AGPL LinkSheet, which already handled DATAROOM_LINK. Replaced
upstream's Omit<LinkType, "WORKFLOW_LINK"> — Omit on a string union
yields an object type and never narrowed anything — with a real union.
Verified: the S3/tus upload+download harness passes 13/13 against MinIO
after the deletions, and tsc goes from 257 pre-existing errors on
origin/main to 192 with zero files worse than baseline.
Reasoning recorded in LLM.md. LICENSE untouched.
Co-authored-by: Hanzo Dev <[email protected]>
769 lines
26 KiB
TypeScript
769 lines
26 KiB
TypeScript
import Link from "next/link";
|
|
import { useRouter } from "next/router";
|
|
|
|
import { useEffect, useRef, useState } from "react";
|
|
|
|
import { useTeam } from "@/context/team-context";
|
|
import { Document, DocumentVersion } from "@prisma/client";
|
|
import {
|
|
ArrowRightIcon,
|
|
BetweenHorizontalStartIcon,
|
|
ChevronRight,
|
|
CloudDownloadIcon,
|
|
DownloadIcon,
|
|
FileDownIcon,
|
|
FolderIcon,
|
|
MoonIcon,
|
|
ServerIcon,
|
|
SheetIcon,
|
|
SunIcon,
|
|
TrashIcon,
|
|
ViewIcon,
|
|
} from "lucide-react";
|
|
import { useTheme } from "next-themes";
|
|
import { toast } from "sonner";
|
|
import { mutate } from "swr";
|
|
|
|
import { getFile } from "@/lib/files/get-file";
|
|
import useDataroomsSimple from "@/lib/swr/use-datarooms-simple";
|
|
import {
|
|
DocumentWithLinksAndLinkCountAndViewCount,
|
|
DocumentWithVersion,
|
|
} from "@/lib/types";
|
|
import { cn } from "@/lib/utils";
|
|
import { supportsAdvancedExcelMode } from "@/lib/utils/get-content-type";
|
|
import { fileIcon } from "@/lib/utils/get-file-icon";
|
|
|
|
import FileUp from "@/components/shared/icons/file-up";
|
|
import MoreVertical from "@/components/shared/icons/more-vertical";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Collapsible,
|
|
CollapsibleContent,
|
|
CollapsibleTrigger,
|
|
} from "@/components/ui/collapsible";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuGroup,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu";
|
|
|
|
import AdvancedSheet from "../shared/icons/advanced-sheet";
|
|
import PortraitLandscape from "../shared/icons/portrait-landscape";
|
|
import LoadingSpinner from "../ui/loading-spinner";
|
|
import { ButtonTooltip } from "../ui/tooltip";
|
|
import { AddDocumentModal } from "./add-document-modal";
|
|
import { AddToDataroomModal } from "./add-document-to-dataroom-modal";
|
|
import AlertBanner from "./alert";
|
|
import { ExportVisitsModal } from "./export-visits-modal";
|
|
|
|
export default function DocumentHeader({
|
|
prismaDocument,
|
|
primaryVersion,
|
|
teamId,
|
|
actions,
|
|
}: {
|
|
prismaDocument: DocumentWithVersion;
|
|
primaryVersion: DocumentVersion;
|
|
teamId: string;
|
|
actions?: React.ReactNode[];
|
|
}) {
|
|
const router = useRouter();
|
|
const teamInfo = useTeam();
|
|
const { datarooms } = useDataroomsSimple();
|
|
const { theme, systemTheme } = useTheme();
|
|
const isLight =
|
|
theme === "light" || (theme === "system" && systemTheme === "light");
|
|
const [isEditingName, setIsEditingName] = useState<boolean>(false);
|
|
const [menuOpen, setMenuOpen] = useState<boolean>(false);
|
|
const [isFirstClick, setIsFirstClick] = useState<boolean>(false);
|
|
const [orientationLoading, setOrientationLoading] = useState<boolean>(false);
|
|
const [addDataRoomOpen, setAddDataRoomOpen] = useState<boolean>(false);
|
|
const [addDocumentVersion, setAddDocumentVersion] = useState<boolean>(false);
|
|
const [openAddDocModal, setOpenAddDocModal] = useState<boolean>(false);
|
|
const [exportModalOpen, setExportModalOpen] = useState<boolean>(false);
|
|
const nameRef = useRef<HTMLHeadingElement>(null);
|
|
const enterPressedRef = useRef<boolean>(false);
|
|
const dropdownRef = useRef<HTMLDivElement | null>(null);
|
|
|
|
const actionRows: React.ReactNode[][] = [];
|
|
|
|
if (actions) {
|
|
for (let i = 0; i < actions.length; i += 3) {
|
|
actionRows.push(actions.slice(i, i + 3));
|
|
}
|
|
}
|
|
|
|
// Check if document is in any datarooms
|
|
const dataroomCount = prismaDocument.datarooms?.length || 0;
|
|
|
|
const handleCloseAlert = (id: string) => {
|
|
const alert = document.getElementById(id);
|
|
if (alert) {
|
|
alert.style.display = "none";
|
|
}
|
|
};
|
|
|
|
const currentTime = new Date();
|
|
const formattedTime =
|
|
currentTime.getFullYear() +
|
|
"-" +
|
|
String(currentTime.getMonth() + 1).padStart(2, "0") +
|
|
"-" +
|
|
String(currentTime.getDate()).padStart(2, "0") +
|
|
"_" +
|
|
String(currentTime.getHours()).padStart(2, "0") +
|
|
"-" +
|
|
String(currentTime.getMinutes()).padStart(2, "0");
|
|
"-" + String(currentTime.getSeconds()).padStart(2, "0");
|
|
|
|
// https://github.com/radix-ui/primitives/issues/1241#issuecomment-1888232392
|
|
useEffect(() => {
|
|
if (!addDataRoomOpen || !addDocumentVersion) {
|
|
setTimeout(() => {
|
|
document.body.style.pointerEvents = "";
|
|
});
|
|
}
|
|
}, [addDataRoomOpen, addDocumentVersion]);
|
|
|
|
const handleNameSubmit = async () => {
|
|
if (enterPressedRef.current) {
|
|
enterPressedRef.current = false;
|
|
return;
|
|
}
|
|
if (nameRef.current && isEditingName) {
|
|
const newName = nameRef.current.innerText;
|
|
|
|
if (newName !== prismaDocument!.name) {
|
|
const response = await fetch(
|
|
`/api/teams/${teamId}/documents/${prismaDocument!.id}/update-name`,
|
|
{
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
name: newName,
|
|
}),
|
|
},
|
|
);
|
|
|
|
if (response.ok) {
|
|
const { message } = await response.json();
|
|
toast.success(message);
|
|
} else {
|
|
const { message } = await response.json();
|
|
toast.error(message);
|
|
}
|
|
}
|
|
setIsEditingName(false);
|
|
}
|
|
};
|
|
|
|
const preventEnterAndSubmit = (
|
|
event: React.KeyboardEvent<HTMLHeadingElement>,
|
|
) => {
|
|
if (event.key === "Enter") {
|
|
event.preventDefault(); // Prevent the default line break
|
|
setIsEditingName(true);
|
|
enterPressedRef.current = true;
|
|
handleNameSubmit(); // Handle the submit
|
|
if (nameRef.current) {
|
|
nameRef.current.blur(); // Remove focus from the h2 element
|
|
}
|
|
}
|
|
};
|
|
|
|
const changeDocumentOrientation = async () => {
|
|
setOrientationLoading(true);
|
|
try {
|
|
const response = await fetch(
|
|
"/api/teams/" +
|
|
teamId +
|
|
"/documents/" +
|
|
prismaDocument.id +
|
|
"/change-orientation",
|
|
{
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
versionId: primaryVersion.id,
|
|
isVertical: primaryVersion.isVertical ? false : true,
|
|
}),
|
|
},
|
|
);
|
|
|
|
if (response.ok) {
|
|
const { message } = await response.json();
|
|
toast.success(message);
|
|
|
|
mutate(`/api/teams/${teamId}/documents/${prismaDocument.id}`);
|
|
} else {
|
|
const { message } = await response.json();
|
|
toast.error(message);
|
|
}
|
|
} catch (error) {
|
|
console.error("Error:", error);
|
|
toast.error("An error occurred. Please try again.");
|
|
} finally {
|
|
setOrientationLoading(false);
|
|
}
|
|
};
|
|
|
|
const toggleAdvancedExcel = async (document: Document, enabled: boolean) => {
|
|
toast.promise(
|
|
fetch(`/api/teams/${teamId}/documents/${document.id}/advanced-mode`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ enabled }),
|
|
}).then(async (response) => {
|
|
if (!response.ok) {
|
|
const { message } = await response.json();
|
|
throw new Error(message);
|
|
}
|
|
const { message } = await response.json();
|
|
mutate(`/api/teams/${teamId}/documents/${document.id}`);
|
|
if (enabled) {
|
|
handleCloseAlert("enable-advanced-excel-alert");
|
|
}
|
|
return message;
|
|
}),
|
|
{
|
|
loading: enabled
|
|
? "Enabling advanced Excel mode..."
|
|
: "Disabling advanced Excel mode...",
|
|
success: (message) => message,
|
|
error: (error) =>
|
|
error.message ||
|
|
(enabled
|
|
? "Failed to enable advanced Excel mode"
|
|
: "Failed to disable advanced Excel mode"),
|
|
},
|
|
);
|
|
};
|
|
|
|
// Make a document download only or viewable
|
|
const toggleDownloadOnly = async () => {
|
|
toast.promise(
|
|
fetch(
|
|
`/api/teams/${teamId}/documents/${prismaDocument.id}/toggle-download-only`,
|
|
{
|
|
method: "PATCH",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
downloadOnly: !prismaDocument.downloadOnly,
|
|
}),
|
|
},
|
|
).then(() => {
|
|
mutate(`/api/teams/${teamId}/documents/${prismaDocument.id}`);
|
|
}),
|
|
{
|
|
loading: "Updating document...",
|
|
success: `Document is now ${
|
|
!prismaDocument.downloadOnly ? "download only" : "viewable"
|
|
}`,
|
|
error: "Failed to update document",
|
|
},
|
|
);
|
|
};
|
|
|
|
// Toggle dark mode for Notion documents
|
|
const toggleNotionDarkMode = async (darkMode: boolean) => {
|
|
if (prismaDocument.type !== "notion") {
|
|
toast.error("This feature is not available for your document type");
|
|
return;
|
|
}
|
|
|
|
toast.promise(
|
|
fetch(
|
|
`/api/teams/${teamId}/documents/${prismaDocument.id}/toggle-dark-mode`,
|
|
{
|
|
method: "PATCH",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
darkMode: darkMode,
|
|
}),
|
|
},
|
|
).then(() => {
|
|
mutate(`/api/teams/${teamId}/documents/${prismaDocument.id}`);
|
|
}),
|
|
{
|
|
loading: "Updating Notion theme...",
|
|
success: `Notion theme changed to ${darkMode ? "dark" : "light"} mode`,
|
|
error: "Failed to update Notion theme",
|
|
},
|
|
);
|
|
};
|
|
|
|
useEffect(() => {
|
|
function handleClickOutside(event: { target: any }) {
|
|
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
|
setMenuOpen(false);
|
|
setIsFirstClick(false);
|
|
}
|
|
}
|
|
|
|
document.addEventListener("mousedown", handleClickOutside);
|
|
return () => {
|
|
document.removeEventListener("mousedown", handleClickOutside);
|
|
};
|
|
}, []);
|
|
|
|
const handleDeleteDocument = async (documentId: string) => {
|
|
// Prevent the first click from deleting the document
|
|
if (!isFirstClick) {
|
|
setIsFirstClick(true);
|
|
return;
|
|
}
|
|
|
|
toast.promise(
|
|
fetch(`/api/teams/${teamId}/documents/${documentId}`, {
|
|
method: "DELETE",
|
|
}).then(async (res) => {
|
|
if (!res.ok) {
|
|
const error = await res.json();
|
|
throw new Error(error.message || "Failed to delete document");
|
|
}
|
|
mutate(`/api/teams/${teamInfo?.currentTeam?.id}/documents`, null, {
|
|
populateCache: (_, docs) => {
|
|
return docs.filter(
|
|
(doc: DocumentWithLinksAndLinkCountAndViewCount) =>
|
|
doc.id !== documentId,
|
|
);
|
|
},
|
|
revalidate: false,
|
|
});
|
|
setIsFirstClick(false);
|
|
setMenuOpen(false);
|
|
router.push("/documents");
|
|
}),
|
|
{
|
|
loading: "Deleting document...",
|
|
success: "Document deleted successfully.",
|
|
error: (err) => err.message || "Failed to delete document. Try again.",
|
|
},
|
|
);
|
|
};
|
|
|
|
const handleMenuStateChange = (open: boolean) => {
|
|
if (isFirstClick) {
|
|
setMenuOpen(true); // Keep the dropdown open on the first click
|
|
return;
|
|
}
|
|
|
|
// If the menu is closed, reset the isFirstClick state
|
|
if (!open) {
|
|
setIsFirstClick(false);
|
|
setMenuOpen(false); // Ensure the dropdown is closed
|
|
} else {
|
|
setMenuOpen(true); // Open the dropdown
|
|
}
|
|
};
|
|
|
|
const handleButtonClick = (event: any, documentId: string) => {
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
|
|
if (isFirstClick) {
|
|
handleDeleteDocument(documentId);
|
|
setIsFirstClick(false);
|
|
setMenuOpen(false); // Close the dropdown after deleting
|
|
} else {
|
|
setIsFirstClick(true);
|
|
}
|
|
};
|
|
|
|
const downloadDocument = async (documentVersion: DocumentVersion) => {
|
|
if (documentVersion.type === "notion") {
|
|
toast.error("Notion documents cannot be downloaded.");
|
|
return;
|
|
}
|
|
toast.promise(
|
|
(async () => {
|
|
const downloadUrl = await getFile({
|
|
type: documentVersion.storageType,
|
|
data: documentVersion.originalFile ?? documentVersion.file,
|
|
isDownload: true,
|
|
});
|
|
|
|
// Fetch the file from the S3 URL and create blob
|
|
const response = await fetch(downloadUrl);
|
|
const blob = await response.blob();
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement("a");
|
|
a.href = url;
|
|
a.download = prismaDocument.name;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
document.body.removeChild(a);
|
|
})(),
|
|
{
|
|
loading: "Downloading document...",
|
|
success: "Document downloaded successfully",
|
|
error: "Failed to download document",
|
|
},
|
|
);
|
|
};
|
|
|
|
return (
|
|
<header className="flex flex-col gap-y-4">
|
|
<div className="flex items-center justify-between gap-x-8">
|
|
<div className="flex items-center space-x-2">
|
|
{fileIcon({
|
|
fileType: prismaDocument.type ?? "",
|
|
className: "size-7 sm:size-8",
|
|
isLight,
|
|
})}
|
|
|
|
<div className="mt-1 flex flex-col lg:mt-0">
|
|
<h2
|
|
className="rounded-md border border-transparent px-1 py-0.5 text-lg font-semibold tracking-tight text-foreground duration-200 hover:cursor-text hover:border hover:border-border focus-visible:text-lg lg:px-3 lg:py-1 lg:text-xl lg:focus-visible:text-xl xl:text-2xl"
|
|
ref={nameRef}
|
|
contentEditable={true}
|
|
onFocus={() => setIsEditingName(true)}
|
|
onBlur={handleNameSubmit}
|
|
onKeyDown={preventEnterAndSubmit}
|
|
title="Click to edit"
|
|
dangerouslySetInnerHTML={{ __html: prismaDocument.name }}
|
|
/>
|
|
{isEditingName && (
|
|
<span className="mt-1 text-xs text-muted-foreground">
|
|
{`Press <Enter> to save the name.`}
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
{prismaDocument.type === "sheet" &&
|
|
prismaDocument.advancedExcelEnabled && (
|
|
<ButtonTooltip content="Advanced Excel mode">
|
|
<span className="mt-1 text-xs">
|
|
<AdvancedSheet className="h-6 w-6" />
|
|
</span>
|
|
</ButtonTooltip>
|
|
)}
|
|
|
|
{prismaDocument.downloadOnly && (
|
|
<ButtonTooltip content="Download only">
|
|
<span className="text-xs">
|
|
<CloudDownloadIcon className="h-6 w-6" />
|
|
<span className="sr-only">This document is download only</span>
|
|
</span>
|
|
</ButtonTooltip>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex items-center gap-x-4 md:gap-x-2">
|
|
{primaryVersion.type !== "notion" &&
|
|
primaryVersion.type !== "link" &&
|
|
primaryVersion.type !== "sheet" &&
|
|
primaryVersion.type !== "zip" &&
|
|
primaryVersion.type !== "video" &&
|
|
(!orientationLoading ? (
|
|
<ButtonTooltip content="Change orientation">
|
|
<Button
|
|
variant="outline"
|
|
size="icon"
|
|
className="hidden size-8 sm:flex lg:size-9"
|
|
onClick={changeDocumentOrientation}
|
|
title={`Change document orientation to ${primaryVersion.isVertical ? "landscape" : "portrait"}`}
|
|
>
|
|
<PortraitLandscape
|
|
className={cn(
|
|
"h-6 w-6",
|
|
!primaryVersion.isVertical && "-rotate-90 transform",
|
|
)}
|
|
/>
|
|
</Button>
|
|
</ButtonTooltip>
|
|
) : (
|
|
<div className="hidden md:flex">
|
|
<LoadingSpinner className="h-6 w-6" />
|
|
</div>
|
|
))}
|
|
|
|
{primaryVersion.type !== "notion" &&
|
|
primaryVersion.type !== "link" && (
|
|
<AddDocumentModal
|
|
newVersion
|
|
openModal={openAddDocModal}
|
|
setAddDocumentModalOpen={setOpenAddDocModal}
|
|
>
|
|
<ButtonTooltip content="Upload new version">
|
|
<Button
|
|
variant="outline"
|
|
size="icon"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
setOpenAddDocModal(true);
|
|
}}
|
|
className="hidden size-8 md:flex lg:size-9"
|
|
>
|
|
<FileUp className="h-6 w-6" />
|
|
</Button>
|
|
</ButtonTooltip>
|
|
</AddDocumentModal>
|
|
)}
|
|
|
|
<div className="flex items-center gap-x-1">
|
|
{actionRows.map((row, i) => (
|
|
<ul
|
|
key={i.toString()}
|
|
className="flex flex-wrap items-center justify-end gap-x-2 md:flex-nowrap md:gap-x-1"
|
|
>
|
|
{row.map((action, i) => (
|
|
<li key={i}>{action}</li>
|
|
))}
|
|
</ul>
|
|
))}
|
|
</div>
|
|
|
|
<DropdownMenu open={menuOpen} onOpenChange={handleMenuStateChange}>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button
|
|
variant="outline"
|
|
className="h-8 w-8 bg-transparent p-0 lg:h-9 lg:w-9"
|
|
>
|
|
<span className="sr-only">Open menu</span>
|
|
<MoreVertical className="h-4 w-4" />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent
|
|
align="end"
|
|
className="w-[240px]"
|
|
ref={dropdownRef}
|
|
>
|
|
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
|
<DropdownMenuGroup className="block md:hidden">
|
|
{prismaDocument.type !== "notion" &&
|
|
primaryVersion.type !== "video" && (
|
|
<DropdownMenuItem>
|
|
<AddDocumentModal
|
|
newVersion
|
|
setAddDocumentModalOpen={setAddDocumentVersion}
|
|
>
|
|
<button
|
|
title="Add a new version"
|
|
className="flex items-center"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
setAddDocumentVersion(true);
|
|
}}
|
|
>
|
|
<FileUp className="mr-2 h-4 w-4" /> Add new version
|
|
</button>
|
|
</AddDocumentModal>
|
|
</DropdownMenuItem>
|
|
)}
|
|
|
|
<DropdownMenuSeparator />
|
|
</DropdownMenuGroup>
|
|
{prismaDocument.type === "sheet" &&
|
|
supportsAdvancedExcelMode(primaryVersion.contentType) && (
|
|
<DropdownMenuItem
|
|
onClick={() =>
|
|
toggleAdvancedExcel(
|
|
prismaDocument,
|
|
!prismaDocument.advancedExcelEnabled,
|
|
)
|
|
}
|
|
>
|
|
<SheetIcon className="mr-2 h-4 w-4" />
|
|
{prismaDocument.advancedExcelEnabled
|
|
? "Disable Advanced Mode"
|
|
: "Enable Advanced Mode"}
|
|
</DropdownMenuItem>
|
|
)}
|
|
{datarooms && datarooms.length !== 0 && (
|
|
<DropdownMenuItem onClick={() => setAddDataRoomOpen(true)}>
|
|
<BetweenHorizontalStartIcon className="mr-2 h-4 w-4" />
|
|
Add to dataroom
|
|
</DropdownMenuItem>
|
|
)}
|
|
|
|
{primaryVersion.type !== "notion" &&
|
|
primaryVersion.type !== "link" &&
|
|
primaryVersion.type !== "zip" &&
|
|
primaryVersion.type !== "map" &&
|
|
primaryVersion.type !== "email" && (
|
|
<DropdownMenuItem onClick={toggleDownloadOnly}>
|
|
{prismaDocument.downloadOnly ? (
|
|
<>
|
|
<ViewIcon className="mr-2 h-4 w-4" />
|
|
Set viewable
|
|
</>
|
|
) : (
|
|
<>
|
|
<CloudDownloadIcon className="mr-2 h-4 w-4" />
|
|
Set download only
|
|
</>
|
|
)}
|
|
</DropdownMenuItem>
|
|
)}
|
|
|
|
{prismaDocument.type === "notion" && (
|
|
<>
|
|
{primaryVersion.file.includes("mode=dark") ? (
|
|
<DropdownMenuItem
|
|
onClick={() => toggleNotionDarkMode(false)}
|
|
>
|
|
<MoonIcon className="mr-2 h-4 w-4" />
|
|
Disable dark mode
|
|
</DropdownMenuItem>
|
|
) : (
|
|
<DropdownMenuItem
|
|
onClick={() => toggleNotionDarkMode(true)}
|
|
>
|
|
<SunIcon className="mr-2 h-4 w-4" />
|
|
Enable dark mode
|
|
</DropdownMenuItem>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
<DropdownMenuSeparator />
|
|
|
|
{/* Export views in CSV */}
|
|
<DropdownMenuItem onClick={() => setExportModalOpen(true)}>
|
|
<FileDownIcon className="mr-2 h-4 w-4" />
|
|
Export views
|
|
</DropdownMenuItem>
|
|
|
|
{/* Download latest version */}
|
|
{primaryVersion.type !== "notion" &&
|
|
primaryVersion.type !== "link" &&
|
|
primaryVersion.type !== "video" && (
|
|
<DropdownMenuItem
|
|
onClick={() => downloadDocument(primaryVersion)}
|
|
>
|
|
<DownloadIcon className="mr-2 h-4 w-4" />
|
|
Download latest version
|
|
</DropdownMenuItem>
|
|
)}
|
|
|
|
<DropdownMenuSeparator />
|
|
|
|
<DropdownMenuItem
|
|
className="text-destructive focus:bg-destructive focus:text-destructive-foreground"
|
|
onClick={(event) => handleButtonClick(event, prismaDocument.id)}
|
|
>
|
|
<TrashIcon className="mr-2 h-4 w-4" />
|
|
{isFirstClick ? "Really delete?" : "Delete document"}
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Datarooms collapsible section */}
|
|
{dataroomCount > 0 && (
|
|
<div className="mb-2">
|
|
<Collapsible className="w-full">
|
|
<CollapsibleTrigger className="flex w-full items-center text-sm font-medium">
|
|
<div className="flex items-center space-x-2 [&[data-state=open]>svg.chevron]:rotate-180">
|
|
<ChevronRight className="h-4 w-4 transition-transform duration-200" />
|
|
<ServerIcon className="h-4 w-4 text-[#fb7a00]" />
|
|
<span>
|
|
In {dataroomCount} dataroom{dataroomCount > 1 ? "s" : ""}
|
|
</span>
|
|
</div>
|
|
</CollapsibleTrigger>
|
|
<CollapsibleContent className="pl-6 pt-2">
|
|
<ul className="space-y-1">
|
|
{prismaDocument.datarooms?.map((item) => (
|
|
<li
|
|
key={item.dataroom.id}
|
|
className="flex items-center space-x-2 text-sm"
|
|
>
|
|
<ArrowRightIcon className="h-3.5 w-3.5" />
|
|
<Link
|
|
href={`/datarooms/${item.dataroom.id}/documents`}
|
|
className="hover:underline"
|
|
>
|
|
{item.dataroom.name}
|
|
</Link>
|
|
{item.folder ? (
|
|
<Link
|
|
href={`/datarooms/${item.dataroom.id}/documents/${item.folder.path}`}
|
|
className="flex flex-row items-center space-x-2 hover:underline"
|
|
title={`Folder: ${item.folder.name}`}
|
|
>
|
|
<ArrowRightIcon className="h-3.5 w-3.5" />
|
|
<FolderIcon className="mr-1 h-4 w-4" />
|
|
<span className="ml-1 truncate">
|
|
{item.folder.name}
|
|
</span>
|
|
</Link>
|
|
) : (
|
|
<Link
|
|
href={`/datarooms/${item.dataroom.id}/documents`}
|
|
className="flex flex-row items-center space-x-2 hover:underline"
|
|
title="Home"
|
|
>
|
|
<ArrowRightIcon className="h-3.5 w-3.5" />
|
|
<FolderIcon className="mr-1 h-4 w-4" />
|
|
<span className="ml-1 truncate">Home</span>
|
|
</Link>
|
|
)}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</CollapsibleContent>
|
|
</Collapsible>
|
|
</div>
|
|
)}
|
|
|
|
{prismaDocument.type === "sheet" &&
|
|
!prismaDocument.advancedExcelEnabled &&
|
|
supportsAdvancedExcelMode(primaryVersion.contentType) && (
|
|
<AlertBanner
|
|
id="enable-advanced-excel-alert"
|
|
variant="default"
|
|
title="Advanced Excel mode"
|
|
description={
|
|
<>
|
|
You can{" "}
|
|
<span
|
|
className="cursor-pointer underline underline-offset-4 hover:text-primary/80"
|
|
onClick={() => setMenuOpen(true)}
|
|
>
|
|
turn on
|
|
</span>{" "}
|
|
advanced excel mode to improve the file formatting.
|
|
<br /> The advanced mode uses Microsoft viewer.
|
|
</>
|
|
}
|
|
onClose={() => handleCloseAlert("enable-advanced-excel-alert")}
|
|
/>
|
|
)}
|
|
|
|
{addDataRoomOpen ? (
|
|
<AddToDataroomModal
|
|
open={addDataRoomOpen}
|
|
setOpen={setAddDataRoomOpen}
|
|
documentId={prismaDocument.id}
|
|
documentName={prismaDocument.name}
|
|
/>
|
|
) : null}
|
|
|
|
{exportModalOpen && (
|
|
<ExportVisitsModal
|
|
document={prismaDocument}
|
|
teamId={teamId}
|
|
onClose={() => setExportModalOpen(false)}
|
|
/>
|
|
)}
|
|
</header>
|
|
);
|
|
}
|