feat(pdf): redesign audit log and signing certificate documents

This commit is contained in:
ephraimduncan
2026-07-08 05:58:11 +00:00
parent 50f272be87
commit 529e83b0fa
6 changed files with 1031 additions and 1103 deletions
@@ -1,48 +1,20 @@
import { APP_I18N_OPTIONS } from '@documenso/lib/constants/i18n';
import { DOCUMENT_AUDIT_LOG_TYPE, type TDocumentAuditLog } from '@documenso/lib/types/document-audit-logs';
import type { TDocumentAuditLog } from '@documenso/lib/types/document-audit-logs';
import { formatDocumentAuditLogAction } from '@documenso/lib/utils/document-audit-logs';
import { cn } from '@documenso/ui/lib/utils';
import { Card, CardContent } from '@documenso/ui/primitives/card';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import type { DateTimeFormatOptions } from 'luxon';
import { DateTime } from 'luxon';
import { match, P } from 'ts-pattern';
import { UAParser } from 'ua-parser-js';
export type AuditLogDataTableProps = {
logs: TDocumentAuditLog[];
};
const dateFormat: DateTimeFormatOptions = {
...DateTime.DATETIME_SHORT,
hourCycle: 'h12',
};
/**
* Get the color indicator for the audit log type
*/
const getAuditLogIndicatorColor = (type: string) =>
match(type)
.with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_COMPLETED, () => 'bg-green-500')
.with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_REJECTED, () => 'bg-red-500')
.with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT, () => 'bg-orange-500')
.with(
P.union(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_INSERTED, DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_UNINSERTED),
() => 'bg-blue-500',
)
.otherwise(() => 'bg-muted');
/**
* DO NOT USE TRANS. YOU MUST USE _ FOR THIS FILE AND ALL CHILDREN COMPONENTS.
*/
const formatUserAgent = (userAgent: string | null | undefined, userAgentInfo: UAParser.IResult) => {
if (!userAgent) {
return msg`N/A`;
}
const formatUserAgent = (userAgent: string, userAgentInfo: UAParser.IResult) => {
const browser = userAgentInfo.browser.name;
const version = userAgentInfo.browser.version;
const os = userAgentInfo.os.name;
@@ -63,74 +35,45 @@ export const InternalAuditLogTable = ({ logs }: AuditLogDataTableProps) => {
const parser = new UAParser();
return (
<div className="space-y-4">
<div className="divide-y divide-border">
{logs.map((log, index) => {
parser.setUA(log.userAgent || '');
const formattedAction = formatDocumentAuditLogAction(i18n, log);
const userAgentInfo = parser.getResult();
const createdAt = DateTime.fromJSDate(log.createdAt).setLocale(APP_I18N_OPTIONS.defaultLocale);
const metaSegments = [
log.email,
log.ipAddress,
log.userAgent ? _(formatUserAgent(log.userAgent, userAgentInfo)) : null,
].filter((segment): segment is string => Boolean(segment));
return (
<Card
<div
key={index}
// Add top margin for the first card to ensure it's not cut off from the 2nd page onwards
className={`border shadow-sm ${index > 0 ? 'print:mt-8' : ''}`}
className="flex gap-5 py-2.5"
style={{
pageBreakInside: 'avoid',
breakInside: 'avoid',
}}
>
<CardContent className="p-4">
{/* Header Section with indicator, event type, and timestamp */}
<div className="mb-3 flex items-start justify-between">
<div className="flex items-baseline gap-3">
<div className={cn(`h-2 w-2 rounded-full`, getAuditLogIndicatorColor(log.type))} />
<div className="w-[5.5rem] shrink-0">
<div className="text-foreground text-xs">{createdAt.toFormat('yyyy-MM-dd')}</div>
<div>
<div className="font-medium text-muted-foreground text-sm uppercase tracking-wide print:text-[8pt]">
{log.type.replace(/_/g, ' ')}
</div>
<div className="mt-0.5 text-[0.6875rem] text-muted-foreground">{createdAt.toFormat('hh:mm:ss a')}</div>
</div>
<div className="font-medium text-foreground text-sm print:text-[8pt]">
{formattedAction.description}
</div>
</div>
<div className="min-w-0 flex-1">
<div className="text-pretty text-foreground text-sm print:text-xs">{formattedAction.description}</div>
{metaSegments.length > 0 && (
<div className="mt-1 break-words text-muted-foreground text-xs print:text-[0.6875rem]">
{metaSegments.join(' · ')}
</div>
<div className="text-muted-foreground text-sm print:text-[8pt]">
{DateTime.fromJSDate(log.createdAt)
.setLocale(APP_I18N_OPTIONS.defaultLocale)
.toLocaleString(dateFormat)}
</div>
</div>
<hr className="my-4" />
{/* Details Section - Two column layout */}
<div className="grid grid-cols-2 gap-x-8 gap-y-2 text-xs print:text-[6pt]">
<div>
<div className="font-medium text-muted-foreground/70 uppercase tracking-wide">{_(msg`User`)}</div>
<div className="mt-1 font-mono text-foreground">{log.email || 'N/A'}</div>
</div>
<div className="text-right">
<div className="font-medium text-muted-foreground/70 uppercase tracking-wide">
{_(msg`IP Address`)}
</div>
<div className="mt-1 font-mono text-foreground">{log.ipAddress || 'N/A'}</div>
</div>
<div className="col-span-2">
<div className="font-medium text-muted-foreground/70 uppercase tracking-wide">
{_(msg`User Agent`)}
</div>
<div className="mt-1 text-foreground">{_(formatUserAgent(log.userAgent, userAgentInfo))}</div>
</div>
</div>
</CardContent>
</Card>
)}
</div>
</div>
);
})}
</div>
@@ -7,7 +7,6 @@ import { findDocumentAuditLogs } from '@documenso/lib/server-only/document/find-
import { getOrganisationClaimByTeamId } from '@documenso/lib/server-only/organisation/get-organisation-claims';
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
import { getTranslations } from '@documenso/lib/utils/i18n';
import { Card, CardContent } from '@documenso/ui/primitives/card';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { EnvelopeType } from '@prisma/client';
@@ -106,82 +105,86 @@ export default function AuditLog({ loaderData }: Route.ComponentProps) {
return (
<div className="print-provider pointer-events-none mx-auto max-w-screen-md">
<div className="mb-6 border-b pb-4">
<h1 className="font-semibold text-xl">{_(msg`Audit Log`)}</h1>
</div>
<header>
<h1 className="font-semibold text-lg tracking-tight">{_(msg`Audit Log`)}</h1>
<Card>
<CardContent className="grid grid-cols-2 gap-4 p-6 text-sm print:text-xs">
<p>
<span className="font-medium">{_(msg`Envelope ID`)}</span>
<p className="mt-1 text-pretty text-muted-foreground text-sm">{document.title}</p>
</header>
<span className="mt-1 block break-words">{document.envelopeId}</span>
</p>
<dl className="mt-6 grid grid-cols-2 gap-x-8 gap-y-5">
<div>
<dt className="font-medium text-muted-foreground text-xs">{_(msg`Envelope ID`)}</dt>
<p>
<span className="font-medium">{_(msg`Enclosed Document`)}</span>
<dd className="mt-1 break-all text-foreground text-sm print:text-xs">{document.envelopeId}</dd>
</div>
<span className="mt-1 block break-words">{document.title}</span>
</p>
<div>
<dt className="font-medium text-muted-foreground text-xs">{_(msg`Owner`)}</dt>
<p>
<span className="font-medium">{_(msg`Status`)}</span>
<dd className="mt-1 break-words text-foreground text-sm print:text-xs">
{document.user.name} ({document.user.email})
</dd>
</div>
<span className="mt-1 block">
{_(document.deletedAt ? msg`Deleted` : DOCUMENT_STATUS[document.status].description).toUpperCase()}
</span>
</p>
<div>
<dt className="font-medium text-muted-foreground text-xs">{_(msg`Status`)}</dt>
<p>
<span className="font-medium">{_(msg`Owner`)}</span>
<dd className="mt-1 text-foreground text-sm print:text-xs">
{_(document.deletedAt ? msg`Deleted` : DOCUMENT_STATUS[document.status].description)}
</dd>
</div>
<span className="mt-1 block break-words">
{document.user.name} ({document.user.email})
</span>
</p>
<div>
<dt className="font-medium text-muted-foreground text-xs">{_(msg`Time Zone`)}</dt>
<p>
<span className="font-medium">{_(msg`Created At`)}</span>
<dd className="mt-1 break-words text-foreground text-sm print:text-xs">
{document.documentMeta?.timezone ?? 'N/A'}
</dd>
</div>
<span className="mt-1 block">
{DateTime.fromJSDate(document.createdAt)
.setLocale(APP_I18N_OPTIONS.defaultLocale)
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')}
</span>
</p>
<div>
<dt className="font-medium text-muted-foreground text-xs">{_(msg`Created At`)}</dt>
<p>
<span className="font-medium">{_(msg`Last Updated`)}</span>
<dd className="mt-1 text-foreground text-sm print:text-xs">
{DateTime.fromJSDate(document.createdAt)
.setLocale(APP_I18N_OPTIONS.defaultLocale)
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')}
</dd>
</div>
<span className="mt-1 block">
{DateTime.fromJSDate(document.updatedAt)
.setLocale(APP_I18N_OPTIONS.defaultLocale)
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')}
</span>
</p>
<div>
<dt className="font-medium text-muted-foreground text-xs">{_(msg`Last Updated`)}</dt>
<p>
<span className="font-medium">{_(msg`Time Zone`)}</span>
<dd className="mt-1 text-foreground text-sm print:text-xs">
{DateTime.fromJSDate(document.updatedAt)
.setLocale(APP_I18N_OPTIONS.defaultLocale)
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')}
</dd>
</div>
<span className="mt-1 block break-words">{document.documentMeta?.timezone ?? 'N/A'}</span>
</p>
<div>
<dt className="font-medium text-muted-foreground text-xs">{_(msg`Enclosed Document`)}</dt>
<div>
<p className="font-medium">{_(msg`Recipients`)}</p>
<dd className="mt-1 break-words text-foreground text-sm print:text-xs">{document.title}</dd>
</div>
<ul className="mt-1 list-inside list-disc">
<div>
<dt className="font-medium text-muted-foreground text-xs">{_(msg`Recipients`)}</dt>
<dd className="mt-1 text-foreground text-sm print:text-xs">
<ul className="space-y-0.5">
{document.recipients.map((recipient) => (
<li key={recipient.id}>
<li key={recipient.id} className="break-words">
{recipient.name} ({recipient.email}) ·{' '}
<span className="text-muted-foreground">
[{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}]
</span>{' '}
{recipient.name} ({recipient.email})
{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}
</span>
</li>
))}
</ul>
</div>
</CardContent>
</Card>
</dd>
</div>
</dl>
<div className="mt-8">
<InternalAuditLogTable logs={auditLogs} />
@@ -9,11 +9,8 @@ import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-log
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
import { getTranslations } from '@documenso/lib/utils/i18n';
import { Card, CardContent } from '@documenso/ui/primitives/card';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@documenso/ui/primitives/table';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { EnvelopeType, FieldType, SigningStatus } from '@prisma/client';
import { DateTime } from 'luxon';
import { redirect } from 'react-router';
@@ -203,165 +200,178 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps)
return (
<div className="print-provider pointer-events-none mx-auto max-w-screen-md">
<div className="flex items-center">
<h1 className="my-8 font-bold text-2xl">{_(msg`Signing Certificate`)}</h1>
</div>
<header>
<h1 className="font-semibold text-lg tracking-tight">{_(msg`Signing Certificate`)}</h1>
<Card>
<CardContent className="p-0">
<Table overflowHidden>
<TableHeader>
<TableRow>
<TableHead>{_(msg`Signer Events`)}</TableHead>
<TableHead>{_(msg`Signature`)}</TableHead>
<TableHead>{_(msg`Details`)}</TableHead>
{/* <TableHead>Security</TableHead> */}
</TableRow>
</TableHeader>
<p className="mt-1 text-pretty text-muted-foreground text-sm">{document.title}</p>
</header>
<TableBody className="print:text-xs">
{document.recipients.map((recipient, i) => {
const logs = getRecipientAuditLogs(recipient.id);
const signature = getRecipientSignatureField(recipient.id);
<table className="mt-6 w-full">
<thead>
<tr className="border-border border-b">
<th className="w-[30%] pr-4 pb-2 text-left font-medium text-muted-foreground text-xs">
{_(msg`Signer Events`)}
</th>
return (
<TableRow key={i} className="print:break-inside-avoid">
<TableCell truncate={false} className="w-[min-content] max-w-[220px] align-top">
<div className="hyphens-auto break-words font-medium">{recipient.name}</div>
<div className="break-all">{recipient.email}</div>
<p className="mt-2 text-muted-foreground text-sm print:text-xs">
{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}
</p>
<th className="w-[30%] pr-4 pb-2 text-left font-medium text-muted-foreground text-xs">
{_(msg`Signature`)}
</th>
<p className="mt-2 text-muted-foreground text-sm print:text-xs">
<span className="font-medium">{_(msg`Authentication Level`)}:</span>{' '}
<span className="block">{getAuthenticationLevel(recipient.id)}</span>
</p>
</TableCell>
<th className="pb-2 text-left font-medium text-muted-foreground text-xs">{_(msg`Details`)}</th>
</tr>
</thead>
<TableCell truncate={false} className="w-[min-content] align-top">
{signature ? (
<>
<div
className="inline-block rounded-lg p-1"
style={{
boxShadow: `0px 0px 0px 4.88px rgba(122, 196, 85, 0.1), 0px 0px 0px 1.22px rgba(122, 196, 85, 0.6), 0px 0px 0px 0.61px rgba(122, 196, 85, 1)`,
}}
>
{signature.signature?.signatureImageAsBase64 && (
<img
src={`${signature.signature?.signatureImageAsBase64}`}
alt="Signature"
className="max-h-12 max-w-full"
/>
)}
<tbody className="divide-y divide-border">
{document.recipients.map((recipient, i) => {
const logs = getRecipientAuditLogs(recipient.id);
const signature = getRecipientSignatureField(recipient.id);
const isRejected = Boolean(logs.DOCUMENT_RECIPIENT_REJECTED[0]);
{signature.signature?.typedSignature && (
<p className="text-center font-signature text-sm">
{signature.signature?.typedSignature}
</p>
)}
</div>
return (
<tr key={i} className="align-top print:break-inside-avoid">
<td className="py-3 pr-4">
<div className="hyphens-auto break-words font-medium text-sm print:text-xs">{recipient.name}</div>
<p className="mt-2 text-muted-foreground text-sm print:text-xs">
<span className="font-medium">{_(msg`Signature ID`)}:</span>{' '}
<span className="block font-mono uppercase">{signature.secondaryId}</span>
</p>
</>
) : (
<p className="text-muted-foreground">
<Trans>N/A</Trans>
</p>
<div className="break-all text-muted-foreground text-xs">{recipient.email}</div>
<div className="mt-0.5 text-muted-foreground text-xs">
{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}
</div>
<div className="mt-2.5">
<div className="font-medium text-muted-foreground text-xs">{_(msg`Authentication Level`)}</div>
<div className="mt-0.5 text-foreground text-sm print:text-xs">
{getAuthenticationLevel(recipient.id)}
</div>
</div>
</td>
<td className="py-3 pr-4">
{signature ? (
<div className="space-y-2.5">
{!isRejected && (
<div
className="inline-block rounded-lg p-1"
style={{
boxShadow: `0px 0px 0px 4.88px rgba(122, 196, 85, 0.1), 0px 0px 0px 1.22px rgba(122, 196, 85, 0.6), 0px 0px 0px 0.61px rgba(122, 196, 85, 1)`,
}}
>
{signature.signature?.signatureImageAsBase64 && (
<img
src={`${signature.signature?.signatureImageAsBase64}`}
alt="Signature"
className="max-h-12 max-w-full"
/>
)}
{signature.signature?.typedSignature && (
<p className="text-center font-signature text-sm">{signature.signature?.typedSignature}</p>
)}
</div>
)}
<p className="mt-2 text-muted-foreground text-sm print:text-xs">
<span className="font-medium">{_(msg`IP Address`)}:</span>{' '}
<span className="inline-block">
{logs.DOCUMENT_RECIPIENT_COMPLETED[0]?.ipAddress ?? _(msg`Unknown`)}
</span>
</p>
<div>
<div className="font-medium text-muted-foreground text-xs">{_(msg`Signature ID`)}</div>
<p className="mt-1 text-muted-foreground text-sm print:text-xs">
<span className="font-medium">{_(msg`Device`)}:</span>{' '}
<span className="inline-block">
{getDevice(logs.DOCUMENT_RECIPIENT_COMPLETED[0]?.userAgent)}
</span>
</p>
</TableCell>
<TableCell truncate={false} className="w-[min-content] align-top">
<div className="space-y-1">
<p className="text-muted-foreground text-sm print:text-xs">
<span className="font-medium">{_(msg`Sent`)}:</span>{' '}
<span className="inline-block">
{logs.EMAIL_SENT[0]
? DateTime.fromJSDate(logs.EMAIL_SENT[0].createdAt)
.setLocale(APP_I18N_OPTIONS.defaultLocale)
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')
: logs.DOCUMENT_SENT[0]
? DateTime.fromJSDate(logs.DOCUMENT_SENT[0].createdAt)
.setLocale(APP_I18N_OPTIONS.defaultLocale)
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')
: _(msg`Unknown`)}
</span>
</p>
<p className="text-muted-foreground text-sm print:text-xs">
<span className="font-medium">{_(msg`Viewed`)}:</span>{' '}
<span className="inline-block">
{logs.DOCUMENT_OPENED[0]
? DateTime.fromJSDate(logs.DOCUMENT_OPENED[0].createdAt)
.setLocale(APP_I18N_OPTIONS.defaultLocale)
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')
: _(msg`Unknown`)}
</span>
</p>
{logs.DOCUMENT_RECIPIENT_REJECTED[0] ? (
<p className="text-muted-foreground text-sm print:text-xs">
<span className="font-medium">{_(msg`Rejected`)}:</span>{' '}
<span className="inline-block">
{logs.DOCUMENT_RECIPIENT_REJECTED[0]
? DateTime.fromJSDate(logs.DOCUMENT_RECIPIENT_REJECTED[0].createdAt)
.setLocale(APP_I18N_OPTIONS.defaultLocale)
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')
: _(msg`Unknown`)}
</span>
</p>
) : (
<p className="text-muted-foreground text-sm print:text-xs">
<span className="font-medium">{_(msg`Signed`)}:</span>{' '}
<span className="inline-block">
{logs.DOCUMENT_RECIPIENT_COMPLETED[0]
? DateTime.fromJSDate(logs.DOCUMENT_RECIPIENT_COMPLETED[0].createdAt)
.setLocale(APP_I18N_OPTIONS.defaultLocale)
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')
: _(msg`Unknown`)}
</span>
</p>
)}
<p className="text-muted-foreground text-sm print:text-xs">
<span className="font-medium">{_(msg`Reason`)}:</span>{' '}
<span className="inline-block">
{recipient.signingStatus === SigningStatus.REJECTED
? recipient.rejectionReason
: _(
isOwner(recipient.email)
? FRIENDLY_SIGNING_REASONS['__OWNER__']
: FRIENDLY_SIGNING_REASONS[recipient.role],
)}
</span>
</p>
<div className="mt-0.5 break-all font-mono text-xs uppercase">{signature.secondaryId}</div>
</div>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</CardContent>
</Card>
</div>
) : (
<p className="text-muted-foreground text-sm print:text-xs">{_(msg`N/A`)}</p>
)}
<div className="mt-2.5">
<div className="font-medium text-muted-foreground text-xs">{_(msg`IP Address`)}</div>
<div className="mt-0.5 text-foreground text-sm print:text-xs">
{logs.DOCUMENT_RECIPIENT_COMPLETED[0]?.ipAddress ?? _(msg`Unknown`)}
</div>
</div>
<div className="mt-2.5">
<div className="font-medium text-muted-foreground text-xs">{_(msg`Device`)}</div>
<div className="mt-0.5 text-foreground text-sm print:text-xs">
{getDevice(logs.DOCUMENT_RECIPIENT_COMPLETED[0]?.userAgent)}
</div>
</div>
</td>
<td className="py-3">
<div className="space-y-2.5">
<div>
<div className="font-medium text-muted-foreground text-xs">{_(msg`Sent`)}</div>
<div className="mt-0.5 text-foreground text-sm print:text-xs">
{logs.EMAIL_SENT[0]
? DateTime.fromJSDate(logs.EMAIL_SENT[0].createdAt)
.setLocale(APP_I18N_OPTIONS.defaultLocale)
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')
: logs.DOCUMENT_SENT[0]
? DateTime.fromJSDate(logs.DOCUMENT_SENT[0].createdAt)
.setLocale(APP_I18N_OPTIONS.defaultLocale)
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')
: _(msg`Unknown`)}
</div>
</div>
<div>
<div className="font-medium text-muted-foreground text-xs">{_(msg`Viewed`)}</div>
<div className="mt-0.5 text-foreground text-sm print:text-xs">
{logs.DOCUMENT_OPENED[0]
? DateTime.fromJSDate(logs.DOCUMENT_OPENED[0].createdAt)
.setLocale(APP_I18N_OPTIONS.defaultLocale)
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')
: _(msg`Unknown`)}
</div>
</div>
{logs.DOCUMENT_RECIPIENT_REJECTED[0] ? (
<div>
<div className="font-medium text-red-600 text-xs">{_(msg`Rejected`)}</div>
<div className="mt-0.5 text-red-600 text-sm print:text-xs">
{DateTime.fromJSDate(logs.DOCUMENT_RECIPIENT_REJECTED[0].createdAt)
.setLocale(APP_I18N_OPTIONS.defaultLocale)
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')}
</div>
</div>
) : (
<div>
<div className="font-medium text-muted-foreground text-xs">{_(msg`Signed`)}</div>
<div className="mt-0.5 text-foreground text-sm print:text-xs">
{logs.DOCUMENT_RECIPIENT_COMPLETED[0]
? DateTime.fromJSDate(logs.DOCUMENT_RECIPIENT_COMPLETED[0].createdAt)
.setLocale(APP_I18N_OPTIONS.defaultLocale)
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')
: _(msg`Unknown`)}
</div>
</div>
)}
<div>
<div className="font-medium text-muted-foreground text-xs">{_(msg`Reason`)}</div>
<div className="mt-0.5 text-foreground text-sm print:text-xs">
{recipient.signingStatus === SigningStatus.REJECTED
? recipient.rejectionReason
: _(
isOwner(recipient.email)
? FRIENDLY_SIGNING_REASONS['__OWNER__']
: FRIENDLY_SIGNING_REASONS[recipient.role],
)}
</div>
</div>
</div>
</td>
</tr>
);
})}
</tbody>
</table>
{!hidePoweredBy && (
<div className="my-8 flex-row-reverse space-y-4">
@@ -377,7 +387,7 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps)
</div>
<div className="flex items-end justify-end gap-x-4">
<p className="flex-shrink-0 font-medium text-sm print:text-xs">
<p className="flex-shrink-0 font-medium text-muted-foreground text-xs">
{_(msg`Signing certificate provided by`)}:
</p>
<BrandingLogo className="max-h-6 print:max-h-4" />
@@ -137,6 +137,7 @@ export const generateCertificatePdf = async (options: GenerateCertificatePdfOpti
}),
envelopeOwner,
envelopeId: envelope.id,
envelopeTitle: envelope.title,
qrToken: envelope.qrToken,
hidePoweredBy: organisationClaim.flags.hidePoweredBy ?? false,
pageWidth,
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff