Sign Document
diff --git a/apps/web/src/app/(signing)/sign/[token]/name-field.tsx b/apps/web/src/app/(signing)/sign/[token]/name-field.tsx
index bbe18fb8a..6e661e77a 100644
--- a/apps/web/src/app/(signing)/sign/[token]/name-field.tsx
+++ b/apps/web/src/app/(signing)/sign/[token]/name-field.tsx
@@ -6,8 +6,8 @@ import { useRouter } from 'next/navigation';
import { Loader } from 'lucide-react';
-import { Recipient } from '@documenso/prisma/client';
-import { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
+import type { Recipient } from '@documenso/prisma/client';
+import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import { Dialog, DialogContent, DialogFooter, DialogTitle } from '@documenso/ui/primitives/dialog';
@@ -98,7 +98,7 @@ export const NameField = ({ field, recipient }: NameFieldProps) => {
};
return (
-
+
{isLoading && (
diff --git a/apps/web/src/app/(signing)/sign/[token]/page.tsx b/apps/web/src/app/(signing)/sign/[token]/page.tsx
index 67e679412..865f553d6 100644
--- a/apps/web/src/app/(signing)/sign/[token]/page.tsx
+++ b/apps/web/src/app/(signing)/sign/[token]/page.tsx
@@ -2,9 +2,12 @@ import { notFound, redirect } from 'next/navigation';
import { match } from 'ts-pattern';
+import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
+import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
+import { getDocumentMetaByDocumentId } from '@documenso/lib/server-only/document/get-document-meta-by-document-id';
import { viewedDocument } from '@documenso/lib/server-only/document/viewed-document';
import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token';
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
@@ -40,6 +43,8 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
viewedDocument({ token }).catch(() => null),
]);
+ const documentMeta = await getDocumentMetaByDocumentId({ id: document!.id }).catch(() => null);
+
if (!document || !document.documentData || !recipient) {
return notFound();
}
@@ -97,7 +102,13 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
))
.with(FieldType.DATE, () => (
-
+
))
.with(FieldType.EMAIL, () => (
diff --git a/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx b/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx
index 01923bd6c..7ded3b698 100644
--- a/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx
+++ b/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx
@@ -6,8 +6,8 @@ import { useRouter } from 'next/navigation';
import { Loader } from 'lucide-react';
-import { Recipient } from '@documenso/prisma/client';
-import { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
+import type { Recipient } from '@documenso/prisma/client';
+import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import { Dialog, DialogContent, DialogFooter, DialogTitle } from '@documenso/ui/primitives/dialog';
@@ -121,7 +121,7 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => {
};
return (
-
+
{isLoading && (
diff --git a/apps/web/src/app/(signing)/sign/[token]/signing-field-container.tsx b/apps/web/src/app/(signing)/sign/[token]/signing-field-container.tsx
index 046e5b3df..b4805fa6b 100644
--- a/apps/web/src/app/(signing)/sign/[token]/signing-field-container.tsx
+++ b/apps/web/src/app/(signing)/sign/[token]/signing-field-container.tsx
@@ -2,8 +2,9 @@
import React from 'react';
-import { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
+import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { FieldRootContainer } from '@documenso/ui/components/field/field';
+import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
export type SignatureFieldProps = {
field: FieldWithSignature;
@@ -11,6 +12,8 @@ export type SignatureFieldProps = {
children: React.ReactNode;
onSign?: () => Promise
| void;
onRemove?: () => Promise | void;
+ type?: 'Date' | 'Email' | 'Name' | 'Signature';
+ tooltipText?: string | null;
};
export const SigningFieldContainer = ({
@@ -19,6 +22,8 @@ export const SigningFieldContainer = ({
onSign,
onRemove,
children,
+ type,
+ tooltipText,
}: SignatureFieldProps) => {
const onSignFieldClick = async () => {
if (field.inserted) {
@@ -46,7 +51,22 @@ export const SigningFieldContainer = ({
/>
)}
- {field.inserted && !loading && (
+ {type === 'Date' && field.inserted && !loading && (
+
+
+
+
+
+ {tooltipText && {tooltipText}}
+
+ )}
+
+ {type !== 'Date' && field.inserted && !loading && (
-
+
+
-
+
+
No value found.
-
- {allRoles.map((value: string, i: number) => (
- handleSelect(value)}>
+
+
+ {options.map((option, index) => (
+ onOptionSelected(option)}>
- {value}
+
+ {option}
))}
diff --git a/packages/ui/primitives/command.tsx b/packages/ui/primitives/command.tsx
index 67cd3f487..cbc306c66 100644
--- a/packages/ui/primitives/command.tsx
+++ b/packages/ui/primitives/command.tsx
@@ -2,7 +2,7 @@
import * as React from 'react';
-import { DialogProps } from '@radix-ui/react-dialog';
+import type { DialogProps } from '@radix-ui/react-dialog';
import { Command as CommandPrimitive } from 'cmdk';
import { Search } from 'lucide-react';
diff --git a/packages/ui/primitives/document-flow/add-fields.tsx b/packages/ui/primitives/document-flow/add-fields.tsx
index f662dca8b..0e17a9d35 100644
--- a/packages/ui/primitives/document-flow/add-fields.tsx
+++ b/packages/ui/primitives/document-flow/add-fields.tsx
@@ -11,7 +11,8 @@ import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-c
import { useDocumentElement } from '@documenso/lib/client-only/hooks/use-document-element';
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import { nanoid } from '@documenso/lib/universal/id';
-import { Field, FieldType, Recipient, SendStatus } from '@documenso/prisma/client';
+import type { Field, Recipient } from '@documenso/prisma/client';
+import { FieldType, SendStatus } from '@documenso/prisma/client';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import { Card, CardContent } from '@documenso/ui/primitives/card';
@@ -25,7 +26,7 @@ import {
import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover';
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
-import { TAddFieldsFormSchema } from './add-fields.types';
+import type { TAddFieldsFormSchema } from './add-fields.types';
import {
DocumentFlowFormContainerActions,
DocumentFlowFormContainerContent,
@@ -33,7 +34,8 @@ import {
DocumentFlowFormContainerStep,
} from './document-flow-root';
import { FieldItem } from './field-item';
-import { DocumentFlowStep, FRIENDLY_FIELD_TYPE } from './types';
+import type { DocumentFlowStep } from './types';
+import { FRIENDLY_FIELD_TYPE } from './types';
const fontCaveat = Caveat({
weight: ['500'],
diff --git a/packages/ui/primitives/document-flow/add-signature.tsx b/packages/ui/primitives/document-flow/add-signature.tsx
index aed252083..02e79ae19 100644
--- a/packages/ui/primitives/document-flow/add-signature.tsx
+++ b/packages/ui/primitives/document-flow/add-signature.tsx
@@ -7,21 +7,23 @@ import { DateTime } from 'luxon';
import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
+import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import { sortFieldsByPosition, validateFieldsInserted } from '@documenso/lib/utils/fields';
-import { Field, FieldType } from '@documenso/prisma/client';
-import { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
+import type { Field } from '@documenso/prisma/client';
+import { FieldType } from '@documenso/prisma/client';
+import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip';
import { cn } from '@documenso/ui/lib/utils';
import { Card, CardContent } from '@documenso/ui/primitives/card';
-import { TAddSignatureFormSchema } from '@documenso/ui/primitives/document-flow/add-signature.types';
+import type { TAddSignatureFormSchema } from '@documenso/ui/primitives/document-flow/add-signature.types';
import {
DocumentFlowFormContainerActions,
DocumentFlowFormContainerContent,
DocumentFlowFormContainerFooter,
DocumentFlowFormContainerStep,
} from '@documenso/ui/primitives/document-flow/document-flow-root';
-import { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/types';
+import type { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/types';
import { ElementVisible } from '@documenso/ui/primitives/element-visible';
import { Input } from '@documenso/ui/primitives/input';
import { SignaturePad } from '@documenso/ui/primitives/signature-pad';
@@ -135,7 +137,7 @@ export const AddSignatureFormPartial = ({
return match(field.type)
.with(FieldType.DATE, () => ({
...field,
- customText: DateTime.now().toFormat('yyyy-MM-dd hh:mm a'),
+ customText: DateTime.now().toFormat(DEFAULT_DOCUMENT_DATE_FORMAT),
inserted: true,
}))
.with(FieldType.EMAIL, () => ({
diff --git a/packages/ui/primitives/document-flow/add-signers.tsx b/packages/ui/primitives/document-flow/add-signers.tsx
index 14d728f0a..0330700a5 100644
--- a/packages/ui/primitives/document-flow/add-signers.tsx
+++ b/packages/ui/primitives/document-flow/add-signers.tsx
@@ -9,21 +9,23 @@ import { Controller, useFieldArray, useForm } from 'react-hook-form';
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
import { nanoid } from '@documenso/lib/universal/id';
-import { Field, Recipient, SendStatus } from '@documenso/prisma/client';
+import type { Field, Recipient } from '@documenso/prisma/client';
+import { SendStatus } from '@documenso/prisma/client';
import { Button } from '@documenso/ui/primitives/button';
import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import { useToast } from '@documenso/ui/primitives/use-toast';
-import { TAddSignersFormSchema, ZAddSignersFormSchema } from './add-signers.types';
+import type { TAddSignersFormSchema } from './add-signers.types';
+import { ZAddSignersFormSchema } from './add-signers.types';
import {
DocumentFlowFormContainerActions,
DocumentFlowFormContainerContent,
DocumentFlowFormContainerFooter,
DocumentFlowFormContainerStep,
} from './document-flow-root';
-import { DocumentFlowStep } from './types';
+import type { DocumentFlowStep } from './types';
export type AddSignersFormProps = {
documentFlow: DocumentFlowStep;
diff --git a/packages/ui/primitives/document-flow/add-subject.tsx b/packages/ui/primitives/document-flow/add-subject.tsx
index 1bf3b2cb4..bbd5962da 100644
--- a/packages/ui/primitives/document-flow/add-subject.tsx
+++ b/packages/ui/primitives/document-flow/add-subject.tsx
@@ -1,22 +1,41 @@
'use client';
-import { useForm } from 'react-hook-form';
+import { useEffect } from 'react';
-import { DocumentStatus, Field, Recipient } from '@documenso/prisma/client';
-import { DocumentWithData } from '@documenso/prisma/types/document-with-data';
+import { Controller, useForm } from 'react-hook-form';
+
+import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
+import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
+import type { Field, Recipient } from '@documenso/prisma/client';
+import { DocumentStatus, SendStatus } from '@documenso/prisma/client';
+import type { DocumentWithData } from '@documenso/prisma/types/document-with-data';
+import {
+ Accordion,
+ AccordionContent,
+ AccordionItem,
+ AccordionTrigger,
+} from '@documenso/ui/primitives/accordion';
import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@documenso/ui/primitives/select';
import { Textarea } from '@documenso/ui/primitives/textarea';
-import { TAddSubjectFormSchema } from './add-subject.types';
+import { Combobox } from '../combobox';
+import type { TAddSubjectFormSchema } from './add-subject.types';
import {
DocumentFlowFormContainerActions,
DocumentFlowFormContainerContent,
DocumentFlowFormContainerFooter,
DocumentFlowFormContainerStep,
} from './document-flow-root';
-import { DocumentFlowStep } from './types';
+import type { DocumentFlowStep } from './types';
export type AddSubjectFormProps = {
documentFlow: DocumentFlowStep;
@@ -29,27 +48,46 @@ export type AddSubjectFormProps = {
export const AddSubjectFormPartial = ({
documentFlow,
- recipients: _recipients,
- fields: _fields,
+ recipients: recipients,
+ fields: fields,
document,
numberOfSteps,
onSubmit,
}: AddSubjectFormProps) => {
const {
+ control,
register,
handleSubmit,
- formState: { errors, isSubmitting },
+ formState: { errors, isSubmitting, touchedFields },
+ getValues,
+ setValue,
} = useForm({
defaultValues: {
- email: {
+ meta: {
subject: document.documentMeta?.subject ?? '',
message: document.documentMeta?.message ?? '',
+ timezone: document.documentMeta?.timezone ?? DEFAULT_DOCUMENT_TIME_ZONE,
+ dateFormat: document.documentMeta?.dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT,
},
},
});
const onFormSubmit = handleSubmit(onSubmit);
+ const hasDateField = fields.find((field) => field.type === 'DATE');
+
+ const documentHasBeenSent = recipients.some(
+ (recipient) => recipient.sendStatus === SendStatus.SENT,
+ );
+
+ // We almost always want to set the timezone to the user's local timezone to avoid confusion
+ // when the document is signed.
+ useEffect(() => {
+ if (!touchedFields.meta?.timezone && !documentHasBeenSent) {
+ setValue('meta.timezone', Intl.DateTimeFormat().resolvedOptions().timeZone);
+ }
+ }, [documentHasBeenSent, setValue, touchedFields.meta?.timezone]);
+
return (
<>
@@ -65,10 +103,10 @@ export const AddSubjectFormPartial = ({
// placeholder="Subject"
className="bg-background mt-2"
disabled={isSubmitting}
- {...register('email.subject')}
+ {...register('meta.subject')}
/>
-
+
@@ -80,14 +118,12 @@ export const AddSubjectFormPartial = ({
id="message"
className="bg-background mt-2 h-32 resize-none"
disabled={isSubmitting}
- {...register('email.message')}
+ {...register('meta.message')}
/>
@@ -117,6 +153,67 @@ export const AddSubjectFormPartial = ({
+
+
+
+
+ Advanced Options
+
+
+
+ {hasDateField && (
+
+
+
+ (
+
+ )}
+ />
+
+ )}
+
+ {hasDateField && (
+
+
+
+ (
+ value && onChange(value)}
+ disabled={documentHasBeenSent}
+ />
+ )}
+ />
+
+ )}
+
+
+