Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: show custom fields in dataroom analytics #1448

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions components/datarooms/dataroom-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -13,6 +15,7 @@ export const DataroomHeader = ({
actions?: React.ReactNode[];
}) => {
const [isLinkSheetOpen, setIsLinkSheetOpen] = useState<boolean>(false);
const { links } = useDataroomLinks();

const actionRows: React.ReactNode[][] = [];
if (actions) {
Expand All @@ -38,6 +41,7 @@ export const DataroomHeader = ({
linkType={"DATAROOM_LINK"}
isOpen={isLinkSheetOpen}
setIsOpen={setIsLinkSheetOpen}
existingLinks={links}
/>
</div>
</section>
Expand Down
240 changes: 172 additions & 68 deletions components/sidebar/nav-user.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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<Article[]>([]);
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 (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton
size="lg"
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<Avatar className="h-8 w-8 rounded-lg">
<AvatarImage
src={session?.user?.image || ""}
alt={session?.user?.name || ""}
/>
<AvatarFallback className="rounded-lg">
{session?.user?.name?.charAt(0) ||
session?.user?.email?.charAt(0)}
</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">
{session?.user?.name || ""}
</span>
<span className="truncate text-xs">
{session?.user?.email || ""}
</span>
</div>
<ChevronsUpDown className="ml-auto size-4" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
side={isMobile ? "bottom" : "right"}
align="end"
sideOffset={4}
>
<DropdownMenuLabel className="p-0 font-normal">
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton
size="lg"
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<Avatar className="h-8 w-8 rounded-lg">
<AvatarImage
src={session?.user?.image || ""}
Expand All @@ -92,35 +108,123 @@ export function NavUser() {
{session?.user?.email || ""}
</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<ModeToggle />
<DropdownMenuGroup>
<ChevronsUpDown className="ml-auto size-4" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
side={isMobile ? "bottom" : "right"}
align="end"
sideOffset={4}
>
<DropdownMenuLabel className="p-0 font-normal">
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar className="h-8 w-8 rounded-lg">
<AvatarImage
src={session?.user?.image || ""}
alt={session?.user?.name || ""}
/>
<AvatarFallback className="rounded-lg">
{session?.user?.name?.charAt(0) ||
session?.user?.email?.charAt(0)}
</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">
{session?.user?.name || ""}
</span>
<span className="truncate text-xs">
{session?.user?.email || ""}
</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<ModeToggle />
{/* <DropdownMenuGroup>
<DropdownMenuItem>
<Settings2 />
User Settings
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<LifeBuoyIcon />
Help Center
</DropdownMenuItem>
<DropdownMenuItem>
<MailIcon />
Contact Support
</DropdownMenuGroup> */}
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem
onClick={() => {
setSearchOpen(true);
fetchArticles();
}}
>
<LifeBuoyIcon />
Help Center
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
navigator.clipboard.writeText("[email protected]");
toast.success("[email protected] copied to clipboard");
}}
>
<MailIcon />
Contact Support
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() =>
signOut({
callbackUrl: `${window.location.origin}`,
})
}
>
<LogOut />
Log out
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<LogOut />
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>

<Dialog open={searchOpen} onOpenChange={setSearchOpen}>
<DialogContent className="max-w-[550px] gap-0 overflow-hidden border-none p-0 shadow-lg">
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
<CommandInput
placeholder="Search help articles..."
className="h-14 border-none px-4 focus:ring-0 focus-visible:ring-0 focus-visible:ring-offset-0"
onValueChange={(search) => fetchArticles(search)}
/>
<CommandList className="max-h-[400px] overflow-y-auto">
<CommandEmpty>No articles found</CommandEmpty>
<CommandGroup heading="All Articles">
{articles.map((article) => (
<CommandItem
key={article.data.slug}
value={article.data.title}
onSelect={() => {
window.open(
`${process.env.NEXT_PUBLIC_MARKETING_URL}/help/article/${article.data.slug}`,
"_blank",
);
setSearchOpen(false);
}}
>
<FileTextIcon className="mr-2 h-4 w-4 text-[#fb7a00]" />
<div className="flex flex-col">
<span className="text-sm font-medium">
{article.data.title}
</span>
{article.data.description && (
<span className="text-xs text-muted-foreground">
{article.data.description}
</span>
)}
</div>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</DialogContent>
</Dialog>
</>
);
}
43 changes: 43 additions & 0 deletions components/visitors/dataroom-visitor-custom-fields.tsx
Original file line number Diff line number Diff line change
@@ -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<CustomFieldResponse[] | null>(
`/api/teams/${teamId}/datarooms/${dataroomId}/views/${viewId}/custom-fields`,
fetcher,
);

console.log("customFieldResponse", customFieldResponse);

if (!customFieldResponse) return null;

return (
<div className="space-y-2 px-1.5 pb-2 md:px-2">
<dl className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-2">
{customFieldResponse.map((field, index) => (
<Fragment key={index}>
<dt className="text-sm text-muted-foreground">{field.label}</dt>
<dd className="text-sm">{field.response}</dd>
</Fragment>
))}
</dl>
</div>
);
}
8 changes: 7 additions & 1 deletion components/visitors/dataroom-visitors-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -201,6 +202,11 @@ export default function DataroomVisitorsTable({
<>
<TableRow>
<TableCell colSpan={3}>
<DataroomVisitorCustomFields
viewId={view.id}
teamId={view.teamId!}
dataroomId={dataroomId}
/>
<DataroomVisitorUserAgent viewId={view.id} />
</TableCell>
</TableRow>
Expand Down
1 change: 1 addition & 0 deletions pages/api/teams/[teamId]/datarooms/[id]/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export default async function handle(
viewedAt: "desc",
},
},
customFields: true,
_count: {
select: { views: { where: { viewType: "DATAROOM_VIEW" } } },
},
Expand Down
Loading
Loading