Merge branch 'main' into feat/expiry-links

This commit is contained in:
Lucas Smith
2025-10-06 16:39:34 +11:00
committed by GitHub
183 changed files with 10954 additions and 1513 deletions

View File

@ -39,7 +39,9 @@ export interface BadgeProps
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, size, ...props }: BadgeProps) {
return <div className={cn(badgeVariants({ variant, size }), className)} {...props} />;
return (
<div role="status" className={cn(badgeVariants({ variant, size }), className)} {...props} />
);
}
export { Badge, badgeVariants };

View File

@ -65,6 +65,27 @@ const CommandInput = React.forwardRef<
CommandInput.displayName = CommandPrimitive.Input.displayName;
const CommandTextInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
<div cmdk-input-wrapper="">
<CommandPrimitive.Input
ref={ref}
className={cn(
'bg-background border-input ring-offset-background placeholder:text-muted-foreground/40 focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className,
{
'ring-2 !ring-red-500 transition-all': props['aria-invalid'],
},
)}
{...props}
/>
</div>
));
CommandTextInput.displayName = CommandPrimitive.Input.displayName;
const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
@ -147,6 +168,7 @@ export {
Command,
CommandDialog,
CommandInput,
CommandTextInput,
CommandList,
CommandEmpty,
CommandGroup,

View File

@ -1,5 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
@ -46,7 +47,7 @@ import { Form } from '../form/form';
import { RecipientSelector } from '../recipient-selector';
import { useStep } from '../stepper';
import { useToast } from '../use-toast';
import type { TAddFieldsFormSchema } from './add-fields.types';
import { type TAddFieldsFormSchema, ZAddFieldsFormSchema } from './add-fields.types';
import {
DocumentFlowFormContainerActions,
DocumentFlowFormContainerContent,
@ -75,6 +76,7 @@ export type FieldFormType = {
pageWidth: number;
pageHeight: number;
signerEmail: string;
recipientId: number;
fieldMeta?: FieldMeta;
};
@ -127,9 +129,11 @@ export const AddFieldsFormPartial = ({
pageHeight: Number(field.height),
signerEmail:
recipients.find((recipient) => recipient.id === field.recipientId)?.email ?? '',
recipientId: field.recipientId,
fieldMeta: field.fieldMeta ? ZFieldMetaSchema.parse(field.fieldMeta) : undefined,
})),
},
resolver: zodResolver(ZAddFieldsFormSchema),
});
useHotkeys(['ctrl+c', 'meta+c'], (evt) => onFieldCopy(evt));
@ -323,6 +327,7 @@ export const AddFieldsFormPartial = ({
const field = {
formId: nanoid(12),
nativeId: undefined,
type: selectedField,
pageNumber,
pageX,
@ -330,6 +335,7 @@ export const AddFieldsFormPartial = ({
pageWidth: fieldPageWidth,
pageHeight: fieldPageHeight,
signerEmail: selectedSigner.email,
recipientId: selectedSigner.id,
fieldMeta: undefined,
};
@ -414,6 +420,7 @@ export const AddFieldsFormPartial = ({
nativeId: undefined,
formId: nanoid(12),
signerEmail: selectedSigner?.email ?? lastActiveField.signerEmail,
recipientId: selectedSigner?.id ?? lastActiveField.recipientId,
pageX: lastActiveField.pageX + 3,
pageY: lastActiveField.pageY + 3,
};
@ -438,6 +445,7 @@ export const AddFieldsFormPartial = ({
nativeId: undefined,
formId: nanoid(12),
signerEmail: selectedSigner?.email ?? lastActiveField.signerEmail,
recipientId: selectedSigner?.id ?? lastActiveField.recipientId,
pageNumber,
};
@ -470,6 +478,7 @@ export const AddFieldsFormPartial = ({
nativeId: undefined,
formId: nanoid(12),
signerEmail: selectedSigner?.email ?? copiedField.signerEmail,
recipientId: selectedSigner?.id ?? copiedField.recipientId,
pageX: copiedField.pageX + 3,
pageY: copiedField.pageY + 3,
});
@ -663,7 +672,7 @@ export const AddFieldsFormPartial = ({
{isDocumentPdfLoaded &&
localFields.map((field, index) => {
const recipientIndex = recipients.findIndex((r) => r.email === field.signerEmail);
const recipientIndex = recipients.findIndex((r) => r.id === field.recipientId);
const hasFieldError =
emptyCheckboxFields.find((f) => f.formId === field.formId) ||
emptyRadioFields.find((f) => f.formId === field.formId) ||

View File

@ -10,6 +10,7 @@ export const ZAddFieldsFormSchema = z.object({
nativeId: z.number().optional(),
type: z.nativeEnum(FieldType),
signerEmail: z.string().min(1),
recipientId: z.number().min(1),
pageNumber: z.number().min(1),
pageX: z.number().min(0),
pageY: z.number().min(0),

View File

@ -242,6 +242,7 @@ export const AddSettingsFormPartial = ({
className="bg-background"
{...field}
disabled={document.status !== DocumentStatus.DRAFT || field.disabled}
maxLength={255}
onBlur={handleAutoSave}
/>
</FormControl>

View File

@ -1,4 +1,4 @@
import React, { useCallback, useId, useMemo, useRef, useState } from 'react';
import { useCallback, useId, useMemo, useRef, useState } from 'react';
import type { DropResult, SensorAPI } from '@hello-pangea/dnd';
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
@ -15,11 +15,13 @@ import { prop, sortBy } from 'remeda';
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
import { useAutoSave } from '@documenso/lib/client-only/hooks/use-autosave';
import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { ZRecipientAuthOptionsSchema } from '@documenso/lib/types/document-auth';
import { nanoid } from '@documenso/lib/universal/id';
import { canRecipientBeModified as utilCanRecipientBeModified } from '@documenso/lib/utils/recipients';
import { trpc } from '@documenso/trpc/react';
import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out';
import { RecipientActionAuthSelect } from '@documenso/ui/components/recipient/recipient-action-auth-select';
import { RecipientRoleSelect } from '@documenso/ui/components/recipient/recipient-role-select';
@ -29,6 +31,8 @@ import {
DocumentReadOnlyFields,
mapFieldsWithRecipients,
} from '../../components/document/document-read-only-fields';
import type { RecipientAutoCompleteOption } from '../../components/recipient/recipient-autocomplete-input';
import { RecipientAutoCompleteInput } from '../../components/recipient/recipient-autocomplete-input';
import { Button } from '../button';
import { Checkbox } from '../checkbox';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '../form/form';
@ -49,6 +53,10 @@ import {
import { SigningOrderConfirmation } from './signing-order-confirmation';
import type { DocumentFlowStep } from './types';
type AutoSaveResponse = {
recipients: Recipient[];
};
export type AddSignersFormProps = {
documentFlow: DocumentFlowStep;
recipients: Recipient[];
@ -56,7 +64,7 @@ export type AddSignersFormProps = {
signingOrder?: DocumentSigningOrder | null;
allowDictateNextSigner?: boolean;
onSubmit: (_data: TAddSignersFormSchema) => void;
onAutoSave: (_data: TAddSignersFormSchema) => Promise<void>;
onAutoSave: (_data: TAddSignersFormSchema) => Promise<AutoSaveResponse>;
isDocumentPdfLoaded: boolean;
};
@ -75,6 +83,10 @@ export const AddSignersFormPartial = ({
const { remaining } = useLimits();
const { user } = useSession();
const [recipientSearchQuery, setRecipientSearchQuery] = useState('');
const debouncedRecipientSearchQuery = useDebouncedValue(recipientSearchQuery, 500);
const initialId = useId();
const $sensorApi = useRef<SensorAPI | null>(null);
@ -82,6 +94,17 @@ export const AddSignersFormPartial = ({
const organisation = useCurrentOrganisation();
const { data: recipientSuggestionsData, isLoading } = trpc.recipient.suggestions.find.useQuery(
{
query: debouncedRecipientSearchQuery,
},
{
enabled: debouncedRecipientSearchQuery.length > 1,
},
);
const recipientSuggestions = recipientSuggestionsData?.results || [];
const defaultRecipients = [
{
formId: initialId,
@ -189,7 +212,44 @@ export const AddSignersFormPartial = ({
const formData = form.getValues();
scheduleSave(formData);
scheduleSave(formData, (response) => {
// Sync the response recipients back to form state to prevent duplicates
if (response?.recipients) {
const currentSigners = form.getValues('signers');
const updatedSigners = currentSigners.map((signer) => {
// Find the matching recipient from the response using nativeId
const matchingRecipient = response.recipients.find(
(recipient) => recipient.id === signer.nativeId,
);
if (matchingRecipient) {
// Update the signer with the server-returned data, especially the ID
return {
...signer,
nativeId: matchingRecipient.id,
};
}
// For new signers without nativeId, match by email and update with server ID
if (!signer.nativeId) {
const newRecipient = response.recipients.find(
(recipient) => recipient.email === signer.email,
);
if (newRecipient) {
return {
...signer,
nativeId: newRecipient.id,
};
}
}
return signer;
});
// Update the form state with the synced data
form.setValue('signers', updatedSigners, { shouldValidate: false });
}
});
};
const emptySignerIndex = watchedSigners.findIndex((signer) => !signer.name && !signer.email);
@ -286,10 +346,12 @@ export const AddSignersFormPartial = ({
}
};
const onKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter' && event.target instanceof HTMLInputElement) {
onAddSigner();
}
const handleRecipientAutoCompleteSelect = (
index: number,
suggestion: RecipientAutoCompleteOption,
) => {
setValue(`signers.${index}.email`, suggestion.email);
setValue(`signers.${index}.name`, suggestion.name || '');
};
const onDragEnd = useCallback(
@ -679,17 +741,26 @@ export const AddSignersFormPartial = ({
)}
<FormControl>
<Input
<RecipientAutoCompleteInput
type="email"
placeholder={_(msg`Email`)}
{...field}
value={field.value}
disabled={
snapshot.isDragging ||
isSubmitting ||
!canRecipientBeModified(signer.nativeId)
}
options={recipientSuggestions}
onSelect={(suggestion) =>
handleRecipientAutoCompleteSelect(index, suggestion)
}
onSearchQueryChange={(query) => {
field.onChange(query);
setRecipientSearchQuery(query);
}}
loading={isLoading}
data-testid="signer-email-input"
onKeyDown={onKeyDown}
maxLength={254}
onBlur={handleAutoSave}
/>
</FormControl>
@ -719,7 +790,8 @@ export const AddSignersFormPartial = ({
)}
<FormControl>
<Input
<RecipientAutoCompleteInput
type="text"
placeholder={_(msg`Name`)}
{...field}
disabled={
@ -727,7 +799,16 @@ export const AddSignersFormPartial = ({
isSubmitting ||
!canRecipientBeModified(signer.nativeId)
}
onKeyDown={onKeyDown}
options={recipientSuggestions}
onSelect={(suggestion) =>
handleRecipientAutoCompleteSelect(index, suggestion)
}
onSearchQueryChange={(query) => {
field.onChange(query);
setRecipientSearchQuery(query);
}}
loading={isLoading}
maxLength={255}
onBlur={handleAutoSave}
/>
</FormControl>

View File

@ -4,33 +4,23 @@ import { z } from 'zod';
import { ZRecipientActionAuthTypesSchema } from '@documenso/lib/types/document-auth';
export const ZAddSignersFormSchema = z
.object({
signers: z.array(
z.object({
formId: z.string().min(1),
nativeId: z.number().optional(),
email: z
.string()
.email({ message: msg`Invalid email`.id })
.min(1),
name: z.string(),
role: z.nativeEnum(RecipientRole),
signingOrder: z.number().optional(),
actionAuth: z.array(ZRecipientActionAuthTypesSchema).optional().default([]),
}),
),
signingOrder: z.nativeEnum(DocumentSigningOrder),
allowDictateNextSigner: z.boolean().default(false),
})
.refine(
(schema) => {
const emails = schema.signers.map((signer) => signer.email.toLowerCase());
return new Set(emails).size === emails.length;
},
// Dirty hack to handle errors when .root is populated for an array type
{ message: msg`Signers must have unique emails`.id, path: ['signers__root'] },
);
export const ZAddSignersFormSchema = z.object({
signers: z.array(
z.object({
formId: z.string().min(1),
nativeId: z.number().optional(),
email: z
.string()
.email({ message: msg`Invalid email`.id })
.min(1),
name: z.string(),
role: z.nativeEnum(RecipientRole),
signingOrder: z.number().optional(),
actionAuth: z.array(ZRecipientActionAuthTypesSchema).optional().default([]),
}),
),
signingOrder: z.nativeEnum(DocumentSigningOrder),
allowDictateNextSigner: z.boolean().default(false),
});
export type TAddSignersFormSchema = z.infer<typeof ZAddSignersFormSchema>;

View File

@ -262,7 +262,7 @@ export const AddSubjectFormPartial = ({
</FormLabel>
<FormControl>
<Input {...field} />
<Input {...field} maxLength={254} />
</FormControl>
<FormMessage />
@ -300,7 +300,7 @@ export const AddSubjectFormPartial = ({
</FormLabel>
<FormControl>
<Input {...field} />
<Input {...field} maxLength={255} />
</FormControl>
<FormMessage />
</FormItem>
@ -326,7 +326,11 @@ export const AddSubjectFormPartial = ({
</FormLabel>
<FormControl>
<Textarea className="bg-background mt-2 h-16 resize-none" {...field} />
<Textarea
className="bg-background mt-2 h-16 resize-none"
{...field}
maxLength={5000}
/>
</FormControl>
<FormMessage />
</FormItem>

View File

@ -299,6 +299,8 @@ export const FieldItem = ({
}}
ref={$el}
data-field-id={field.nativeId}
data-field-type={field.type}
data-recipient-id={field.recipientId}
>
<FieldContent field={field} />

View File

@ -8,19 +8,14 @@ import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
export const ZDocumentFlowFormSchema = z.object({
title: z.string().min(1),
signers: z
.array(
z.object({
formId: z.string().min(1),
nativeId: z.number().optional(),
email: z.string().min(1).email(),
name: z.string(),
}),
)
.refine((signers) => {
const emails = signers.map((signer) => signer.email);
return new Set(emails).size === emails.length;
}, 'Signers must have unique emails'),
signers: z.array(
z.object({
formId: z.string().min(1),
nativeId: z.number().optional(),
email: z.string().min(1).email(),
name: z.string(),
}),
),
fields: z.array(
z.object({
@ -28,6 +23,7 @@ export const ZDocumentFlowFormSchema = z.object({
nativeId: z.number().optional(),
type: z.nativeEnum(FieldType),
signerEmail: z.string().min(1).optional(),
recipientId: z.number().min(1),
pageNumber: z.number().min(1),
pageX: z.number().min(0),
pageY: z.number().min(0),

View File

@ -8,6 +8,8 @@ const Popover = PopoverPrimitive.Root;
const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverAnchor = PopoverPrimitive.Anchor;
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
@ -91,4 +93,4 @@ const PopoverHover = ({ trigger, children, contentProps, side = 'top' }: Popover
);
};
export { Popover, PopoverTrigger, PopoverContent, PopoverHover };
export { Popover, PopoverTrigger, PopoverAnchor, PopoverContent, PopoverHover };

View File

@ -3,6 +3,7 @@ import { useState } from 'react';
import { KeyboardIcon, UploadCloudIcon } from 'lucide-react';
import { match } from 'ts-pattern';
import { Trans } from '@lingui/react/macro';
import { DocumentSignatureType } from '@documenso/lib/constants/document';
import { isBase64Image } from '@documenso/lib/constants/signatures';
@ -146,21 +147,21 @@ export const SignaturePad = ({
{drawSignatureEnabled && (
<TabsTrigger value="draw">
<SignatureIcon className="mr-2 size-4" />
Draw
<Trans>Draw</Trans>
</TabsTrigger>
)}
{typedSignatureEnabled && (
<TabsTrigger value="text">
<KeyboardIcon className="mr-2 size-4" />
Type
<Trans>Type</Trans>
</TabsTrigger>
)}
{uploadSignatureEnabled && (
<TabsTrigger value="image">
<UploadCloudIcon className="mr-2 size-4" />
Upload
<Trans>Upload</Trans>
</TabsTrigger>
)}
</TabsList>

View File

@ -1,5 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
@ -61,7 +62,10 @@ import type { FieldFormType } from '../document-flow/add-fields';
import { FieldAdvancedSettings } from '../document-flow/field-item-advanced-settings';
import { Form } from '../form/form';
import { useStep } from '../stepper';
import type { TAddTemplateFieldsFormSchema } from './add-template-fields.types';
import {
type TAddTemplateFieldsFormSchema,
ZAddTemplateFieldsFormSchema,
} from './add-template-fields.types';
const MIN_HEIGHT_PX = 12;
const MIN_WIDTH_PX = 36;
@ -112,7 +116,7 @@ export const AddTemplateFieldsFormPartial = ({
pageY: Number(field.positionY),
pageWidth: Number(field.width),
pageHeight: Number(field.height),
signerId: field.recipientId ?? -1,
recipientId: field.recipientId ?? -1,
signerEmail:
recipients.find((recipient) => recipient.id === field.recipientId)?.email ?? '',
signerToken:
@ -120,6 +124,7 @@ export const AddTemplateFieldsFormPartial = ({
fieldMeta: field.fieldMeta ? ZFieldMetaSchema.parse(field.fieldMeta) : undefined,
})),
},
resolver: zodResolver(ZAddTemplateFieldsFormSchema),
});
const onFormSubmit = form.handleSubmit(onSubmit);
@ -170,7 +175,7 @@ export const AddTemplateFieldsFormPartial = ({
nativeId: undefined,
formId: nanoid(12),
signerEmail: selectedSigner?.email ?? lastActiveField.signerEmail,
signerId: selectedSigner?.id ?? lastActiveField.signerId,
recipientId: selectedSigner?.id ?? lastActiveField.recipientId,
signerToken: selectedSigner?.token ?? lastActiveField.signerToken,
pageX: lastActiveField.pageX + 3,
pageY: lastActiveField.pageY + 3,
@ -197,7 +202,7 @@ export const AddTemplateFieldsFormPartial = ({
nativeId: undefined,
formId: nanoid(12),
signerEmail: selectedSigner?.email ?? lastActiveField.signerEmail,
signerId: selectedSigner?.id ?? lastActiveField.signerId,
recipientId: selectedSigner?.id ?? lastActiveField.recipientId,
signerToken: selectedSigner?.token ?? lastActiveField.signerToken,
pageNumber,
};
@ -240,7 +245,7 @@ export const AddTemplateFieldsFormPartial = ({
formId: nanoid(12),
nativeId: undefined,
signerEmail: selectedSigner?.email ?? copiedField.signerEmail,
signerId: selectedSigner?.id ?? copiedField.signerId,
recipientId: selectedSigner?.id ?? copiedField.recipientId,
signerToken: selectedSigner?.token ?? copiedField.signerToken,
pageX: copiedField.pageX + 3,
pageY: copiedField.pageY + 3,
@ -371,7 +376,7 @@ export const AddTemplateFieldsFormPartial = ({
pageWidth: fieldPageWidth,
pageHeight: fieldPageHeight,
signerEmail: selectedSigner.email,
signerId: selectedSigner.id,
recipientId: selectedSigner.id,
signerToken: selectedSigner.token ?? '',
fieldMeta: undefined,
};
@ -597,14 +602,14 @@ export const AddTemplateFieldsFormPartial = ({
)}
{localFields.map((field, index) => {
const recipientIndex = recipients.findIndex((r) => r.email === field.signerEmail);
const recipientIndex = recipients.findIndex((r) => r.id === field.recipientId);
return (
<FieldItem
key={index}
recipientIndex={recipientIndex === -1 ? 0 : recipientIndex}
field={field}
disabled={selectedSigner?.email !== field.signerEmail}
disabled={selectedSigner?.id !== field.recipientId}
minHeight={MIN_HEIGHT_PX}
minWidth={MIN_WIDTH_PX}
defaultHeight={DEFAULT_HEIGHT_PX}

View File

@ -10,8 +10,8 @@ export const ZAddTemplateFieldsFormSchema = z.object({
nativeId: z.number().optional(),
type: z.nativeEnum(FieldType),
signerEmail: z.string().min(1),
recipientId: z.number().min(1),
signerToken: z.string(),
signerId: z.number().optional(),
pageNumber: z.number().min(1),
pageX: z.number().min(0),
pageY: z.number().min(0),

View File

@ -48,6 +48,10 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip';
import type { TAddTemplatePlacholderRecipientsFormSchema } from './add-template-placeholder-recipients.types';
import { ZAddTemplatePlacholderRecipientsFormSchema } from './add-template-placeholder-recipients.types';
type AutoSaveResponse = {
recipients: Recipient[];
};
export type AddTemplatePlaceholderRecipientsFormProps = {
documentFlow: DocumentFlowStep;
recipients: Recipient[];
@ -56,7 +60,7 @@ export type AddTemplatePlaceholderRecipientsFormProps = {
allowDictateNextSigner?: boolean;
templateDirectLink?: TemplateDirectLink | null;
onSubmit: (_data: TAddTemplatePlacholderRecipientsFormSchema) => void;
onAutoSave: (_data: TAddTemplatePlacholderRecipientsFormSchema) => Promise<void>;
onAutoSave: (_data: TAddTemplatePlacholderRecipientsFormSchema) => Promise<AutoSaveResponse>;
isDocumentPdfLoaded: boolean;
};
@ -146,7 +150,44 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
const formData = form.getValues();
scheduleSave(formData);
scheduleSave(formData, (response) => {
// Sync the response recipients back to form state to prevent duplicates
if (response?.recipients) {
const currentSigners = form.getValues('signers');
const updatedSigners = currentSigners.map((signer) => {
// Find the matching recipient from the response using nativeId
const matchingRecipient = response.recipients.find(
(recipient) => recipient.id === signer.nativeId,
);
if (matchingRecipient) {
// Update the signer with the server-returned data, especially the ID
return {
...signer,
nativeId: matchingRecipient.id,
};
}
// For new signers without nativeId, match by email and update with server ID
if (!signer.nativeId) {
const newRecipient = response.recipients.find(
(recipient) => recipient.email === signer.email,
);
if (newRecipient) {
return {
...signer,
nativeId: newRecipient.id,
};
}
}
return signer;
});
// Update the form state with the synced data
form.setValue('signers', updatedSigners, { shouldValidate: false });
}
});
};
// useEffect(() => {
@ -627,6 +668,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
signers[index].email === user?.email ||
isSignerDirectRecipient(signer)
}
maxLength={254}
onBlur={handleAutoSave}
data-testid="placeholder-recipient-email-input"
/>
@ -663,6 +705,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
signers[index].email === user?.email ||
isSignerDirectRecipient(signer)
}
maxLength={255}
onBlur={handleAutoSave}
data-testid="placeholder-recipient-name-input"
/>

View File

@ -1,7 +1,6 @@
import { DocumentSigningOrder, RecipientRole } from '@prisma/client';
import { z } from 'zod';
import { TEMPLATE_RECIPIENT_EMAIL_PLACEHOLDER_REGEX } from '@documenso/lib/constants/template';
import { ZRecipientActionAuthTypesSchema } from '@documenso/lib/types/document-auth';
export const ZAddTemplatePlacholderRecipientsFormSchema = z
@ -20,17 +19,7 @@ export const ZAddTemplatePlacholderRecipientsFormSchema = z
signingOrder: z.nativeEnum(DocumentSigningOrder),
allowDictateNextSigner: z.boolean().default(false),
})
.refine(
(schema) => {
const nonPlaceholderEmails = schema.signers
.map((signer) => signer.email.toLowerCase())
.filter((email) => !TEMPLATE_RECIPIENT_EMAIL_PLACEHOLDER_REGEX.test(email));
return new Set(nonPlaceholderEmails).size === nonPlaceholderEmails.length;
},
// Dirty hack to handle errors when .root is populated for an array type
{ message: 'Signers must have unique emails', path: ['signers__root'] },
)
.refine(
/*
Since placeholder emails are empty, we need to check that the names are unique.

View File

@ -216,7 +216,7 @@ export const AddTemplateSettingsFormPartial = ({
</FormLabel>
<FormControl>
<Input className="bg-background" {...field} onBlur={handleAutoSave} />
<Input className="bg-background" {...field} maxLength={255} onBlur={handleAutoSave} />
</FormControl>
<FormMessage />
</FormItem>
@ -519,7 +519,7 @@ export const AddTemplateSettingsFormPartial = ({
</FormLabel>
<FormControl>
<Input {...field} />
<Input {...field} maxLength={254} />
</FormControl>
<FormMessage />
@ -539,7 +539,7 @@ export const AddTemplateSettingsFormPartial = ({
</FormLabel>
<FormControl>
<Input {...field} onBlur={handleAutoSave} />
<Input {...field} maxLength={254} onBlur={handleAutoSave} />
</FormControl>
<FormMessage />
@ -569,6 +569,7 @@ export const AddTemplateSettingsFormPartial = ({
<Textarea
className="bg-background h-16 resize-none"
{...field}
maxLength={5000}
onBlur={handleAutoSave}
/>
</FormControl>
@ -623,7 +624,7 @@ export const AddTemplateSettingsFormPartial = ({
</FormLabel>
<FormControl>
<Input className="bg-background" {...field} onBlur={handleAutoSave} />
<Input className="bg-background" {...field} maxLength={255} onBlur={handleAutoSave} />
</FormControl>
<FormMessage />
@ -714,7 +715,7 @@ export const AddTemplateSettingsFormPartial = ({
</FormLabel>
<FormControl>
<Input className="bg-background" {...field} onBlur={handleAutoSave} />
<Input className="bg-background" {...field} maxLength={255} onBlur={handleAutoSave} />
</FormControl>
<FormMessage />