import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; import { useSession } from '@documenso/lib/client-only/providers/session'; import { IS_AI_FEATURES_CONFIGURED } from '@documenso/lib/constants/app'; import { DATE_FORMATS } from '@documenso/lib/constants/date-formats'; import { DOCUMENT_SIGNATURE_TYPES, DocumentSignatureType } from '@documenso/lib/constants/document'; import { isValidLanguageCode, SUPPORTED_LANGUAGE_CODES, SUPPORTED_LANGUAGES } 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, ZDocumentMetaDateFormatSchema } from '@documenso/lib/types/document-meta'; import { generateDefaultOrganisationSettings, isPersonalLayout } from '@documenso/lib/utils/organisations'; import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter'; import { extractTeamSignatureSettings, generateDefaultTeamSettings } from '@documenso/lib/utils/teams'; import { DocumentSignatureSettingsTooltip } from '@documenso/ui/components/document/document-signature-settings-tooltip'; import { RecipientRoleSelect } from '@documenso/ui/components/recipient/recipient-role-select'; import { AvatarWithText } from '@documenso/ui/primitives/avatar'; import { Combobox } from '@documenso/ui/primitives/combobox'; import { Form, FormControl, FormDescription, FormField, 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 { zodResolver } from '@hookform/resolvers/zod'; import { msg, t } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; import { DocumentVisibility, type RecipientRole, type TeamGlobalSettings } from '@prisma/client'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { DocumentPreferencesResetDialog } from '~/components/dialogs/document-preferences-reset-dialog'; import { useOptionalCurrentTeam } from '~/providers/team'; import { DefaultRecipientsMultiSelectCombobox } from '../general/default-recipients-multiselect-combobox'; import { FormStickySaveBar } from './form-sticky-save-bar'; import { InheritableField } from './inheritable-field'; /** * 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; signatureTypes: DocumentSignatureType[]; defaultRecipients: TDefaultRecipients | null; delegateDocumentOwnership: boolean | null; aiFeaturesEnabled: boolean | null; }; type SettingsSubset = Pick< TeamGlobalSettings, | 'documentVisibility' | 'documentLanguage' | 'documentTimezone' | 'documentDateFormat' | 'typedSignatureEnabled' | 'uploadSignatureEnabled' | 'drawSignatureEnabled' | 'defaultRecipients' | 'delegateDocumentOwnership' | 'aiFeaturesEnabled' >; export type DocumentPreferencesFormProps = { settings: SettingsSubset; canInherit: boolean; onFormSubmit: (data: TDocumentPreferencesFormSchema) => Promise; }; const getDocumentPreferencesFormValues = (settings: SettingsSubset): TDocumentPreferencesFormSchema => { const parsedDocumentDateFormat = ZDocumentMetaDateFormatSchema.safeParse(settings.documentDateFormat); return { documentVisibility: settings.documentVisibility, documentLanguage: isValidLanguageCode(settings.documentLanguage) ? settings.documentLanguage : null, documentTimezone: settings.documentTimezone, documentDateFormat: parsedDocumentDateFormat.success ? parsedDocumentDateFormat.data : null, signatureTypes: extractTeamSignatureSettings({ ...settings }), defaultRecipients: settings.defaultRecipients ? ZDefaultRecipientsSchema.parse(settings.defaultRecipients) : null, delegateDocumentOwnership: settings.delegateDocumentOwnership, aiFeaturesEnabled: settings.aiFeaturesEnabled, }; }; export const DocumentPreferencesForm = ({ settings, onFormSubmit, canInherit }: DocumentPreferencesFormProps) => { const { _ } = useLingui(); const { organisations } = useSession(); const currentOrganisation = useCurrentOrganisation(); const optionalTeam = useOptionalCurrentTeam(); const isAiFeaturesConfigured = IS_AI_FEATURES_CONFIGURED(); const isPersonalLayoutMode = isPersonalLayout(organisations); const ZDocumentPreferencesFormSchema = z.object({ documentVisibility: z.nativeEnum(DocumentVisibility).nullable(), documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES).nullable(), documentTimezone: z.string().nullable(), documentDateFormat: ZDocumentMetaDateFormatSchema.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(), }); const defaultValues = getDocumentPreferencesFormValues(settings); const defaultSettings = canInherit ? generateDefaultTeamSettings() : generateDefaultOrganisationSettings(); const baseResetValues = getDocumentPreferencesFormValues(defaultSettings); const resetValues = { ...baseResetValues, aiFeaturesEnabled: isAiFeaturesConfigured ? baseResetValues.aiFeaturesEnabled : defaultValues.aiFeaturesEnabled, }; const form = useForm({ defaultValues, resolver: zodResolver(ZDocumentPreferencesFormSchema), }); // Parse both sides through the schema so we compare canonical representations const parsedCurrentValues = ZDocumentPreferencesFormSchema.safeParse(defaultValues); const parsedResetValues = ZDocumentPreferencesFormSchema.safeParse(resetValues); const isResetToDefaultsVisible = !parsedCurrentValues.success || !parsedResetValues.success || JSON.stringify(parsedCurrentValues.data) !== JSON.stringify(parsedResetValues.data); const handleResetToDefaults = async () => { await onFormSubmit(resetValues); form.reset(resetValues); }; const handleFormSubmit = form.handleSubmit(async (data) => { try { await onFormSubmit(data); } catch { // The page handler surfaces its own error toast. Keep the form dirty so // the save bar stays visible and the user can retry. return; } form.reset(data); }); return (
{!isPersonalLayoutMode && ( ( Default Document Visibility} testId="document-visibility" > Controls the default visibility of an uploaded document. )} /> )} ( Default Document Language} testId="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} testId="document-date-format" > )} /> ( Default Time Zone} testId="document-timezone" > field.onChange(value)} testId="document-timezone-trigger" /> )} /> ( Default Signature Settings } testId="signature-types" > ({ 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. )} )} /> { const recipients = field.value ?? []; return ( Default Recipients} testId="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} testId="delegate-document-ownership" > Enable team API tokens to delegate document ownership to another team member. )} /> {isAiFeaturesConfigured && ( ( AI Features} testId="ai-features-enabled" > 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. )} /> )} form.reset()} resetToDefaults={ isResetToDefaultsVisible ? ( ) : undefined } />
); };