Merge branch 'main' into feat/document-file-conversion

This commit is contained in:
Ephraim Duncan
2026-01-06 20:48:27 +00:00
committed by GitHub
43 changed files with 2567 additions and 298 deletions
@@ -5,14 +5,22 @@ description: Learn how to get the coordinates of a field in a document.
## Field Coordinates
Field coordinates represent the position of a field in a document. They are returned in the `pageX` and `pageY` properties of the field.
Field coordinates represent the position of a field in a document. They are returned in the `pageX`, `pageY`, `width` and `height` properties of the field.
To enable field coordinates, you can use the `devmode` query parameter.
```bash
https://app.documenso.com/documents/<document-id>/edit?devmode=true
# Legacy editor
https://app.documenso.com/t/<team-url>/documents/<envelope-id>/legacy_editor?devmode=true
```
You should then see the coordinates on top of each field.
![Field Coordinates Legacy Editor](/developer-mode/field-coordinates-legacy-editor.webp)
![Field Coordinates](/developer-mode/field-coordinates.webp)
```bash
# New editor
https://app.documenso.com/t/<team-url>/documents/<envelope-id>/edit?step=addFields&devmode=true
```
![Field Coordinates New Editor](/developer-mode/field-coordinates-new-editor.webp)
@@ -148,6 +148,7 @@ This method avoids file permission issues by creating the certificate directly i
# Generate certificate inside container using environment variable
docker exec -e CERT_PASS="$CERT_PASS" -it documenso-production-documenso-1 bash -c "
mkdir -p /app/certs && \
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /tmp/private.key \
-out /tmp/certificate.crt \
Binary file not shown.

After

Width:  |  Height:  |  Size: 596 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

@@ -1,6 +1,4 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { Recipient } from '@prisma/client';
import type { Field } from '@prisma/client';
@@ -57,8 +55,6 @@ export const DirectTemplateConfigureForm = ({
initialEmail,
onSubmit,
}: DirectTemplateConfigureFormProps) => {
const { _ } = useLingui();
const { sessionData } = useOptionalSession();
const user = sessionData?.user;
@@ -77,17 +73,7 @@ export const DirectTemplateConfigureForm = ({
});
const form = useForm<TDirectTemplateConfigureFormSchema>({
resolver: zodResolver(
ZDirectTemplateConfigureFormSchema.superRefine((items, ctx) => {
if (template.recipients.map((recipient) => recipient.email).includes(items.email)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: _(msg`Email cannot already exist in the template`),
path: ['email'],
});
}
}),
),
resolver: zodResolver(ZDirectTemplateConfigureFormSchema),
defaultValues: {
email: initialEmail || '',
},
@@ -138,7 +124,7 @@ export const DirectTemplateConfigureForm = ({
</FormControl>
{!fieldState.error && (
<p className="text-muted-foreground text-xs">
<p className="text-xs text-muted-foreground">
<Trans>Enter your email address to receive the completed document.</Trans>
</p>
)}
@@ -22,7 +22,7 @@ export const DocumentPageViewRecentActivity = ({
documentId,
userId,
}: DocumentPageViewRecentActivityProps) => {
const { _ } = useLingui();
const { _, i18n } = useLingui();
const {
data,
@@ -48,9 +48,9 @@ export const DocumentPageViewRecentActivity = ({
const documentAuditLogs = useMemo(() => (data?.pages ?? []).flatMap((page) => page.data), [data]);
return (
<section className="dark:bg-background border-border bg-widget flex flex-col rounded-xl border">
<section className="flex flex-col rounded-xl border border-border bg-widget dark:bg-background">
<div className="flex flex-row items-center justify-between border-b px-4 py-3">
<h1 className="text-foreground font-medium">
<h1 className="font-medium text-foreground">
<Trans>Recent activity</Trans>
</h1>
@@ -59,18 +59,18 @@ export const DocumentPageViewRecentActivity = ({
{isLoading && (
<div className="flex h-full items-center justify-center py-16">
<Loader className="text-muted-foreground h-6 w-6 animate-spin" />
<Loader className="h-6 w-6 animate-spin text-muted-foreground" />
</div>
)}
{isLoadingError && (
<div className="flex h-full flex-col items-center justify-center py-16">
<p className="text-foreground/80 text-sm">
<p className="text-sm text-foreground/80">
<Trans>Unable to load document history</Trans>
</p>
<button
onClick={async () => refetch()}
className="text-foreground/70 hover:text-muted-foreground mt-2 text-sm"
className="mt-2 text-sm text-foreground/70 hover:text-muted-foreground"
>
<Trans>Click here to retry</Trans>
</button>
@@ -83,16 +83,16 @@ export const DocumentPageViewRecentActivity = ({
{hasNextPage && (
<li className="relative flex gap-x-4">
<div className="absolute -bottom-6 left-0 top-0 flex w-6 justify-center">
<div className="bg-border w-px" />
<div className="w-px bg-border" />
</div>
<div className="bg-widget relative flex h-6 w-6 flex-none items-center justify-center">
<div className="bg-widget h-1.5 w-1.5 rounded-full ring-1 ring-gray-300 dark:ring-neutral-600" />
<div className="relative flex h-6 w-6 flex-none items-center justify-center bg-widget">
<div className="h-1.5 w-1.5 rounded-full bg-widget ring-1 ring-gray-300 dark:ring-neutral-600" />
</div>
<button
onClick={async () => fetchNextPage()}
className="text-foreground/70 hover:text-muted-foreground text-xs"
className="text-xs text-foreground/70 hover:text-muted-foreground"
>
{isFetchingNextPage ? _(msg`Loading...`) : _(msg`Load older activity`)}
</button>
@@ -101,7 +101,7 @@ export const DocumentPageViewRecentActivity = ({
{documentAuditLogs.length === 0 && (
<div className="flex items-center justify-center py-4">
<p className="text-muted-foreground/70 text-sm">
<p className="text-sm text-muted-foreground/70">
<Trans>No recent activity</Trans>
</p>
</div>
@@ -115,44 +115,44 @@ export const DocumentPageViewRecentActivity = ({
'absolute left-0 top-0 flex w-6 justify-center',
)}
>
<div className="bg-border w-px" />
<div className="w-px bg-border" />
</div>
<div className="bg-widget text-foreground/40 relative flex h-6 w-6 flex-none items-center justify-center">
<div className="relative flex h-6 w-6 flex-none items-center justify-center bg-widget text-foreground/40">
{match(auditLog.type)
.with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED, () => (
<div className="bg-widget rounded-full border border-gray-300 p-1 dark:border-neutral-600">
<div className="rounded-full border border-gray-300 bg-widget p-1 dark:border-neutral-600">
<CheckCheckIcon className="h-3 w-3" aria-hidden="true" />
</div>
))
.with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_COMPLETED, () => (
<div className="bg-widget rounded-full border border-gray-300 p-1 dark:border-neutral-600">
<div className="rounded-full border border-gray-300 bg-widget p-1 dark:border-neutral-600">
<CheckIcon className="h-3 w-3" aria-hidden="true" />
</div>
))
.with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_REJECTED, () => (
<div className="bg-widget rounded-full border border-gray-300 p-1 dark:border-neutral-600">
<div className="rounded-full border border-gray-300 bg-widget p-1 dark:border-neutral-600">
<AlertTriangle className="h-3 w-3" aria-hidden="true" />
</div>
))
.with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED, () => (
<div className="bg-widget rounded-full border border-gray-300 p-1 dark:border-neutral-600">
<div className="rounded-full border border-gray-300 bg-widget p-1 dark:border-neutral-600">
<MailOpen className="h-3 w-3" aria-hidden="true" />
</div>
))
.otherwise(() => (
<div className="bg-widget h-1.5 w-1.5 rounded-full ring-1 ring-gray-300 dark:ring-neutral-600" />
<div className="h-1.5 w-1.5 rounded-full bg-widget ring-1 ring-gray-300 dark:ring-neutral-600" />
))}
</div>
<p
className="text-muted-foreground dark:text-muted-foreground/70 flex-auto truncate py-0.5 text-xs leading-5"
title={formatDocumentAuditLogAction(_, auditLog, userId).description}
className="flex-auto truncate py-0.5 text-xs leading-5 text-muted-foreground dark:text-muted-foreground/70"
title={formatDocumentAuditLogAction(i18n, auditLog, userId).description}
>
{formatDocumentAuditLogAction(_, auditLog, userId).description}
{formatDocumentAuditLogAction(i18n, auditLog, userId).description}
</p>
<time className="text-muted-foreground dark:text-muted-foreground/70 flex-none py-0.5 text-xs leading-5">
<time className="flex-none py-0.5 text-xs leading-5 text-muted-foreground dark:text-muted-foreground/70">
{DateTime.fromJSDate(auditLog.createdAt).toRelative({ style: 'short' })}
</time>
</li>
@@ -28,36 +28,40 @@ export const EnvelopeSignerHeader = () => {
const { envelopeData, envelope, recipientFieldsRemaining, recipient } =
useRequiredEnvelopeSigningContext();
const isEmbedSigning = useEmbedSigningContext() !== null;
return (
<nav className="embed--DocumentWidgetHeader bg-background border-border max-w-screen flex flex-row justify-between border-b px-4 py-3 md:px-6">
<nav className="embed--DocumentWidgetHeader max-w-screen flex flex-row justify-between border-b border-border bg-background px-4 py-3 md:px-6">
{/* Left side - Logo and title */}
<div className="flex min-w-0 flex-1 items-center space-x-2 md:w-auto md:flex-none">
<Link to="/" className="flex-shrink-0">
{envelopeData.settings.brandingEnabled && envelopeData.settings.brandingLogo ? (
<img
src={`/api/branding/logo/team/${envelope.teamId}`}
alt={`${envelope.team.name}'s Logo`}
className="h-6 w-auto"
/>
) : (
<>
<BrandingLogo className="hidden h-6 w-auto md:block" />
<BrandingLogoIcon className="h-6 w-auto md:hidden" />
</>
)}
</Link>
{!isEmbedSigning && (
<Link to="/" className="flex-shrink-0">
{envelopeData.settings.brandingEnabled && envelopeData.settings.brandingLogo ? (
<img
src={`/api/branding/logo/team/${envelope.teamId}`}
alt={`${envelope.team.name}'s Logo`}
className="h-6 w-auto"
/>
) : (
<>
<BrandingLogo className="hidden h-6 w-auto md:block" />
<BrandingLogoIcon className="h-6 w-auto md:hidden" />
</>
)}
</Link>
)}
<h1
title={envelope.title}
className="text-foreground min-w-0 truncate text-base font-semibold md:hidden"
className="min-w-0 truncate text-base font-semibold text-foreground md:hidden"
>
{envelope.title}
</h1>
<Separator orientation="vertical" className="hidden h-6 md:block" />
{!isEmbedSigning && <Separator orientation="vertical" className="hidden h-6 md:block" />}
<div className="hidden items-center space-x-2 md:flex">
<h1 className="text-foreground whitespace-nowrap text-sm font-medium">
<h1 className="whitespace-nowrap text-sm font-medium text-foreground">
{envelope.title}
</h1>
@@ -74,7 +78,7 @@ export const EnvelopeSignerHeader = () => {
{/* Right side - Desktop content */}
<div className="hidden items-center space-x-2 lg:flex">
<p className="text-muted-foreground mr-2 flex-shrink-0 text-sm">
<p className="mr-2 flex-shrink-0 text-sm text-muted-foreground">
<Plural
one="1 Field Remaining"
other="# Fields Remaining"
@@ -7,7 +7,7 @@ import { Skeleton } from '@documenso/ui/primitives/skeleton';
export default function DocumentEditSkeleton() {
return (
<div className="mx-auto -mt-4 flex w-full max-w-screen-xl flex-col px-4 md:px-8">
<Link to="/" className="flex grow-0 items-center text-[#7AC455] hover:opacity-80">
<Link to="/" className="flex grow-0 items-center text-documenso-700 hover:opacity-80">
<ChevronLeft className="mr-2 inline-block h-5 w-5" />
<Trans>Documents</Trans>
</Link>
@@ -95,7 +95,7 @@ export const DocumentLogsTable = ({ documentId, userId }: DocumentLogsTableProps
header: _(msg`Action`),
accessorKey: 'type',
cell: ({ row }) => (
<span>{formatDocumentAuditLogAction(_, row.original, userId).description}</span>
<span>{formatDocumentAuditLogAction(i18n, row.original, userId).description}</span>
),
},
{
@@ -65,7 +65,7 @@ const formatUserAgent = (userAgent: string | null | undefined, userAgentInfo: UA
};
export const InternalAuditLogTable = ({ logs }: AuditLogDataTableProps) => {
const { _ } = useLingui();
const { _, i18n } = useLingui();
const parser = new UAParser();
@@ -73,7 +73,7 @@ export const InternalAuditLogTable = ({ logs }: AuditLogDataTableProps) => {
<div className="space-y-4">
{logs.map((log, index) => {
parser.setUA(log.userAgent || '');
const formattedAction = formatDocumentAuditLogAction(_, log);
const formattedAction = formatDocumentAuditLogAction(i18n, log);
const userAgentInfo = parser.getResult();
return (
@@ -95,17 +95,17 @@ export const InternalAuditLogTable = ({ logs }: AuditLogDataTableProps) => {
/>
<div>
<div className="text-muted-foreground text-sm font-medium uppercase tracking-wide print:text-[8pt]">
<div className="text-sm font-medium uppercase tracking-wide text-muted-foreground print:text-[8pt]">
{log.type.replace(/_/g, ' ')}
</div>
<div className="text-foreground text-sm font-medium print:text-[8pt]">
<div className="text-sm font-medium text-foreground print:text-[8pt]">
{formattedAction.description}
</div>
</div>
</div>
<div className="text-muted-foreground text-sm print:text-[8pt]">
<div className="text-sm text-muted-foreground print:text-[8pt]">
{DateTime.fromJSDate(log.createdAt)
.setLocale(APP_I18N_OPTIONS.defaultLocale)
.toLocaleString(dateFormat)}
@@ -117,27 +117,27 @@ export const InternalAuditLogTable = ({ logs }: AuditLogDataTableProps) => {
{/* 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="text-muted-foreground/70 font-medium uppercase tracking-wide">
<div className="font-medium uppercase tracking-wide text-muted-foreground/70">
{_(msg`User`)}
</div>
<div className="text-foreground mt-1 font-mono">{log.email || 'N/A'}</div>
<div className="mt-1 font-mono text-foreground">{log.email || 'N/A'}</div>
</div>
<div className="text-right">
<div className="text-muted-foreground/70 font-medium uppercase tracking-wide">
<div className="font-medium uppercase tracking-wide text-muted-foreground/70">
{_(msg`IP Address`)}
</div>
<div className="text-foreground mt-1 font-mono">{log.ipAddress || 'N/A'}</div>
<div className="mt-1 font-mono text-foreground">{log.ipAddress || 'N/A'}</div>
</div>
<div className="col-span-2">
<div className="text-muted-foreground/70 font-medium uppercase tracking-wide">
<div className="font-medium uppercase tracking-wide text-muted-foreground/70">
{_(msg`User Agent`)}
</div>
<div className="text-foreground mt-1">
<div className="mt-1 text-foreground">
{_(formatUserAgent(log.userAgent, userAgentInfo))}
</div>
</div>
@@ -100,7 +100,7 @@ export default function DocumentPage({ params }: Route.ComponentProps) {
<DocumentRecipientLinkCopyDialog recipients={envelope.recipients} />
)}
<Link to={documentRootPath} className="flex items-center text-[#7AC455] hover:opacity-80">
<Link to={documentRootPath} className="flex items-center text-documenso-700 hover:opacity-80">
<ChevronLeft className="mr-2 inline-block h-5 w-5" />
<Trans>Documents</Trans>
</Link>
@@ -84,7 +84,7 @@ export default function DocumentEditPage() {
return (
<div className="mx-auto -mt-4 w-full max-w-screen-xl px-4 md:px-8">
<Link to={documentRootPath} className="flex items-center text-[#7AC455] hover:opacity-80">
<Link to={documentRootPath} className="flex items-center text-documenso-700 hover:opacity-80">
<ChevronLeft className="mr-2 inline-block h-5 w-5" />
<Trans>Documents</Trans>
</Link>
@@ -134,7 +134,7 @@ export default function DocumentsLogsPage({ loaderData }: Route.ComponentProps)
<div className="mx-auto -mt-4 w-full max-w-screen-xl px-4 md:px-8">
<Link
to={`${documentRootPath}/${document.envelopeId}`}
className="flex items-center text-[#7AC455] hover:opacity-80"
className="flex items-center text-documenso-700 hover:opacity-80"
>
<ChevronLeft className="mr-2 inline-block h-5 w-5" />
<Trans>Document</Trans>
@@ -117,7 +117,7 @@ export default function TemplatePage({ params }: Route.ComponentProps) {
return (
<div className="mx-auto -mt-4 w-full max-w-screen-xl px-4 md:px-8">
<Link to={templateRootPath} className="flex items-center text-[#7AC455] hover:opacity-80">
<Link to={templateRootPath} className="flex items-center text-documenso-700 hover:opacity-80">
<ChevronLeft className="mr-2 inline-block h-5 w-5" />
<Trans>Templates</Trans>
</Link>
@@ -61,7 +61,7 @@ export default function TemplateEditPage() {
<div>
<Link
to={`${templateRootPath}/${template.envelopeId}`}
className="flex items-center text-[#7AC455] hover:opacity-80"
className="flex items-center text-documenso-700 hover:opacity-80"
>
<ChevronLeft className="mr-2 inline-block h-5 w-5" />
<Trans>Template</Trans>
@@ -149,7 +149,7 @@ export default function AuditLog({ loaderData }: Route.ComponentProps) {
<span className="mt-1 block">
{DateTime.fromJSDate(document.createdAt)
.setLocale(APP_I18N_OPTIONS.defaultLocale)
.toFormat('yyyy-mm-dd hh:mm:ss a (ZZZZ)')}
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')}
</span>
</p>
@@ -159,7 +159,7 @@ export default function AuditLog({ loaderData }: Route.ComponentProps) {
<span className="mt-1 block">
{DateTime.fromJSDate(document.updatedAt)
.setLocale(APP_I18N_OPTIONS.defaultLocale)
.toFormat('yyyy-mm-dd hh:mm:ss a (ZZZZ)')}
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')}
</span>
</p>