mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 08:13:56 +10:00
feat: allow empty placeholder emails on templates (#1930)
Allow users to create template placeholders without the placeholder emails.
This commit is contained in:
@ -24,6 +24,7 @@ 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 { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||
import { isTemplateRecipientEmailPlaceholder } from '@documenso/lib/constants/template';
|
||||
import {
|
||||
type TFieldMetaSchema as FieldMeta,
|
||||
ZFieldMetaSchema,
|
||||
@ -593,15 +594,21 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
selectedSignerStyles?.comboxBoxTrigger,
|
||||
)}
|
||||
>
|
||||
{selectedSigner?.email && (
|
||||
<span className="flex-1 truncate text-left">
|
||||
{selectedSigner?.name} ({selectedSigner?.email})
|
||||
</span>
|
||||
)}
|
||||
{selectedSigner?.email &&
|
||||
!isTemplateRecipientEmailPlaceholder(selectedSigner.email) && (
|
||||
<span className="flex-1 truncate text-left">
|
||||
{selectedSigner?.name} ({selectedSigner?.email})
|
||||
</span>
|
||||
)}
|
||||
|
||||
{selectedSigner?.email &&
|
||||
isTemplateRecipientEmailPlaceholder(selectedSigner.email) && (
|
||||
<span className="flex-1 truncate text-left">{selectedSigner?.name}</span>
|
||||
)}
|
||||
|
||||
{!selectedSigner?.email && (
|
||||
<span className="gradie flex-1 truncate text-left">
|
||||
{selectedSigner?.email}
|
||||
No recipient selected
|
||||
</span>
|
||||
)}
|
||||
|
||||
@ -657,15 +664,22 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
'text-foreground/80': recipient === selectedSigner,
|
||||
})}
|
||||
>
|
||||
{recipient.name && (
|
||||
<span title={`${recipient.name} (${recipient.email})`}>
|
||||
{recipient.name} ({recipient.email})
|
||||
</span>
|
||||
)}
|
||||
{recipient.name &&
|
||||
!isTemplateRecipientEmailPlaceholder(recipient.email) && (
|
||||
<span title={`${recipient.name} (${recipient.email})`}>
|
||||
{recipient.name} ({recipient.email})
|
||||
</span>
|
||||
)}
|
||||
|
||||
{!recipient.name && (
|
||||
<span title={recipient.email}>{recipient.email}</span>
|
||||
)}
|
||||
{recipient.name &&
|
||||
isTemplateRecipientEmailPlaceholder(recipient.email) && (
|
||||
<span title={recipient.name}>{recipient.name}</span>
|
||||
)}
|
||||
|
||||
{!recipient.name &&
|
||||
!isTemplateRecipientEmailPlaceholder(recipient.email) && (
|
||||
<span title={recipient.email}>{recipient.email}</span>
|
||||
)}
|
||||
</span>
|
||||
</CommandItem>
|
||||
))}
|
||||
|
||||
@ -14,6 +14,7 @@ import { useFieldArray, useForm } from 'react-hook-form';
|
||||
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||
import { isTemplateRecipientEmailPlaceholder } from '@documenso/lib/constants/template';
|
||||
import { ZRecipientAuthOptionsSchema } from '@documenso/lib/types/document-auth';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { generateRecipientPlaceholder } from '@documenso/lib/utils/templates';
|
||||
@ -247,62 +248,6 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
[form, watchedSigners, toast],
|
||||
);
|
||||
|
||||
const triggerDragAndDrop = useCallback(
|
||||
(fromIndex: number, toIndex: number) => {
|
||||
if (!$sensorApi.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const draggableId = signers[fromIndex].id;
|
||||
|
||||
const preDrag = $sensorApi.current.tryGetLock(draggableId);
|
||||
|
||||
if (!preDrag) {
|
||||
return;
|
||||
}
|
||||
|
||||
const drag = preDrag.snapLift();
|
||||
|
||||
setTimeout(() => {
|
||||
// Move directly to the target index
|
||||
if (fromIndex < toIndex) {
|
||||
for (let i = fromIndex; i < toIndex; i++) {
|
||||
drag.moveDown();
|
||||
}
|
||||
} else {
|
||||
for (let i = fromIndex; i > toIndex; i--) {
|
||||
drag.moveUp();
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
drag.drop();
|
||||
}, 500);
|
||||
}, 0);
|
||||
},
|
||||
[signers],
|
||||
);
|
||||
|
||||
const updateSigningOrders = useCallback(
|
||||
(newIndex: number, oldIndex: number) => {
|
||||
const updatedSigners = form.getValues('signers').map((signer, index) => {
|
||||
if (index === oldIndex) {
|
||||
return { ...signer, signingOrder: newIndex + 1 };
|
||||
} else if (index >= newIndex && index < oldIndex) {
|
||||
return { ...signer, signingOrder: (signer.signingOrder ?? index + 1) + 1 };
|
||||
} else if (index <= newIndex && index > oldIndex) {
|
||||
return { ...signer, signingOrder: Math.max(1, (signer.signingOrder ?? index + 1) - 1) };
|
||||
}
|
||||
return signer;
|
||||
});
|
||||
|
||||
updatedSigners.forEach((signer, index) => {
|
||||
form.setValue(`signers.${index}.signingOrder`, signer.signingOrder);
|
||||
});
|
||||
},
|
||||
[form],
|
||||
);
|
||||
|
||||
const handleSigningOrderChange = useCallback(
|
||||
(index: number, newOrderString: string) => {
|
||||
const trimmedOrderString = newOrderString.trim();
|
||||
@ -592,7 +537,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
})}
|
||||
>
|
||||
{!showAdvancedSettings && index === 0 && (
|
||||
<FormLabel required>
|
||||
<FormLabel>
|
||||
<Trans>Email</Trans>
|
||||
</FormLabel>
|
||||
)}
|
||||
@ -602,6 +547,11 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
type="email"
|
||||
placeholder={_(msg`Email`)}
|
||||
{...field}
|
||||
value={
|
||||
isTemplateRecipientEmailPlaceholder(field.value)
|
||||
? ''
|
||||
: field.value
|
||||
}
|
||||
disabled={
|
||||
field.disabled ||
|
||||
isSubmitting ||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
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
|
||||
@ -10,7 +11,7 @@ export const ZAddTemplatePlacholderRecipientsFormSchema = z
|
||||
formId: z.string().min(1),
|
||||
nativeId: z.number().optional(),
|
||||
email: z.string().min(1).email(),
|
||||
name: z.string(),
|
||||
name: z.string().min(1, { message: 'Name is required' }),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
actionAuth: z.array(ZRecipientActionAuthTypesSchema).optional().default([]),
|
||||
@ -21,12 +22,25 @@ export const ZAddTemplatePlacholderRecipientsFormSchema = z
|
||||
})
|
||||
.refine(
|
||||
(schema) => {
|
||||
const emails = schema.signers.map((signer) => signer.email.toLowerCase());
|
||||
const nonPlaceholderEmails = schema.signers
|
||||
.map((signer) => signer.email.toLowerCase())
|
||||
.filter((email) => !TEMPLATE_RECIPIENT_EMAIL_PLACEHOLDER_REGEX.test(email));
|
||||
|
||||
return new Set(emails).size === emails.length;
|
||||
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.
|
||||
If we don't do this, the app will add duplicate signers and merge them in the next step, where you add fields.
|
||||
*/
|
||||
(schema) => {
|
||||
const names = schema.signers.map((signer) => signer.name.trim());
|
||||
return new Set(names).size === names.length;
|
||||
},
|
||||
{ message: 'Signers must have unique names', path: ['signers__root'] },
|
||||
);
|
||||
|
||||
export type TAddTemplatePlacholderRecipientsFormSchema = z.infer<
|
||||
|
||||
Reference in New Issue
Block a user