mirror of
https://github.com/documenso/documenso.git
synced 2025-11-16 01:32:06 +10:00
chore: merged main
This commit is contained in:
@ -4,6 +4,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { Caveat } from 'next/font/google';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import {
|
||||
CalendarDays,
|
||||
CheckSquare,
|
||||
@ -21,7 +22,7 @@ import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||
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 { RECIPIENT_ROLES_DESCRIPTION_ENG } from '@documenso/lib/constants/recipient-roles';
|
||||
import {
|
||||
type TFieldMetaSchema as FieldMeta,
|
||||
ZFieldMetaSchema,
|
||||
@ -111,6 +112,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
recipients.find((recipient) => recipient.id === field.recipientId)?.email ?? '',
|
||||
signerToken:
|
||||
recipients.find((recipient) => recipient.id === field.recipientId)?.token ?? '',
|
||||
fieldMeta: field.fieldMeta ? ZFieldMetaSchema.parse(field.fieldMeta) : undefined,
|
||||
})),
|
||||
},
|
||||
});
|
||||
@ -155,6 +157,38 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
selectedSignerIndex === -1 ? 0 : selectedSignerIndex,
|
||||
);
|
||||
|
||||
const filterFieldsWithEmptyValues = (fields: typeof localFields, fieldType: string) =>
|
||||
fields
|
||||
.filter((field) => field.type === fieldType)
|
||||
.filter((field) => {
|
||||
if (field.fieldMeta && 'values' in field.fieldMeta) {
|
||||
return field.fieldMeta.values?.length === 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const emptyCheckboxFields = useMemo(
|
||||
() => filterFieldsWithEmptyValues(localFields, FieldType.CHECKBOX),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[localFields],
|
||||
);
|
||||
|
||||
const emptyRadioFields = useMemo(
|
||||
() => filterFieldsWithEmptyValues(localFields, FieldType.RADIO),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[localFields],
|
||||
);
|
||||
|
||||
const emptySelectFields = useMemo(
|
||||
() => filterFieldsWithEmptyValues(localFields, FieldType.DROPDOWN),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[localFields],
|
||||
);
|
||||
|
||||
const hasErrors =
|
||||
emptyCheckboxFields.length > 0 || emptyRadioFields.length > 0 || emptySelectFields.length > 0;
|
||||
|
||||
const [isFieldWithinBounds, setIsFieldWithinBounds] = useState(false);
|
||||
const [coords, setCoords] = useState({
|
||||
x: 0,
|
||||
@ -365,8 +399,8 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<>
|
||||
{showAdvancedSettings && currentField ? (
|
||||
<FieldAdvancedSettings
|
||||
title="Advanced settings"
|
||||
description={`Configure the ${FRIENDLY_FIELD_TYPE[currentField.type]} field`}
|
||||
title={msg`Advanced settings`}
|
||||
description={msg`Configure the ${FRIENDLY_FIELD_TYPE[currentField.type]} field`}
|
||||
field={currentField}
|
||||
fields={localFields}
|
||||
onAdvancedSettings={handleAdvancedSettings}
|
||||
@ -460,14 +494,15 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
|
||||
<CommandEmpty>
|
||||
<span className="text-muted-foreground inline-block px-4">
|
||||
No recipient matching this description was found.
|
||||
<Trans>No recipient matching this description was found.</Trans>
|
||||
</span>
|
||||
</CommandEmpty>
|
||||
|
||||
{recipientsByRoleToDisplay.map(([role, roleRecipients], roleIndex) => (
|
||||
<CommandGroup key={roleIndex}>
|
||||
<div className="text-muted-foreground mb-1 ml-2 mt-2 text-xs font-medium">
|
||||
{`${RECIPIENT_ROLES_DESCRIPTION[role].roleName}s`}
|
||||
{/* Todo: Translations - Add plural translations. */}
|
||||
{`${RECIPIENT_ROLES_DESCRIPTION_ENG[role].roleName}s`}
|
||||
</div>
|
||||
|
||||
{roleRecipients.length === 0 && (
|
||||
@ -475,7 +510,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
key={`${role}-empty`}
|
||||
className="text-muted-foreground/80 px-4 pb-4 pt-2.5 text-center text-xs"
|
||||
>
|
||||
No recipients with this role
|
||||
<Trans>No recipients with this role</Trans>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -542,7 +577,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
fontCaveat.className,
|
||||
)}
|
||||
>
|
||||
Signature
|
||||
<Trans>Signature</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -594,7 +629,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<Mail className="h-4 w-4" />
|
||||
Email
|
||||
<Trans>Email</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -620,7 +655,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<User className="h-4 w-4" />
|
||||
Name
|
||||
<Trans>Name</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -646,7 +681,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<CalendarDays className="h-4 w-4" />
|
||||
Date
|
||||
<Trans>Date</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -672,7 +707,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<Type className="h-4 w-4" />
|
||||
Text
|
||||
<Trans>Text</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -698,7 +733,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<Hash className="h-4 w-4" />
|
||||
Number
|
||||
<Trans>Number</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -724,7 +759,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<Disc className="h-4 w-4" />
|
||||
Radio
|
||||
<Trans>Radio</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -750,7 +785,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<CheckSquare className="h-4 w-4" />
|
||||
Checkbox
|
||||
<Trans>Checkbox</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -776,7 +811,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
Dropdown
|
||||
<Trans>Dropdown</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -786,17 +821,32 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
</div>
|
||||
</DocumentFlowFormContainerContent>
|
||||
|
||||
{hasErrors && (
|
||||
<div className="mt-4">
|
||||
<ul>
|
||||
<li className="text-sm text-red-500">
|
||||
<Trans>
|
||||
To proceed further, please set at least one value for the{' '}
|
||||
{emptyCheckboxFields.length > 0
|
||||
? 'Checkbox'
|
||||
: emptyRadioFields.length > 0
|
||||
? 'Radio'
|
||||
: 'Select'}{' '}
|
||||
field.
|
||||
</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DocumentFlowFormContainerFooter>
|
||||
<DocumentFlowFormContainerStep
|
||||
title={documentFlow.title}
|
||||
step={currentStep}
|
||||
maxStep={totalSteps}
|
||||
/>
|
||||
<DocumentFlowFormContainerStep step={currentStep} maxStep={totalSteps} />
|
||||
|
||||
<DocumentFlowFormContainerActions
|
||||
loading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
goNextLabel="Save Template"
|
||||
goNextLabel={msg`Save Template`}
|
||||
disableNextStep={hasErrors}
|
||||
onGoBackClick={() => {
|
||||
previousStep();
|
||||
remove();
|
||||
|
||||
@ -5,6 +5,8 @@ import React, { useEffect, useId, useMemo, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Link2Icon, Plus, Trash } from 'lucide-react';
|
||||
import { useSession } from 'next-auth/react';
|
||||
@ -67,6 +69,8 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
const initialId = useId();
|
||||
|
||||
const { _ } = useLingui();
|
||||
const { data: session } = useSession();
|
||||
|
||||
const user = session?.user;
|
||||
@ -290,13 +294,15 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
})}
|
||||
>
|
||||
{!showAdvancedSettings && index === 0 && (
|
||||
<FormLabel required>Email</FormLabel>
|
||||
<FormLabel required>
|
||||
<Trans>Email</Trans>
|
||||
</FormLabel>
|
||||
)}
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
placeholder={_(msg`Email`)}
|
||||
{...field}
|
||||
disabled={field.disabled || isSubmitting}
|
||||
onBlur={() => void handleOnBlur(index)}
|
||||
@ -318,11 +324,15 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
'col-span-4': showAdvancedSettings,
|
||||
})}
|
||||
>
|
||||
{!showAdvancedSettings && index === 0 && <FormLabel>Name</FormLabel>}
|
||||
{!showAdvancedSettings && index === 0 && (
|
||||
<FormLabel>
|
||||
<Trans>Name</Trans>
|
||||
</FormLabel>
|
||||
)}
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Name"
|
||||
placeholder={_(msg`Name`)}
|
||||
{...field}
|
||||
disabled={field.disabled || isSubmitting}
|
||||
onBlur={() => void handleOnBlur(index)}
|
||||
@ -382,12 +392,14 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="text-foreground z-9999 max-w-md p-4">
|
||||
<h3 className="text-foreground text-lg font-semibold">
|
||||
Direct link receiver
|
||||
<Trans>Direct link receiver</Trans>
|
||||
</h3>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
This field cannot be modified or deleted. When you share this template's
|
||||
direct link or add it to your public profile, anyone who accesses it can
|
||||
input their name and email, and fill in the fields assigned to them.
|
||||
<Trans>
|
||||
This field cannot be modified or deleted. When you share this template's
|
||||
direct link or add it to your public profile, anyone who accesses it can
|
||||
input their name and email, and fill in the fields assigned to them.
|
||||
</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@ -423,7 +435,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
onClick={() => onAddPlaceholderRecipient()}
|
||||
>
|
||||
<Plus className="-ml-1 mr-2 h-5 w-5" />
|
||||
Add Placeholder Recipient
|
||||
<Trans>Add Placeholder Recipient</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -437,7 +449,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
onClick={() => onAddPlaceholderSelfRecipient()}
|
||||
>
|
||||
<Plus className="-ml-1 mr-2 h-5 w-5" />
|
||||
Add Myself
|
||||
<Trans>Add Myself</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -455,7 +467,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
className="text-muted-foreground ml-2 text-sm"
|
||||
htmlFor="showAdvancedRecipientSettings"
|
||||
>
|
||||
Show advanced settings
|
||||
<Trans>Show advanced settings</Trans>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
@ -464,11 +476,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
</DocumentFlowFormContainerContent>
|
||||
|
||||
<DocumentFlowFormContainerFooter>
|
||||
<DocumentFlowFormContainerStep
|
||||
title={documentFlow.title}
|
||||
step={currentStep}
|
||||
maxStep={totalSteps}
|
||||
/>
|
||||
<DocumentFlowFormContainerStep step={currentStep} maxStep={totalSteps} />
|
||||
|
||||
<DocumentFlowFormContainerActions
|
||||
loading={isSubmitting}
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
@ -72,6 +74,8 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
template,
|
||||
onSubmit,
|
||||
}: AddTemplateSettingsFormProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const { documentAuthOption } = extractDocumentAuthMethods({
|
||||
documentAuth: template.authOptions,
|
||||
});
|
||||
@ -126,7 +130,9 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required>Template title</FormLabel>
|
||||
<FormLabel required>
|
||||
<Trans>Template title</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Input className="bg-background" {...field} />
|
||||
@ -142,7 +148,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
Document access
|
||||
<Trans>Document access</Trans>
|
||||
<DocumentGlobalAuthAccessTooltip />
|
||||
</FormLabel>
|
||||
|
||||
@ -160,7 +166,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
Recipient action authentication
|
||||
<Trans>Recipient action authentication</Trans>
|
||||
<DocumentGlobalAuthActionTooltip />
|
||||
</FormLabel>
|
||||
|
||||
@ -175,7 +181,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
<Accordion type="multiple">
|
||||
<AccordionItem value="email-options" className="border-none">
|
||||
<AccordionTrigger className="text-foreground rounded border px-3 py-2 text-left hover:bg-neutral-200/30 hover:no-underline">
|
||||
Email Options
|
||||
<Trans>Email Options</Trans>
|
||||
</AccordionTrigger>
|
||||
|
||||
<AccordionContent className="text-muted-foreground -mx-1 px-1 pt-4 text-sm leading-relaxed [&>div]:pb-0">
|
||||
@ -186,7 +192,9 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Subject <span className="text-muted-foreground">(Optional)</span>
|
||||
<Trans>
|
||||
Subject <span className="text-muted-foreground">(Optional)</span>
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
@ -204,7 +212,9 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Message <span className="text-muted-foreground">(Optional)</span>
|
||||
<Trans>
|
||||
Message <span className="text-muted-foreground">(Optional)</span>
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
@ -225,7 +235,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
<Accordion type="multiple">
|
||||
<AccordionItem value="advanced-options" className="border-none">
|
||||
<AccordionTrigger className="text-foreground rounded border px-3 py-2 text-left hover:bg-neutral-200/30 hover:no-underline">
|
||||
Advanced Options
|
||||
<Trans>Advanced Options</Trans>
|
||||
</AccordionTrigger>
|
||||
|
||||
<AccordionContent className="text-muted-foreground -mx-1 px-1 pt-4 text-sm leading-relaxed">
|
||||
@ -236,15 +246,17 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
External ID{' '}
|
||||
<Trans>External ID</Trans>{' '}
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-muted-foreground max-w-xs">
|
||||
Add an external ID to the template. This can be used to identify in
|
||||
external systems.
|
||||
<Trans>
|
||||
Add an external ID to the template. This can be used to identify
|
||||
in external systems.
|
||||
</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormLabel>
|
||||
@ -263,7 +275,9 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
name="meta.dateFormat"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Date Format</FormLabel>
|
||||
<FormLabel>
|
||||
<Trans>Date Format</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Select {...field} onValueChange={field.onChange}>
|
||||
@ -291,7 +305,9 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
name="meta.timezone"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Time Zone</FormLabel>
|
||||
<FormLabel>
|
||||
<Trans>Time Zone</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Combobox
|
||||
@ -313,14 +329,16 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
Redirect URL{' '}
|
||||
<Trans>Redirect URL</Trans>{' '}
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-muted-foreground max-w-xs">
|
||||
Add a URL to redirect the user to once the document is signed
|
||||
<Trans>
|
||||
Add a URL to redirect the user to once the document is signed
|
||||
</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormLabel>
|
||||
@ -342,11 +360,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
</DocumentFlowFormContainerContent>
|
||||
|
||||
<DocumentFlowFormContainerFooter>
|
||||
<DocumentFlowFormContainerStep
|
||||
title={documentFlow.title}
|
||||
step={currentStep}
|
||||
maxStep={totalSteps}
|
||||
/>
|
||||
<DocumentFlowFormContainerStep step={currentStep} maxStep={totalSteps} />
|
||||
|
||||
<DocumentFlowFormContainerActions
|
||||
loading={form.formState.isSubmitting}
|
||||
|
||||
Reference in New Issue
Block a user