import { zodResolver } from '@hookform/resolvers/zod'; import { msg, t } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; import type { TeamGlobalSettings } from '@prisma/client'; import { DocumentVisibility, OrganisationType, type RecipientRole } from '@prisma/client'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; import { useSession } from '@documenso/lib/client-only/providers/session'; import { DATE_FORMATS } from '@documenso/lib/constants/date-formats'; import { DOCUMENT_SIGNATURE_TYPES, DocumentSignatureType } from '@documenso/lib/constants/document'; import { type TEnvelopeExpirationPeriod, ZEnvelopeExpirationPeriod, } from '@documenso/lib/constants/envelope-expiration'; import { type TEnvelopeReminderSettings, ZEnvelopeReminderSettings, } from '@documenso/lib/constants/envelope-reminder'; import { SUPPORTED_LANGUAGES, SUPPORTED_LANGUAGE_CODES, isValidLanguageCode, } from '@documenso/lib/constants/i18n'; import { TIME_ZONES } from '@documenso/lib/constants/time-zones'; import type { TDefaultRecipients } from '@documenso/lib/types/default-recipients'; import { ZDefaultRecipientsSchema } from '@documenso/lib/types/default-recipients'; import { type TDocumentMetaDateFormat, ZDocumentMetaTimezoneSchema, } from '@documenso/lib/types/document-meta'; import { isPersonalLayout } from '@documenso/lib/utils/organisations'; import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter'; import { extractTeamSignatureSettings } from '@documenso/lib/utils/teams'; import { DocumentSignatureSettingsTooltip } from '@documenso/ui/components/document/document-signature-settings-tooltip'; import { ExpirationPeriodPicker } from '@documenso/ui/components/document/expiration-period-picker'; import { ReminderSettingsPicker } from '@documenso/ui/components/document/reminder-settings-picker'; import { RecipientRoleSelect } from '@documenso/ui/components/recipient/recipient-role-select'; import { Alert } from '@documenso/ui/primitives/alert'; import { AvatarWithText } from '@documenso/ui/primitives/avatar'; import { Button } from '@documenso/ui/primitives/button'; import { Combobox } from '@documenso/ui/primitives/combobox'; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from '@documenso/ui/primitives/form/form'; import { MultiSelectCombobox } from '@documenso/ui/primitives/multi-select-combobox'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@documenso/ui/primitives/select'; import { useOptionalCurrentTeam } from '~/providers/team'; import { DefaultRecipientsMultiSelectCombobox } from '../general/default-recipients-multiselect-combobox'; /** * Can't infer this from the schema since we need to keep the schema inside the component to allow * it to be dynamic. */ export type TDocumentPreferencesFormSchema = { documentVisibility: DocumentVisibility | null; documentLanguage: (typeof SUPPORTED_LANGUAGE_CODES)[number] | null; documentTimezone: string | null; documentDateFormat: TDocumentMetaDateFormat | null; includeSenderDetails: boolean | null; includeSigningCertificate: boolean | null; includeAuditLog: boolean | null; signatureTypes: DocumentSignatureType[]; defaultRecipients: TDefaultRecipients | null; delegateDocumentOwnership: boolean | null; aiFeaturesEnabled: boolean | null; envelopeExpirationPeriod: TEnvelopeExpirationPeriod | null; reminderSettings: TEnvelopeReminderSettings | null; }; type SettingsSubset = Pick< TeamGlobalSettings, | 'documentVisibility' | 'documentLanguage' | 'documentTimezone' | 'documentDateFormat' | 'includeSenderDetails' | 'includeSigningCertificate' | 'includeAuditLog' | 'typedSignatureEnabled' | 'uploadSignatureEnabled' | 'drawSignatureEnabled' | 'defaultRecipients' | 'delegateDocumentOwnership' | 'aiFeaturesEnabled' | 'envelopeExpirationPeriod' | 'reminderSettings' >; export type DocumentPreferencesFormProps = { settings: SettingsSubset; canInherit: boolean; isAiFeaturesConfigured?: boolean; onFormSubmit: (data: TDocumentPreferencesFormSchema) => Promise; }; export const DocumentPreferencesForm = ({ settings, onFormSubmit, canInherit, isAiFeaturesConfigured = false, }: DocumentPreferencesFormProps) => { const { _ } = useLingui(); const { user, organisations } = useSession(); const currentOrganisation = useCurrentOrganisation(); const optionalTeam = useOptionalCurrentTeam(); const isPersonalLayoutMode = isPersonalLayout(organisations); const isPersonalOrganisation = currentOrganisation.type === OrganisationType.PERSONAL; const placeholderEmail = user.email ?? 'user@example.com'; const ZDocumentPreferencesFormSchema = z.object({ documentVisibility: z.nativeEnum(DocumentVisibility).nullable(), documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES).nullable(), documentTimezone: z.string().nullable(), documentDateFormat: ZDocumentMetaTimezoneSchema.nullable(), includeSenderDetails: z.boolean().nullable(), includeSigningCertificate: z.boolean().nullable(), includeAuditLog: z.boolean().nullable(), signatureTypes: z.array(z.nativeEnum(DocumentSignatureType)).min(canInherit ? 0 : 1, { message: msg`At least one signature type must be enabled`.id, }), defaultRecipients: ZDefaultRecipientsSchema.nullable(), delegateDocumentOwnership: z.boolean().nullable(), aiFeaturesEnabled: z.boolean().nullable(), envelopeExpirationPeriod: ZEnvelopeExpirationPeriod.nullable(), reminderSettings: ZEnvelopeReminderSettings.nullable(), }); const form = useForm({ defaultValues: { documentVisibility: settings.documentVisibility, documentLanguage: isValidLanguageCode(settings.documentLanguage) ? settings.documentLanguage : null, documentTimezone: settings.documentTimezone, // eslint-disable-next-line @typescript-eslint/consistent-type-assertions documentDateFormat: settings.documentDateFormat as TDocumentMetaDateFormat | null, includeSenderDetails: settings.includeSenderDetails, includeSigningCertificate: settings.includeSigningCertificate, includeAuditLog: settings.includeAuditLog, signatureTypes: extractTeamSignatureSettings({ ...settings }), defaultRecipients: settings.defaultRecipients ? ZDefaultRecipientsSchema.parse(settings.defaultRecipients) : null, delegateDocumentOwnership: settings.delegateDocumentOwnership, aiFeaturesEnabled: settings.aiFeaturesEnabled, envelopeExpirationPeriod: settings.envelopeExpirationPeriod ?? null, reminderSettings: settings.reminderSettings ?? null, }, resolver: zodResolver(ZDocumentPreferencesFormSchema), }); return (
{!isPersonalLayoutMode && ( ( Default Document Visibility Controls the default visibility of an uploaded document. )} /> )} ( Default Document Language Controls the default language of an uploaded document. This will be used as the language in email communications with the recipients. )} /> ( Default Date Format )} /> ( Default Time Zone field.onChange(value)} testId="document-timezone-trigger" /> )} /> ( Default Signature Settings ({ label: _(option.label), value: option.value, }))} selectedValues={field.value} onChange={field.onChange} className="w-full bg-background" enableSearch={false} emptySelectionPlaceholder={ canInherit ? t`Inherit from organisation` : t`Select signature types` } testId="signature-types-trigger" /> {form.formState.errors.signatureTypes ? ( ) : ( Controls which signatures are allowed to be used when signing a document. )} )} /> {!isPersonalLayoutMode && !isPersonalOrganisation && ( ( Send on Behalf of Team
Preview
{field.value ? ( "{placeholderEmail}" on behalf of "Team Name" has invited you to sign "example document". ) : ( "Team Name" has invited you to sign "example document". )}
Controls the formatting of the message that will be sent when inviting a recipient to sign a document. If a custom message has been provided while configuring the document, it will be used instead.
)} /> )} ( Include the Signing Certificate in the Document Controls whether the signing certificate will be included in the document when it is downloaded. The signing certificate can still be downloaded from the logs page separately. )} /> ( Include the Audit Logs in the Document Controls whether the audit logs will be included in the document when it is downloaded. The audit logs can still be downloaded from the logs page separately. )} /> { const recipients = field.value ?? []; return ( Default Recipients {canInherit && ( )} {(field.value !== null || !canInherit) && (
{recipients.map((recipient, index) => { return (
{recipient.name || recipient.email} } secondaryText={ recipient.name ? ( {recipient.email} ) : undefined } className="flex-1" />
{ field.onChange( recipients.map((recipient, idx) => idx === index ? { ...recipient, role } : recipient, ), ); }} />
); })}
)} Recipients that will be automatically added to new documents.
); }} /> ( Delegate Document Ownership Enable team API tokens to delegate document ownership to another team member. )} /> ( Default Envelope Expiration Controls how long recipients have to complete signing before the document expires. After expiration, recipients can no longer sign the document. )} /> ( Default Signing Reminders Controls when and how often reminder emails are sent to recipients who have not yet completed signing. )} /> {isAiFeaturesConfigured && ( ( AI Features Enable AI-powered features such as automatic recipient detection. When enabled, document content will be sent to AI providers. We only use providers that do not retain data for training and prefer European regions where available. )} /> )}
); };