diff --git a/apps/remix/app/components/general/document/document-history-sheet-changes.tsx b/apps/remix/app/components/general/document/document-history-sheet-changes.tsx
deleted file mode 100644
index 577dbc473..000000000
--- a/apps/remix/app/components/general/document/document-history-sheet-changes.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import React from 'react';
-
-import { Badge } from '@documenso/ui/primitives/badge';
-
-export type DocumentHistorySheetChangesProps = {
- values: {
- key: string | React.ReactNode;
- value: string | React.ReactNode;
- }[];
-};
-
-export const DocumentHistorySheetChanges = ({ values }: DocumentHistorySheetChangesProps) => {
- return (
-
- {values.map(({ key, value }, i) => (
-
- {key}:
- {value}
-
- ))}
-
- );
-};
diff --git a/apps/remix/app/components/general/document/document-history-sheet.tsx b/apps/remix/app/components/general/document/document-history-sheet.tsx
deleted file mode 100644
index f7c70bc84..000000000
--- a/apps/remix/app/components/general/document/document-history-sheet.tsx
+++ /dev/null
@@ -1,410 +0,0 @@
-import { useMemo, useState } from 'react';
-
-import { useLingui } from '@lingui/react';
-import { Trans } from '@lingui/react/macro';
-import { ArrowRightIcon, Loader } from 'lucide-react';
-import { DateTime } from 'luxon';
-import { match } from 'ts-pattern';
-import { UAParser } from 'ua-parser-js';
-
-import { DOCUMENT_AUDIT_LOG_EMAIL_FORMAT } from '@documenso/lib/constants/document-audit-logs';
-import { DOCUMENT_AUTH_TYPES } from '@documenso/lib/constants/document-auth';
-import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
-import { formatDocumentAuditLogAction } from '@documenso/lib/utils/document-audit-logs';
-import { trpc } from '@documenso/trpc/react';
-import { cn } from '@documenso/ui/lib/utils';
-import { Avatar, AvatarFallback } from '@documenso/ui/primitives/avatar';
-import { Badge } from '@documenso/ui/primitives/badge';
-import { Button } from '@documenso/ui/primitives/button';
-import { Sheet, SheetContent, SheetTrigger } from '@documenso/ui/primitives/sheet';
-
-import { DocumentHistorySheetChanges } from './document-history-sheet-changes';
-
-export type DocumentHistorySheetProps = {
- documentId: number;
- userId: number;
- isMenuOpen?: boolean;
- onMenuOpenChange?: (_value: boolean) => void;
- children?: React.ReactNode;
-};
-
-export const DocumentHistorySheet = ({
- documentId,
- userId,
- isMenuOpen,
- onMenuOpenChange,
- children,
-}: DocumentHistorySheetProps) => {
- const { _, i18n } = useLingui();
-
- const [isUserDetailsVisible, setIsUserDetailsVisible] = useState(false);
-
- const {
- data,
- isLoading,
- isLoadingError,
- refetch,
- hasNextPage,
- fetchNextPage,
- isFetchingNextPage,
- } = trpc.document.findDocumentAuditLogs.useInfiniteQuery(
- {
- documentId,
- },
- {
- getNextPageParam: (lastPage) => lastPage.nextCursor,
- placeholderData: (previousData) => previousData,
- },
- );
-
- const documentAuditLogs = useMemo(() => (data?.pages ?? []).flatMap((page) => page.data), [data]);
-
- const extractBrowser = (userAgent?: string | null) => {
- if (!userAgent) {
- return 'Unknown';
- }
-
- const parser = new UAParser(userAgent);
-
- parser.setUA(userAgent);
-
- const result = parser.getResult();
-
- return result.browser.name;
- };
-
- /**
- * Applies the following formatting for a given text:
- * - Uppercase first lower, lowercase rest
- * - Replace _ with spaces
- *
- * @param text The text to format
- * @returns The formatted text
- */
- const formatGenericText = (text?: string | string[] | null): string => {
- if (!text) {
- return '';
- }
-
- if (Array.isArray(text)) {
- return text.map((t) => formatGenericText(t)).join(', ');
- }
-
- return (text.charAt(0).toUpperCase() + text.slice(1).toLowerCase()).replaceAll('_', ' ');
- };
-
- return (
-
- {children && {children}}
-
-
-
-
- Document history
-
-
-
-
- {isLoading && (
-
-
-
- )}
-
- {isLoadingError && (
-
-
- Unable to load document history
-
-
-
- )}
-
- {data && (
-
- )}
-
-
- );
-};
diff --git a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
index 6513bddc0..99c2bd243 100644
--- a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
+++ b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
@@ -1,7 +1,7 @@
import { useLingui } from '@lingui/react';
import { Plural, Trans } from '@lingui/react/macro';
import { DocumentStatus, TeamMemberRole } from '@prisma/client';
-import { ChevronLeft, Clock9, Users2 } from 'lucide-react';
+import { ChevronLeft, Users2 } from 'lucide-react';
import { Link, redirect } from 'react-router';
import { match } from 'ts-pattern';
@@ -13,11 +13,9 @@ import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
import { DocumentReadOnlyFields } from '@documenso/ui/components/document/document-read-only-fields';
import { Badge } from '@documenso/ui/primitives/badge';
-import { Button } from '@documenso/ui/primitives/button';
import { Card, CardContent } from '@documenso/ui/primitives/card';
import { PDFViewer } from '@documenso/ui/primitives/pdf-viewer';
-import { DocumentHistorySheet } from '~/components/general/document/document-history-sheet';
import { DocumentPageViewButton } from '~/components/general/document/document-page-view-button';
import { DocumentPageViewDropdown } from '~/components/general/document/document-page-view-dropdown';
import { DocumentPageViewInformation } from '~/components/general/document/document-page-view-information';
@@ -101,9 +99,6 @@ export default function DocumentPage() {
const { recipients, documentData, documentMeta } = document;
- // This was a feature flag. Leave to false since it's not ready.
- const isDocumentHistoryEnabled = false;
-
return (
{document.status === DocumentStatus.PENDING && (
@@ -154,17 +149,6 @@ export default function DocumentPage() {
)}
-
- {isDocumentHistoryEnabled && (
-
-
-
-
-
- )}
diff --git a/packages/lib/server-only/document/viewed-document.ts b/packages/lib/server-only/document/viewed-document.ts
index fbc0aaa2d..a9faf992a 100644
--- a/packages/lib/server-only/document/viewed-document.ts
+++ b/packages/lib/server-only/document/viewed-document.ts
@@ -27,7 +27,6 @@ export const viewedDocument = async ({
const recipient = await prisma.recipient.findFirst({
where: {
token,
- readStatus: ReadStatus.NOT_OPENED,
},
});
@@ -37,6 +36,30 @@ export const viewedDocument = async ({
const { documentId } = recipient;
+ await prisma.documentAuditLog.create({
+ data: createDocumentAuditLogData({
+ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_VIEWED,
+ documentId,
+ user: {
+ name: recipient.name,
+ email: recipient.email,
+ },
+ requestMetadata,
+ data: {
+ recipientEmail: recipient.email,
+ recipientId: recipient.id,
+ recipientName: recipient.name,
+ recipientRole: recipient.role,
+ accessAuth: recipientAccessAuth ?? [],
+ },
+ }),
+ });
+
+ // Early return if already opened.
+ if (recipient.readStatus === ReadStatus.OPENED) {
+ return;
+ }
+
await prisma.$transaction(async (tx) => {
await tx.recipient.update({
where: {
diff --git a/packages/lib/types/document-audit-logs.ts b/packages/lib/types/document-audit-logs.ts
index 25679287a..5c3f5bd86 100644
--- a/packages/lib/types/document-audit-logs.ts
+++ b/packages/lib/types/document-audit-logs.ts
@@ -33,6 +33,7 @@ export const ZDocumentAuditLogTypeSchema = z.enum([
'DOCUMENT_GLOBAL_AUTH_ACTION_UPDATED', // When the global action authentication is updated.
'DOCUMENT_META_UPDATED', // When the document meta data is updated.
'DOCUMENT_OPENED', // When the document is opened by a recipient.
+ 'DOCUMENT_VIEWED', // When the document is viewed by a recipient.
'DOCUMENT_RECIPIENT_REJECTED', // When a recipient rejects the document.
'DOCUMENT_RECIPIENT_COMPLETED', // When a recipient completes all their required tasks for the document.
'DOCUMENT_SENT', // When the document transitions from DRAFT to PENDING.
@@ -438,6 +439,22 @@ export const ZDocumentAuditLogEventDocumentOpenedSchema = z.object({
}),
});
+/**
+ * Event: Document viewed.
+ */
+export const ZDocumentAuditLogEventDocumentViewedSchema = z.object({
+ type: z.literal(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_VIEWED),
+ data: ZBaseRecipientDataSchema.extend({
+ accessAuth: z.preprocess((unknownValue) => {
+ if (!unknownValue) {
+ return [];
+ }
+
+ return Array.isArray(unknownValue) ? unknownValue : [unknownValue];
+ }, z.array(ZRecipientAccessAuthTypesSchema)),
+ }),
+});
+
/**
* Event: Document recipient completed the document (the recipient has fully actioned and completed their required steps for the document).
*/
@@ -601,6 +618,7 @@ export const ZDocumentAuditLogSchema = ZDocumentAuditLogBaseSchema.and(
ZDocumentAuditLogEventDocumentGlobalAuthActionUpdatedSchema,
ZDocumentAuditLogEventDocumentMetaUpdatedSchema,
ZDocumentAuditLogEventDocumentOpenedSchema,
+ ZDocumentAuditLogEventDocumentViewedSchema,
ZDocumentAuditLogEventDocumentRecipientCompleteSchema,
ZDocumentAuditLogEventDocumentRecipientRejectedSchema,
ZDocumentAuditLogEventDocumentSentSchema,
diff --git a/packages/lib/utils/document-audit-logs.ts b/packages/lib/utils/document-audit-logs.ts
index 287d4a823..58797510f 100644
--- a/packages/lib/utils/document-audit-logs.ts
+++ b/packages/lib/utils/document-audit-logs.ts
@@ -338,6 +338,10 @@ export const formatDocumentAuditLogAction = (
anonymous: msg`Document opened`,
identified: msg`${prefix} opened the document`,
}))
+ .with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_VIEWED }, () => ({
+ anonymous: msg`Document viewed`,
+ identified: msg`${prefix} viewed the document`,
+ }))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_TITLE_UPDATED }, () => ({
anonymous: msg`Document title updated`,
identified: msg`${prefix} updated the document title`,