Skip to content
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
8 changes: 7 additions & 1 deletion components/analytics/visitors-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ interface Visitor {
uniqueDocuments: number;
verified: boolean;
totalDuration: number;
viewerName?: string | null;
}

const columns: ColumnDef<Visitor>[] = [
Expand All @@ -63,13 +64,18 @@ const columns: ColumnDef<Visitor>[] = [
<div className="min-w-0 flex-1">
<div className="focus:outline-none">
<p className="flex items-center gap-x-2 overflow-visible text-sm font-medium text-gray-800 dark:text-gray-200">
{row.original.email}{" "}
{row.original.viewerName || row.original.email}{" "}
{row.original.verified && (
<BadgeTooltip content="Verified visitor">
<BadgeCheckIcon className="h-4 w-4 text-emerald-500 hover:text-emerald-600" />
</BadgeTooltip>
)}
</p>
{row.original.viewerName && row.original.email && (
<p className="text-xs text-muted-foreground/60">
{row.original.email}
</p>
)}
<p className="text-sm text-muted-foreground">
{row.original.uniqueDocuments} document
{row.original.uniqueDocuments !== 1 ? "s" : ""} viewed
Expand Down
8 changes: 7 additions & 1 deletion components/visitors/contacts-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type Viewer = {
updatedAt: Date;
totalVisits: number;
lastViewed: Date | null;
viewerName?: string | null;
};

export function ContactsTable({
Expand Down Expand Up @@ -149,8 +150,13 @@ export function ContactsTable({
<div className="min-w-0 flex-1">
<div className="focus:outline-none">
<p className="flex items-center gap-x-2 overflow-visible text-sm font-medium text-gray-800 dark:text-gray-200">
{row.original.email}
{row.original.viewerName || row.original.email}
</p>
{row.original.viewerName && row.original.email && (
<p className="text-xs text-muted-foreground/60">
{row.original.email}
</p>
)}
</div>
</div>
</div>
Expand Down
7 changes: 6 additions & 1 deletion components/visitors/dataroom-viewers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export default function DataroomViewersTable({
<p className="flex items-center gap-x-2 overflow-visible text-sm font-medium text-gray-800 dark:text-gray-200">
{viewer.email ? (
<>
{viewer.email}{" "}
{(viewer as any).viewerName || viewer.email}{" "}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add proper type definition instead of using any cast.

The type assertion (viewer as any).viewerName bypasses TypeScript's type safety. Consider defining a proper type or extending the viewer interface to include the optional viewerName field.

Apply this pattern (similar to other components in this PR):

// At the top of the file or in a types file
interface ViewerWithName {
  viewerName?: string | null;
  // ... other viewer properties
}

Then use:

-{(viewer as any).viewerName || viewer.email}{" "}
+{(viewer as ViewerWithName).viewerName || viewer.email}{" "}

Or better yet, ensure the useDataroomViewers hook returns viewers with the proper type that includes viewerName.

🤖 Prompt for AI Agents
In components/visitors/dataroom-viewers.tsx around line 80, the code uses
(viewer as any).viewerName which bypasses TypeScript safety; define a proper
interface (e.g., ViewerWithName with optional viewerName?: string | null plus
existing viewer fields) either at the top of this file or in a shared types
file, update the component and the useDataroomViewers hook to return viewers
typed as ViewerWithName (or narrow the local viewer variable to that type) and
replace the any cast with viewer.viewerName so TypeScript enforces the optional
field correctly.

{viewer.verified && (
<BadgeTooltip
content="Verified visitor"
Expand Down Expand Up @@ -107,6 +107,11 @@ export default function DataroomViewersTable({
"Anonymous"
)}
</p>
{(viewer as any).viewerName && viewer.email && (
<p className="text-xs text-muted-foreground/60">
{viewer.email}
</p>
)}
Comment on lines +110 to +114
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use proper type definition for consistency.

The same type safety concern applies here as on Line 80.

🤖 Prompt for AI Agents
components/visitors/dataroom-viewers.tsx around lines 110 to 114: currently the
code uses (viewer as any).viewerName which bypasses type checking; replace the
any cast by updating the viewer prop/type so viewer has a proper interface that
includes viewerName and email (or use a narrowed union type) and then reference
viewer.viewerName directly; alternatively add a small type guard function
(isViewerWithName) to narrow the union before accessing viewerName; update the
component/prop type definitions where viewer is declared so the compiler knows
viewerName exists and remove the unsafe any cast.

<p className="text-xs text-muted-foreground/60 sm:text-sm">
{/* {view.link.name ? view.link.name : view.linkId} */}
</p>
Expand Down
7 changes: 6 additions & 1 deletion components/visitors/dataroom-visitors-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export default function DataroomVisitorsTable({
<p className="flex items-center gap-x-2 overflow-visible text-sm font-medium text-gray-800 dark:text-gray-200">
{view.viewerEmail ? (
<>
{view.viewerEmail}{" "}
{view.viewerName || view.viewerEmail}{" "}
{view.verified && (
<BadgeTooltip
content="Verified visitor"
Expand Down Expand Up @@ -141,6 +141,11 @@ export default function DataroomVisitorsTable({
"Anonymous"
)}
</p>
{view.viewerName && view.viewerEmail && (
<p className="text-xs text-muted-foreground/60">
{view.viewerEmail}
</p>
)}
<p className="text-xs text-muted-foreground/60 sm:text-sm">
{view.link.name ? view.link.name : view.linkId}
</p>
Expand Down
62 changes: 37 additions & 25 deletions components/visitors/visitors-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,31 +164,38 @@ export default function VisitorsTable({
key={view.id}
className="group/row opacity-50 grayscale"
>
{/* Name */}
<TableCell>
<div className="flex items-center overflow-visible sm:space-x-3">
<VisitorAvatar
viewerEmail={view.viewerEmail}
isArchived
/>
<div className="min-w-0 flex-1">
<div className="focus:outline-none">
<p className="flex items-center gap-x-2 overflow-visible text-sm font-medium text-gray-800 dark:text-gray-200">
{view.viewerEmail ? (
<>{view.viewerEmail}</>
) : (
"Anonymous"
)}
</p>
<p className="text-xs text-muted-foreground/60 sm:text-sm">
{view.link && view.link.name
? view.link.name
: view.linkId}
</p>
{/* Name */}
<TableCell>
<div className="flex items-center overflow-visible sm:space-x-3">
<VisitorAvatar
viewerEmail={view.viewerEmail}
isArchived
/>
<div className="min-w-0 flex-1">
<div className="focus:outline-none">
<p className="flex items-center gap-x-2 overflow-visible text-sm font-medium text-gray-800 dark:text-gray-200">
{view.viewerEmail ? (
<>
{view.viewerName || view.viewerEmail}
</>
) : (
"Anonymous"
)}
</p>
{view.viewerName && view.viewerEmail && (
<p className="text-xs text-muted-foreground/60">
{view.viewerEmail}
</p>
)}
<p className="text-xs text-muted-foreground/60 sm:text-sm">
{view.link && view.link.name
? view.link.name
: view.linkId}
</p>
</div>
</div>
</div>
</div>
</div>
</TableCell>
</TableCell>
{/* Duration */}
<TableCell className="">
<div className="text-sm text-muted-foreground">
Expand Down Expand Up @@ -277,7 +284,7 @@ export default function VisitorsTable({
<p className="flex items-center gap-x-2 overflow-visible text-sm font-medium text-gray-800 dark:text-gray-200">
{view.viewerEmail ? (
<>
{view.viewerEmail}{" "}
{view.viewerName || view.viewerEmail}{" "}
{view.verified && (
<BadgeTooltip
content="Verified visitor"
Expand Down Expand Up @@ -336,6 +343,11 @@ export default function VisitorsTable({
"Anonymous"
)}
</p>
{view.viewerName && view.viewerEmail && (
<p className="text-xs text-muted-foreground/60">
{view.viewerEmail}
</p>
)}
<p className="text-xs text-muted-foreground/60 sm:text-sm">
{view.link && view.link.name
? view.link.name
Expand Down
4 changes: 4 additions & 0 deletions pages/api/analytics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,9 @@ export default async function handler(
console.error("Error fetching Tinybird data:", error);
}

// Get the name from the most recent view that has a name
const viewerName = viewer.views.find((v) => v.viewerName)?.viewerName;

return {
email: viewer.email,
viewerId: viewer.id,
Expand All @@ -487,6 +490,7 @@ export default async function handler(
uniqueDocuments: uniqueDocuments.size,
verified: viewer.verified,
totalDuration,
viewerName: viewerName || null,
};
}),
);
Expand Down
10 changes: 10 additions & 0 deletions pages/api/teams/[teamId]/datarooms/[id]/viewers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ export default async function handle(
orderBy: {
viewedAt: "desc",
},
select: {
id: true,
viewedAt: true,
downloadedAt: true,
viewerName: true,
},
},
},
});
Expand Down Expand Up @@ -93,11 +99,15 @@ export default async function handle(
});

const returnViews = viewers.map((viewer) => {
// Get the name from the most recent view that has a name
const viewerName = viewer.views.find((v) => v.viewerName)?.viewerName;

return {
...viewer,
dataroomName: dataroom?.name,
lastViewedAt:
viewer.views.length > 0 ? viewer.views[0].viewedAt : null,
viewerName: viewerName || null,
internal: users.some((user) => user.email === viewer.email), // set internal to true if view.viewerEmail is in the users list
};
});
Expand Down
15 changes: 13 additions & 2 deletions pages/api/teams/[teamId]/viewers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,22 @@ export default async function handle(
v."createdAt",
v."updatedAt",
COALESCE(vs.total_visits, 0)::int as "totalVisits",
vs.last_viewed as "lastViewed"
vs.last_viewed as "lastViewed",
vs.viewer_name as "viewerName"
FROM "Viewer" v
LEFT JOIN (
SELECT
"viewerId",
COUNT(*)::int as total_visits,
MAX("viewedAt") as last_viewed
MAX("viewedAt") as last_viewed,
(
SELECT "viewerName"
FROM "View" v2
WHERE v2."viewerId" = "View"."viewerId"
AND v2."viewerName" IS NOT NULL
ORDER BY v2."viewedAt" DESC
LIMIT 1
) as viewer_name
FROM "View"
WHERE "documentId" IS NOT NULL
GROUP BY "viewerId"
Expand All @@ -109,6 +118,7 @@ export default async function handle(
updatedAt: Date;
totalVisits: number;
lastViewed: Date | null;
viewerName: string | null;
}>;

const totalCountResult = await prisma.$queryRaw`
Expand All @@ -128,6 +138,7 @@ export default async function handle(
updatedAt: viewer.updatedAt,
totalVisits: viewer.totalVisits,
lastViewed: viewer.lastViewed,
viewerName: viewer.viewerName,
}));

const response = {
Expand Down