diff --git a/components/datarooms/dataroom-header.tsx b/components/datarooms/dataroom-header.tsx index 155935707..1be3693d2 100644 --- a/components/datarooms/dataroom-header.tsx +++ b/components/datarooms/dataroom-header.tsx @@ -3,6 +3,8 @@ import { useState } from "react"; import LinkSheet from "@/components/links/link-sheet"; import { Button } from "@/components/ui/button"; +import { useDataroomLinks } from "@/lib/swr/use-dataroom"; + export const DataroomHeader = ({ title, description, @@ -13,6 +15,7 @@ export const DataroomHeader = ({ actions?: React.ReactNode[]; }) => { const [isLinkSheetOpen, setIsLinkSheetOpen] = useState(false); + const { links } = useDataroomLinks(); const actionRows: React.ReactNode[][] = []; if (actions) { @@ -38,6 +41,7 @@ export const DataroomHeader = ({ linkType={"DATAROOM_LINK"} isOpen={isLinkSheetOpen} setIsOpen={setIsLinkSheetOpen} + existingLinks={links} /> diff --git a/components/sidebar/nav-user.tsx b/components/sidebar/nav-user.tsx index 21794fb30..d9e696f75 100644 --- a/components/sidebar/nav-user.tsx +++ b/components/sidebar/nav-user.tsx @@ -1,19 +1,27 @@ "use client"; +import { useState } from "react"; + import { - BadgeCheck, - Bell, ChevronsUpDown, - CreditCard, + FileTextIcon, LifeBuoyIcon, LogOut, MailIcon, - Settings2, - Sparkles, } from "lucide-react"; -import { useSession } from "next-auth/react"; +import { signOut, useSession } from "next-auth/react"; +import { toast } from "sonner"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { Dialog, DialogContent } from "@/components/ui/dialog"; import { DropdownMenu, DropdownMenuContent, @@ -32,48 +40,56 @@ import { import { ModeToggle } from "../theme-toggle"; +interface Article { + data: { + slug: string; + title: string; + description?: string; + }; +} + export function NavUser() { const { data: session, status } = useSession(); const { isMobile } = useSidebar(); + const [searchOpen, setSearchOpen] = useState(false); + const [articles, setArticles] = useState([]); + const [loading, setLoading] = useState(false); + + const fetchArticles = async (query?: string) => { + setLoading(true); + try { + const params = new URLSearchParams({ + locale: "en", // or get this from your app's locale + ...(query && { q: query }), + }); + + const res = await fetch(`/api/help?${params}`); + const data = await res.json(); + + if (data.error) { + throw new Error(data.error); + } + + setArticles(data.articles || []); + } catch (error) { + console.error("Error fetching articles:", error); + setArticles([]); // Set empty array on error + } finally { + setLoading(false); + } + }; + return ( - - - - - - - - - {session?.user?.name?.charAt(0) || - session?.user?.email?.charAt(0)} - - -
- - {session?.user?.name || ""} - - - {session?.user?.email || ""} - -
- -
-
- - -
+ <> + + + + +
- -
- - - + + + + + +
+ + + + {session?.user?.name?.charAt(0) || + session?.user?.email?.charAt(0)} + + +
+ + {session?.user?.name || ""} + + + {session?.user?.email || ""} + +
+
+
+ + + {/* User Settings - - - - - - Help Center - - - - Contact Support + */} + + + { + setSearchOpen(true); + fetchArticles(); + }} + > + + Help Center + + { + navigator.clipboard.writeText("support@papermark.io"); + toast.success("support@papermark.io copied to clipboard"); + }} + > + + Contact Support + + + + + signOut({ + callbackUrl: `${window.location.origin}`, + }) + } + > + + Log out -
- - - - Log out - -
-
-
-
+ + + + + + + + + fetchArticles(search)} + /> + + No articles found + + {articles.map((article) => ( + { + window.open( + `${process.env.NEXT_PUBLIC_MARKETING_URL}/help/article/${article.data.slug}`, + "_blank", + ); + setSearchOpen(false); + }} + > + +
+ + {article.data.title} + + {article.data.description && ( + + {article.data.description} + + )} +
+
+ ))} +
+
+
+
+
+ ); } diff --git a/components/visitors/dataroom-visitor-custom-fields.tsx b/components/visitors/dataroom-visitor-custom-fields.tsx new file mode 100644 index 000000000..01c4d53ef --- /dev/null +++ b/components/visitors/dataroom-visitor-custom-fields.tsx @@ -0,0 +1,43 @@ +import { Fragment } from "react"; + +import useSWR from "swr"; + +import { fetcher } from "@/lib/utils"; + +type CustomFieldResponse = { + identifier: string; + label: string; + response: string; +}; + +export default function VisitorCustomFields({ + viewId, + teamId, + dataroomId, +}: { + viewId: string; + teamId: string; + dataroomId: string; +}) { + const { data: customFieldResponse } = useSWR( + `/api/teams/${teamId}/datarooms/${dataroomId}/views/${viewId}/custom-fields`, + fetcher, + ); + + console.log("customFieldResponse", customFieldResponse); + + if (!customFieldResponse) return null; + + return ( +
+
+ {customFieldResponse.map((field, index) => ( + +
{field.label}
+
{field.response}
+
+ ))} +
+
+ ); +} diff --git a/components/visitors/dataroom-visitors-table.tsx b/components/visitors/dataroom-visitors-table.tsx index b72ae9261..aa9a77ac8 100644 --- a/components/visitors/dataroom-visitors-table.tsx +++ b/components/visitors/dataroom-visitors-table.tsx @@ -27,8 +27,9 @@ import { import { BadgeTooltip } from "@/components/ui/tooltip"; import { useDataroomVisits } from "@/lib/swr/use-dataroom"; -import { durationFormat, timeAgo } from "@/lib/utils"; +import { timeAgo } from "@/lib/utils"; +import DataroomVisitorCustomFields from "./dataroom-visitor-custom-fields"; import { DataroomVisitorUserAgent } from "./dataroom-visitor-useragent"; import DataroomVisitHistory from "./dataroom-visitors-history"; import { VisitorAvatar } from "./visitor-avatar"; @@ -201,6 +202,11 @@ export default function DataroomVisitorsTable({ <> + diff --git a/pages/api/teams/[teamId]/datarooms/[id]/links.ts b/pages/api/teams/[teamId]/datarooms/[id]/links.ts index 23a0f5bdf..8067f569e 100644 --- a/pages/api/teams/[teamId]/datarooms/[id]/links.ts +++ b/pages/api/teams/[teamId]/datarooms/[id]/links.ts @@ -62,6 +62,7 @@ export default async function handle( viewedAt: "desc", }, }, + customFields: true, _count: { select: { views: { where: { viewType: "DATAROOM_VIEW" } } }, }, diff --git a/pages/api/teams/[teamId]/datarooms/[id]/views/[viewId]/custom-fields.ts b/pages/api/teams/[teamId]/datarooms/[id]/views/[viewId]/custom-fields.ts new file mode 100644 index 000000000..a22283539 --- /dev/null +++ b/pages/api/teams/[teamId]/datarooms/[id]/views/[viewId]/custom-fields.ts @@ -0,0 +1,80 @@ +import { NextApiRequest, NextApiResponse } from "next"; + +import { authOptions } from "@/pages/api/auth/[...nextauth]"; +import { getServerSession } from "next-auth/next"; + +import { errorhandler } from "@/lib/errorHandler"; +import prisma from "@/lib/prisma"; +import { CustomUser } from "@/lib/types"; + +export default async function handle( + req: NextApiRequest, + res: NextApiResponse, +) { + if (req.method === "GET") { + // GET /api/teams/:teamId/datarooms/:id/views/:viewId/custom-fields + const session = await getServerSession(req, res, authOptions); + if (!session) { + return res.status(401).end("Unauthorized"); + } + + const { + teamId, + id: dataroomId, + viewId, + } = req.query as { + teamId: string; + id: string; + viewId: string; + }; + + const userId = (session.user as CustomUser).id; + + try { + const team = await prisma.team.findUnique({ + where: { + id: teamId, + users: { + some: { + userId: userId, + }, + }, + }, + select: { + id: true, + plan: true, + }, + }); + + if (!team) { + return res.status(401).end("Unauthorized"); + } + + if (team.plan.includes("free")) { + return res.status(403).end("Forbidden"); + } + + const customFields = await prisma.customFieldResponse.findFirst({ + where: { + viewId: viewId, + view: { + dataroomId: dataroomId, + }, + }, + select: { + data: true, + }, + }); + + const data = customFields?.data; + + return res.status(200).json(data); + } catch (error) { + errorhandler(error, res); + } + } else { + // We only allow GET requests + res.setHeader("Allow", ["GET"]); + return res.status(405).end(`Method ${req.method} Not Allowed`); + } +} diff --git a/pages/api/teams/[teamId]/documents/[id]/views/[viewId]/custom-fields.ts b/pages/api/teams/[teamId]/documents/[id]/views/[viewId]/custom-fields.ts index 8e1c1b7ce..89c07af9e 100644 --- a/pages/api/teams/[teamId]/documents/[id]/views/[viewId]/custom-fields.ts +++ b/pages/api/teams/[teamId]/documents/[id]/views/[viewId]/custom-fields.ts @@ -57,6 +57,9 @@ export default async function handle( const customFields = await prisma.customFieldResponse.findFirst({ where: { viewId: viewId, + view: { + documentId: docId, + }, }, select: { data: true, diff --git a/pages/documents/index.tsx b/pages/documents/index.tsx index a710cfebe..83a98bb6f 100644 --- a/pages/documents/index.tsx +++ b/pages/documents/index.tsx @@ -20,7 +20,7 @@ export default function Documents() { return ( -
+

diff --git a/pages/documents/tree/[...name].tsx b/pages/documents/tree/[...name].tsx index e4062bebb..66cbb071f 100644 --- a/pages/documents/tree/[...name].tsx +++ b/pages/documents/tree/[...name].tsx @@ -1,7 +1,7 @@ import { useRouter } from "next/router"; import { useTeam } from "@/context/team-context"; -import { FileIcon, FolderIcon, FolderPlusIcon, PlusIcon } from "lucide-react"; +import { FolderPlusIcon, PlusIcon } from "lucide-react"; import { AddDocumentModal } from "@/components/documents/add-document-modal"; import { DocumentsList } from "@/components/documents/documents-list"; @@ -32,19 +32,6 @@ export default function DocumentTreePage() { Manage all your documents in one place.

- {/*
- - - -
*/}