mirror of
https://github.com/documenso/documenso.git
synced 2025-11-15 17:21:41 +10:00
fix: merge conflicts
This commit is contained in:
@ -5,6 +5,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Caveat } from 'next/font/google';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import {
|
||||
CalendarDays,
|
||||
@ -28,13 +29,14 @@ import { prop, sortBy } from 'remeda';
|
||||
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_ENG } from '@documenso/lib/constants/recipient-roles';
|
||||
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||
import {
|
||||
type TFieldMetaSchema as FieldMeta,
|
||||
ZFieldMetaSchema,
|
||||
} from '@documenso/lib/types/field-meta';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { validateFieldsUninserted } from '@documenso/lib/utils/fields';
|
||||
import { parseMessageDescriptor } from '@documenso/lib/utils/i18n';
|
||||
import {
|
||||
canRecipientBeModified,
|
||||
canRecipientFieldsBeModified,
|
||||
@ -116,6 +118,7 @@ export const AddFieldsFormPartial = ({
|
||||
}: AddFieldsFormProps) => {
|
||||
const { toast } = useToast();
|
||||
const { data: session } = useSession();
|
||||
const { _ } = useLingui();
|
||||
|
||||
const [isMissingSignatureDialogVisible, setIsMissingSignatureDialogVisible] = useState(false);
|
||||
|
||||
@ -574,7 +577,10 @@ export const AddFieldsFormPartial = ({
|
||||
{showAdvancedSettings && currentField ? (
|
||||
<FieldAdvancedSettings
|
||||
title={msg`Advanced settings`}
|
||||
description={msg`Configure the ${FRIENDLY_FIELD_TYPE[currentField.type]} field`}
|
||||
description={msg`Configure the ${parseMessageDescriptor(
|
||||
_,
|
||||
FRIENDLY_FIELD_TYPE[currentField.type],
|
||||
)} field`}
|
||||
field={currentField}
|
||||
fields={localFields}
|
||||
onAdvancedSettings={handleAdvancedSettings}
|
||||
@ -609,7 +615,7 @@ export const AddFieldsFormPartial = ({
|
||||
width: fieldBounds.current.width,
|
||||
}}
|
||||
>
|
||||
{FRIENDLY_FIELD_TYPE[selectedField]}
|
||||
{parseMessageDescriptor(_, FRIENDLY_FIELD_TYPE[selectedField])}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -690,8 +696,7 @@ export const AddFieldsFormPartial = ({
|
||||
{recipientsByRoleToDisplay.map(([role, roleRecipients], roleIndex) => (
|
||||
<CommandGroup key={roleIndex}>
|
||||
<div className="text-muted-foreground mb-1 ml-2 mt-2 text-xs font-medium">
|
||||
{/* Todo: Translations - Add plural translations. */}
|
||||
{`${RECIPIENT_ROLES_DESCRIPTION_ENG[role].roleName}s`}
|
||||
{_(RECIPIENT_ROLES_DESCRIPTION[role].roleNamePlural)}
|
||||
</div>
|
||||
|
||||
{roleRecipients.length === 0 && (
|
||||
@ -1003,7 +1008,7 @@ export const AddFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<Disc className="h-4 w-4" />
|
||||
<Trans>Radio</Trans>
|
||||
Radio
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -1029,7 +1034,8 @@ export const AddFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<CheckSquare className="h-4 w-4" />
|
||||
<Trans>Checkbox</Trans>
|
||||
{/* Not translated on purpose. */}
|
||||
Checkbox
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@ -8,6 +8,7 @@ import { InfoIcon } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
||||
import type { TeamMemberRole } from '@documenso/prisma/client';
|
||||
@ -98,6 +99,7 @@ export const AddSettingsFormPartial = ({
|
||||
DATE_FORMATS.find((format) => format.value === document.documentMeta?.dateFormat)
|
||||
?.value ?? DEFAULT_DOCUMENT_DATE_FORMAT,
|
||||
redirectUrl: document.documentMeta?.redirectUrl ?? '',
|
||||
language: document.documentMeta?.language ?? 'en',
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -165,6 +167,46 @@ export const AddSettingsFormPartial = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="meta.language"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="inline-flex items-center">
|
||||
<Trans>Language</Trans>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-foreground max-w-md space-y-2 p-4">
|
||||
Controls the language for the document, including the language to be used
|
||||
for email notifications, and the final certificate that is generated and
|
||||
attached to the document.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Select {...field} onValueChange={field.onChange}>
|
||||
<SelectTrigger className="bg-background">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent>
|
||||
{Object.entries(SUPPORTED_LANGUAGES).map(([code, language]) => (
|
||||
<SelectItem key={code} value={code}>
|
||||
{language.full}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="globalAccessAuth"
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
||||
import {
|
||||
ZDocumentAccessAuthTypesSchema,
|
||||
ZDocumentActionAuthTypesSchema,
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
|
||||
import { DocumentVisibility } from '@documenso/prisma/client';
|
||||
|
||||
export const ZMapNegativeOneToUndefinedSchema = z
|
||||
.string()
|
||||
@ -22,7 +24,7 @@ export const ZMapNegativeOneToUndefinedSchema = z
|
||||
export const ZAddSettingsFormSchema = z.object({
|
||||
title: z.string().trim().min(1, { message: "Title can't be empty" }),
|
||||
externalId: z.string().optional(),
|
||||
visibility: z.string().optional(),
|
||||
visibility: z.nativeEnum(DocumentVisibility).optional(),
|
||||
globalAccessAuth: ZMapNegativeOneToUndefinedSchema.pipe(
|
||||
ZDocumentAccessAuthTypesSchema.optional(),
|
||||
),
|
||||
@ -39,6 +41,10 @@ export const ZAddSettingsFormSchema = z.object({
|
||||
message:
|
||||
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
|
||||
}),
|
||||
language: z
|
||||
.union([z.string(), z.enum(SUPPORTED_LANGUAGE_CODES)])
|
||||
.optional()
|
||||
.default('en'),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { msg } from '@lingui/macro';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZAddSignatureFormSchema = z.object({
|
||||
email: z
|
||||
.string()
|
||||
.min(1, { message: 'Email is required' })
|
||||
.email({ message: 'Invalid email address' }),
|
||||
.min(1, { message: msg`Email is required`.id })
|
||||
.email({ message: msg`Invalid email address`.id }),
|
||||
name: z.string(),
|
||||
customText: z.string(),
|
||||
number: z.number().optional(),
|
||||
|
||||
@ -453,7 +453,16 @@ export const AddSignersFormPartial = ({
|
||||
control={form.control}
|
||||
name={`signers.${index}.signingOrder`}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-2 mt-auto flex items-center gap-x-1 space-y-0">
|
||||
<FormItem
|
||||
className={cn(
|
||||
'col-span-2 mt-auto flex items-center gap-x-1 space-y-0',
|
||||
{
|
||||
'mb-6':
|
||||
form.formState.errors.signers?.[index] &&
|
||||
!form.formState.errors.signers[index]?.signingOrder,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<GripVerticalIcon className="h-5 w-5 flex-shrink-0 opacity-40" />
|
||||
<FormControl>
|
||||
<Input
|
||||
@ -491,6 +500,9 @@ export const AddSignersFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem
|
||||
className={cn('relative', {
|
||||
'mb-6':
|
||||
form.formState.errors.signers?.[index] &&
|
||||
!form.formState.errors.signers[index]?.email,
|
||||
'col-span-4': !showAdvancedSettings,
|
||||
'col-span-5': showAdvancedSettings,
|
||||
})}
|
||||
@ -504,7 +516,7 @@ export const AddSignersFormPartial = ({
|
||||
<FormControl>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
placeholder={_(msg`Email`)}
|
||||
{...field}
|
||||
disabled={
|
||||
snapshot.isDragging ||
|
||||
@ -526,6 +538,9 @@ export const AddSignersFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem
|
||||
className={cn({
|
||||
'mb-6':
|
||||
form.formState.errors.signers?.[index] &&
|
||||
!form.formState.errors.signers[index]?.name,
|
||||
'col-span-4': !showAdvancedSettings,
|
||||
'col-span-5': showAdvancedSettings,
|
||||
})}
|
||||
@ -561,6 +576,9 @@ export const AddSignersFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem
|
||||
className={cn('col-span-8', {
|
||||
'mb-6':
|
||||
form.formState.errors.signers?.[index] &&
|
||||
!form.formState.errors.signers[index]?.actionAuth,
|
||||
'col-span-10': isSigningOrderSequential,
|
||||
})}
|
||||
>
|
||||
@ -586,7 +604,13 @@ export const AddSignersFormPartial = ({
|
||||
<FormField
|
||||
name={`signers.${index}.role`}
|
||||
render={({ field }) => (
|
||||
<FormItem className="mt-auto">
|
||||
<FormItem
|
||||
className={cn('mt-auto', {
|
||||
'mb-6':
|
||||
form.formState.errors.signers?.[index] &&
|
||||
!form.formState.errors.signers[index]?.role,
|
||||
})}
|
||||
>
|
||||
<FormControl>
|
||||
<RecipientRoleSelect
|
||||
{...field}
|
||||
@ -606,7 +630,12 @@ export const AddSignersFormPartial = ({
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="mt-auto inline-flex h-10 w-10 items-center justify-center hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
className={cn(
|
||||
'mt-auto inline-flex h-10 w-10 items-center justify-center hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
{
|
||||
'mb-6': form.formState.errors.signers?.[index],
|
||||
},
|
||||
)}
|
||||
disabled={
|
||||
snapshot.isDragging ||
|
||||
isSubmitting ||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { msg } from '@lingui/macro';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZRecipientActionAuthTypesSchema } from '@documenso/lib/types/document-auth';
|
||||
@ -11,7 +12,10 @@ export const ZAddSignersFormSchema = z
|
||||
z.object({
|
||||
formId: z.string().min(1),
|
||||
nativeId: z.number().optional(),
|
||||
email: z.string().email().min(1),
|
||||
email: z
|
||||
.string()
|
||||
.email({ message: msg`Invalid email`.id })
|
||||
.min(1),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
@ -29,7 +33,7 @@ export const ZAddSignersFormSchema = z
|
||||
return new Set(emails).size === emails.length;
|
||||
},
|
||||
// Dirty hack to handle errors when .root is populated for an array type
|
||||
{ message: 'Signers must have unique emails', path: ['signers__root'] },
|
||||
{ message: msg`Signers must have unique emails`.id, path: ['signers__root'] },
|
||||
);
|
||||
|
||||
export type TAddSignersFormSchema = z.infer<typeof ZAddSignersFormSchema>;
|
||||
|
||||
@ -2,18 +2,32 @@
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { formatSigningLink } from '@documenso/lib/utils/recipients';
|
||||
import type { Field, Recipient } from '@documenso/prisma/client';
|
||||
import { DocumentStatus } from '@documenso/prisma/client';
|
||||
import {
|
||||
DocumentDistributionMethod,
|
||||
DocumentStatus,
|
||||
RecipientRole,
|
||||
} from '@documenso/prisma/client';
|
||||
import type { DocumentWithData } from '@documenso/prisma/types/document-with-data';
|
||||
import { DocumentSendEmailMessageHelper } from '@documenso/ui/components/document/document-send-email-message-helper';
|
||||
import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
|
||||
|
||||
import { CopyTextButton } from '../../components/common/copy-text-button';
|
||||
import { DocumentEmailCheckboxes } from '../../components/document/document-email-checkboxes';
|
||||
import { AvatarWithText } from '../avatar';
|
||||
import { FormErrorMessage } from '../form/form-error-message';
|
||||
import { Input } from '../input';
|
||||
import { Label } from '../label';
|
||||
import { useStep } from '../stepper';
|
||||
import { Textarea } from '../textarea';
|
||||
import { toast } from '../use-toast';
|
||||
import { type TAddSubjectFormSchema, ZAddSubjectFormSchema } from './add-subject.types';
|
||||
import {
|
||||
DocumentFlowFormContainerActions,
|
||||
@ -42,20 +56,45 @@ export const AddSubjectFormPartial = ({
|
||||
onSubmit,
|
||||
isDocumentPdfLoaded,
|
||||
}: AddSubjectFormProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
watch,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<TAddSubjectFormSchema>({
|
||||
defaultValues: {
|
||||
meta: {
|
||||
subject: document.documentMeta?.subject ?? '',
|
||||
message: document.documentMeta?.message ?? '',
|
||||
distributionMethod:
|
||||
document.documentMeta?.distributionMethod || DocumentDistributionMethod.EMAIL,
|
||||
emailSettings: ZDocumentEmailSettingsSchema.parse(document?.documentMeta?.emailSettings),
|
||||
},
|
||||
},
|
||||
resolver: zodResolver(ZAddSubjectFormSchema),
|
||||
});
|
||||
|
||||
const GoNextLabel = {
|
||||
[DocumentDistributionMethod.EMAIL]: {
|
||||
[DocumentStatus.DRAFT]: msg`Send`,
|
||||
[DocumentStatus.PENDING]: recipients.some((recipient) => recipient.sendStatus === 'SENT')
|
||||
? msg`Resend`
|
||||
: msg`Send`,
|
||||
[DocumentStatus.COMPLETED]: msg`Update`,
|
||||
},
|
||||
[DocumentDistributionMethod.NONE]: {
|
||||
[DocumentStatus.DRAFT]: msg`Generate Links`,
|
||||
[DocumentStatus.PENDING]: msg`View Document`,
|
||||
[DocumentStatus.COMPLETED]: msg`View Document`,
|
||||
},
|
||||
};
|
||||
|
||||
const distributionMethod = watch('meta.distributionMethod');
|
||||
const emailSettings = watch('meta.emailSettings');
|
||||
|
||||
const onFormSubmit = handleSubmit(onSubmit);
|
||||
const { currentStep, totalSteps, previousStep } = useStep();
|
||||
|
||||
@ -72,46 +111,158 @@ export const AddSubjectFormPartial = ({
|
||||
<ShowFieldItem key={index} field={field} recipients={recipients} />
|
||||
))}
|
||||
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<div>
|
||||
<Label htmlFor="subject">
|
||||
<Trans>
|
||||
Subject <span className="text-muted-foreground">(Optional)</span>
|
||||
</Trans>
|
||||
</Label>
|
||||
<Tabs
|
||||
onValueChange={(value) =>
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
setValue('meta.distributionMethod', value as DocumentDistributionMethod)
|
||||
}
|
||||
value={distributionMethod}
|
||||
className="mb-2"
|
||||
>
|
||||
<TabsList className="w-full">
|
||||
<TabsTrigger className="w-full" value={DocumentDistributionMethod.EMAIL}>
|
||||
Email
|
||||
</TabsTrigger>
|
||||
<TabsTrigger className="w-full" value={DocumentDistributionMethod.NONE}>
|
||||
None
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
||||
<Input
|
||||
id="subject"
|
||||
className="bg-background mt-2"
|
||||
disabled={isSubmitting}
|
||||
{...register('meta.subject')}
|
||||
/>
|
||||
<AnimatePresence mode="wait">
|
||||
{distributionMethod === DocumentDistributionMethod.EMAIL && (
|
||||
<motion.div
|
||||
key={'Emails'}
|
||||
initial={{ opacity: 0, y: 15 }}
|
||||
animate={{ opacity: 1, y: 0, transition: { duration: 0.3 } }}
|
||||
exit={{ opacity: 0, transition: { duration: 0.15 } }}
|
||||
className="flex flex-col gap-y-4 rounded-lg border p-4"
|
||||
>
|
||||
<div>
|
||||
<Label htmlFor="subject">
|
||||
<Trans>
|
||||
Subject <span className="text-muted-foreground">(Optional)</span>
|
||||
</Trans>
|
||||
</Label>
|
||||
|
||||
<FormErrorMessage className="mt-2" error={errors.meta?.subject} />
|
||||
</div>
|
||||
<Input
|
||||
id="subject"
|
||||
className="bg-background mt-2"
|
||||
disabled={isSubmitting}
|
||||
{...register('meta.subject')}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="message">
|
||||
<Trans>
|
||||
Message <span className="text-muted-foreground">(Optional)</span>
|
||||
</Trans>
|
||||
</Label>
|
||||
<FormErrorMessage className="mt-2" error={errors.meta?.subject} />
|
||||
</div>
|
||||
|
||||
<Textarea
|
||||
id="message"
|
||||
className="bg-background mt-2 h-32 resize-none"
|
||||
disabled={isSubmitting}
|
||||
{...register('meta.message')}
|
||||
/>
|
||||
<div>
|
||||
<Label htmlFor="message">
|
||||
<Trans>
|
||||
Message <span className="text-muted-foreground">(Optional)</span>
|
||||
</Trans>
|
||||
</Label>
|
||||
|
||||
<FormErrorMessage
|
||||
className="mt-2"
|
||||
error={typeof errors.meta?.message !== 'string' ? errors.meta?.message : undefined}
|
||||
/>
|
||||
</div>
|
||||
<Textarea
|
||||
id="message"
|
||||
className="bg-background mt-2 h-32 resize-none"
|
||||
disabled={isSubmitting}
|
||||
{...register('meta.message')}
|
||||
/>
|
||||
|
||||
<DocumentSendEmailMessageHelper />
|
||||
</div>
|
||||
<FormErrorMessage
|
||||
className="mt-2"
|
||||
error={
|
||||
typeof errors.meta?.message !== 'string' ? errors.meta?.message : undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DocumentSendEmailMessageHelper />
|
||||
|
||||
<DocumentEmailCheckboxes
|
||||
className="mt-2"
|
||||
value={emailSettings}
|
||||
onChange={(value) => setValue('meta.emailSettings', value)}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{distributionMethod === DocumentDistributionMethod.NONE && (
|
||||
<motion.div
|
||||
key={'Links'}
|
||||
initial={{ opacity: 0, y: 15 }}
|
||||
animate={{ opacity: 1, y: 0, transition: { duration: 0.3 } }}
|
||||
exit={{ opacity: 0, transition: { duration: 0.15 } }}
|
||||
className="rounded-lg border"
|
||||
>
|
||||
{document.status === DocumentStatus.DRAFT ? (
|
||||
<div className="text-muted-foreground py-16 text-center text-sm">
|
||||
<p>
|
||||
<Trans>We won't send anything to notify recipients.</Trans>
|
||||
</p>
|
||||
|
||||
<p className="mt-2">
|
||||
<Trans>
|
||||
We will generate signing links for with you, which you can send to the
|
||||
recipients through your method of choice.
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<ul className="text-muted-foreground divide-y">
|
||||
{recipients.length === 0 && (
|
||||
<li className="flex flex-col items-center justify-center py-6 text-sm">
|
||||
<Trans>No recipients</Trans>
|
||||
</li>
|
||||
)}
|
||||
|
||||
{recipients.map((recipient) => (
|
||||
<li
|
||||
key={recipient.id}
|
||||
className="flex items-center justify-between px-4 py-3 text-sm"
|
||||
>
|
||||
<AvatarWithText
|
||||
avatarFallback={recipient.email.slice(0, 1).toUpperCase()}
|
||||
primaryText={
|
||||
<p className="text-muted-foreground text-sm">{recipient.email}</p>
|
||||
}
|
||||
secondaryText={
|
||||
<p className="text-muted-foreground/70 text-xs">
|
||||
{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
|
||||
{recipient.role !== RecipientRole.CC && (
|
||||
<CopyTextButton
|
||||
value={formatSigningLink(recipient.token)}
|
||||
onCopySuccess={() => {
|
||||
toast({
|
||||
title: _(msg`Copied to clipboard`),
|
||||
description: _(
|
||||
msg`The signing link has been copied to your clipboard.`,
|
||||
),
|
||||
});
|
||||
}}
|
||||
badgeContentUncopied={
|
||||
<p className="ml-1 text-xs">
|
||||
<Trans>Copy</Trans>
|
||||
</p>
|
||||
}
|
||||
badgeContentCopied={
|
||||
<p className="ml-1 text-xs">
|
||||
<Trans>Copied</Trans>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</DocumentFlowFormContainerContent>
|
||||
|
||||
@ -121,7 +272,7 @@ export const AddSubjectFormPartial = ({
|
||||
<DocumentFlowFormContainerActions
|
||||
loading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
goNextLabel={document.status === DocumentStatus.DRAFT ? msg`Send` : msg`Update`}
|
||||
goNextLabel={GoNextLabel[distributionMethod][document.status]}
|
||||
onGoBackClick={previousStep}
|
||||
onGoNextClick={() => void onFormSubmit()}
|
||||
/>
|
||||
|
||||
@ -1,9 +1,18 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
|
||||
import { DocumentDistributionMethod } from '.prisma/client';
|
||||
|
||||
export const ZAddSubjectFormSchema = z.object({
|
||||
meta: z.object({
|
||||
subject: z.string(),
|
||||
message: z.string(),
|
||||
distributionMethod: z
|
||||
.nativeEnum(DocumentDistributionMethod)
|
||||
.optional()
|
||||
.default(DocumentDistributionMethod.EMAIL),
|
||||
emailSettings: ZDocumentEmailSettingsSchema,
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@ -2,10 +2,12 @@
|
||||
|
||||
import { Caveat } from 'next/font/google';
|
||||
|
||||
import { useLingui } from '@lingui/react';
|
||||
import type { Prisma } from '@prisma/client';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
import { useFieldPageCoords } from '@documenso/lib/client-only/hooks/use-field-page-coords';
|
||||
import { parseMessageDescriptor } from '@documenso/lib/utils/i18n';
|
||||
import { FieldType } from '@documenso/prisma/client';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
@ -25,6 +27,8 @@ export type ShowFieldItemProps = {
|
||||
};
|
||||
|
||||
export const ShowFieldItem = ({ field, recipients }: ShowFieldItemProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const coords = useFieldPageCoords(field);
|
||||
|
||||
const signerEmail =
|
||||
@ -47,7 +51,7 @@ export const ShowFieldItem = ({ field, recipients }: ShowFieldItemProps) => {
|
||||
field.type === FieldType.SIGNATURE && fontCaveat.className,
|
||||
)}
|
||||
>
|
||||
{FRIENDLY_FIELD_TYPE[field.type]}
|
||||
{parseMessageDescriptor(_, FRIENDLY_FIELD_TYPE[field.type])}
|
||||
|
||||
<p className="text-muted-foreground/50 w-full truncate text-center text-xs">
|
||||
{signerEmail}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/macro';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
@ -44,18 +45,18 @@ export const ZDocumentFlowFormSchema = z.object({
|
||||
|
||||
export type TDocumentFlowFormSchema = z.infer<typeof ZDocumentFlowFormSchema>;
|
||||
|
||||
export const FRIENDLY_FIELD_TYPE: Record<FieldType, string> = {
|
||||
[FieldType.SIGNATURE]: 'Signature',
|
||||
[FieldType.FREE_SIGNATURE]: 'Free Signature',
|
||||
[FieldType.INITIALS]: 'Initials',
|
||||
[FieldType.TEXT]: 'Text',
|
||||
[FieldType.DATE]: 'Date',
|
||||
[FieldType.EMAIL]: 'Email',
|
||||
[FieldType.NAME]: 'Name',
|
||||
[FieldType.NUMBER]: 'Number',
|
||||
[FieldType.RADIO]: 'Radio',
|
||||
[FieldType.CHECKBOX]: 'Checkbox',
|
||||
[FieldType.DROPDOWN]: 'Select',
|
||||
export const FRIENDLY_FIELD_TYPE: Record<FieldType, MessageDescriptor | string> = {
|
||||
[FieldType.SIGNATURE]: msg`Signature`,
|
||||
[FieldType.FREE_SIGNATURE]: msg`Free Signature`,
|
||||
[FieldType.INITIALS]: msg`Initials`,
|
||||
[FieldType.TEXT]: msg`Text`,
|
||||
[FieldType.DATE]: msg`Date`,
|
||||
[FieldType.EMAIL]: msg`Email`,
|
||||
[FieldType.NAME]: msg`Name`,
|
||||
[FieldType.NUMBER]: msg`Number`,
|
||||
[FieldType.RADIO]: `Radio`,
|
||||
[FieldType.CHECKBOX]: `Checkbox`,
|
||||
[FieldType.DROPDOWN]: `Select`,
|
||||
};
|
||||
|
||||
export interface DocumentFlowStep {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
@ -12,6 +13,15 @@ const isErrorWithMessage = (error: unknown): error is { message?: string } => {
|
||||
};
|
||||
|
||||
export const FormErrorMessage = ({ error, className }: FormErrorMessageProps) => {
|
||||
const { i18n } = useLingui();
|
||||
|
||||
let errorMessage = isErrorWithMessage(error) ? error.message : '';
|
||||
|
||||
// Checks to see if there's a translation for the string, since we're passing IDs for Zod errors.
|
||||
if (typeof errorMessage === 'string' && i18n.t(errorMessage)) {
|
||||
errorMessage = i18n.t(errorMessage);
|
||||
}
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isErrorWithMessage(error) && (
|
||||
@ -30,7 +40,7 @@ export const FormErrorMessage = ({ error, className }: FormErrorMessageProps) =>
|
||||
}}
|
||||
className={cn('text-xs text-red-500', className)}
|
||||
>
|
||||
{error.message}
|
||||
{errorMessage}
|
||||
</motion.p>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { useLingui } from '@lingui/react';
|
||||
import type * as LabelPrimitive from '@radix-ui/react-label';
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
@ -136,13 +137,21 @@ const FormMessage = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
const { i18n } = useLingui();
|
||||
|
||||
const { error, formMessageId } = useFormField();
|
||||
const body = error ? String(error?.message) : children;
|
||||
|
||||
let body = error ? String(error?.message) : children;
|
||||
|
||||
if (!body) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Checks to see if there's a translation for the string, since we're passing IDs for Zod errors.
|
||||
if (typeof body === 'string' && i18n.t(body)) {
|
||||
body = i18n.t(body);
|
||||
}
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
<motion.div
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { Loader } from 'lucide-react';
|
||||
|
||||
export const LazyPDFViewer = dynamic(async () => import('./pdf-viewer'), {
|
||||
@ -10,7 +11,9 @@ export const LazyPDFViewer = dynamic(async () => import('./pdf-viewer'), {
|
||||
<div className="dark:bg-background flex h-[80vh] max-h-[60rem] flex-col items-center justify-center bg-white/50">
|
||||
<Loader className="text-documenso h-12 w-12 animate-spin" />
|
||||
|
||||
<p className="text-muted-foreground mt-4">Loading document...</p>
|
||||
<p className="text-muted-foreground mt-4">
|
||||
<Trans>Loading document...</Trans>
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Loader } from 'lucide-react';
|
||||
import { type PDFDocumentProxy, PasswordResponses } from 'pdfjs-dist';
|
||||
import { Document as PDFDocument, Page as PDFPage, pdfjs } from 'react-pdf';
|
||||
@ -38,7 +40,9 @@ const PDFLoader = () => (
|
||||
<>
|
||||
<Loader className="text-documenso h-12 w-12 animate-spin" />
|
||||
|
||||
<p className="text-muted-foreground mt-4">Loading document...</p>
|
||||
<p className="text-muted-foreground mt-4">
|
||||
<Trans>Loading document...</Trans>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -61,6 +65,7 @@ export const PDFViewer = ({
|
||||
onPageClick,
|
||||
...props
|
||||
}: PDFViewerProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const $el = useRef<HTMLDivElement>(null);
|
||||
@ -158,8 +163,8 @@ export const PDFViewer = ({
|
||||
console.error(err);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while loading the document.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while loading the document.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -211,8 +216,12 @@ export const PDFViewer = ({
|
||||
<div className="dark:bg-background flex h-[80vh] max-h-[60rem] flex-col items-center justify-center bg-white/50">
|
||||
{pdfError ? (
|
||||
<div className="text-muted-foreground text-center">
|
||||
<p>Something went wrong while loading the document.</p>
|
||||
<p className="mt-1 text-sm">Please try again or contact our support.</p>
|
||||
<p>
|
||||
<Trans>Something went wrong while loading the document.</Trans>
|
||||
</p>
|
||||
<p className="mt-1 text-sm">
|
||||
<Trans>Please try again or contact our support.</Trans>
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<PDFLoader />
|
||||
@ -222,8 +231,12 @@ export const PDFViewer = ({
|
||||
error={
|
||||
<div className="dark:bg-background flex h-[80vh] max-h-[60rem] flex-col items-center justify-center bg-white/50">
|
||||
<div className="text-muted-foreground text-center">
|
||||
<p>Something went wrong while loading the document.</p>
|
||||
<p className="mt-1 text-sm">Please try again or contact our support.</p>
|
||||
<p>
|
||||
<Trans>Something went wrong while loading the document.</Trans>
|
||||
</p>
|
||||
<p className="mt-1 text-sm">
|
||||
<Trans>Please try again or contact our support.</Trans>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@ -243,7 +256,9 @@ export const PDFViewer = ({
|
||||
/>
|
||||
</div>
|
||||
<p className="text-muted-foreground/80 my-2 text-center text-[11px]">
|
||||
Page {i + 1} of {numPages}
|
||||
<Trans>
|
||||
Page {i + 1} of {numPages}
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@ -5,6 +5,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Caveat } from 'next/font/google';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import {
|
||||
CalendarDays,
|
||||
CheckSquare,
|
||||
@ -22,12 +23,13 @@ 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_ENG } from '@documenso/lib/constants/recipient-roles';
|
||||
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||
import {
|
||||
type TFieldMetaSchema as FieldMeta,
|
||||
ZFieldMetaSchema,
|
||||
} from '@documenso/lib/types/field-meta';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { parseMessageDescriptor } from '@documenso/lib/utils/i18n';
|
||||
import type { Field, Recipient } from '@documenso/prisma/client';
|
||||
import { FieldType, RecipientRole } from '@documenso/prisma/client';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
@ -85,6 +87,8 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
onSubmit,
|
||||
teamId,
|
||||
}: AddTemplateFieldsFormProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const { isWithinPageBounds, getFieldPosition, getPage } = useDocumentElement();
|
||||
const { currentStep, totalSteps, previousStep } = useStep();
|
||||
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
|
||||
@ -400,7 +404,10 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
{showAdvancedSettings && currentField ? (
|
||||
<FieldAdvancedSettings
|
||||
title={msg`Advanced settings`}
|
||||
description={msg`Configure the ${FRIENDLY_FIELD_TYPE[currentField.type]} field`}
|
||||
description={msg`Configure the ${parseMessageDescriptor(
|
||||
_,
|
||||
FRIENDLY_FIELD_TYPE[currentField.type],
|
||||
)} field`}
|
||||
field={currentField}
|
||||
fields={localFields}
|
||||
onAdvancedSettings={handleAdvancedSettings}
|
||||
@ -432,7 +439,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
width: fieldBounds.current.width,
|
||||
}}
|
||||
>
|
||||
{FRIENDLY_FIELD_TYPE[selectedField]}
|
||||
{parseMessageDescriptor(_, FRIENDLY_FIELD_TYPE[selectedField])}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -501,8 +508,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
{recipientsByRoleToDisplay.map(([role, roleRecipients], roleIndex) => (
|
||||
<CommandGroup key={roleIndex}>
|
||||
<div className="text-muted-foreground mb-1 ml-2 mt-2 text-xs font-medium">
|
||||
{/* Todo: Translations - Add plural translations. */}
|
||||
{`${RECIPIENT_ROLES_DESCRIPTION_ENG[role].roleName}s`}
|
||||
{_(RECIPIENT_ROLES_DESCRIPTION[role].roleNamePlural)}
|
||||
</div>
|
||||
|
||||
{roleRecipients.length === 0 && (
|
||||
@ -785,7 +791,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<CheckSquare className="h-4 w-4" />
|
||||
<Trans>Checkbox</Trans>
|
||||
Checkbox
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@ -9,9 +9,12 @@ import { InfoIcon } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { DOCUMENT_DISTRIBUTION_METHODS } from '@documenso/lib/constants/document';
|
||||
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
||||
import { type Field, type Recipient } from '@documenso/prisma/client';
|
||||
import { DocumentDistributionMethod, type Field, type Recipient } from '@documenso/prisma/client';
|
||||
import type { TemplateWithData } from '@documenso/prisma/types/template';
|
||||
import {
|
||||
DocumentGlobalAuthAccessSelect,
|
||||
@ -37,6 +40,7 @@ import {
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
|
||||
import { DocumentEmailCheckboxes } from '../../components/document/document-email-checkboxes';
|
||||
import { Combobox } from '../combobox';
|
||||
import {
|
||||
DocumentFlowFormContainerActions,
|
||||
@ -92,17 +96,24 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
message: template.templateMeta?.message ?? '',
|
||||
timezone: template.templateMeta?.timezone ?? DEFAULT_DOCUMENT_TIME_ZONE,
|
||||
dateFormat: template.templateMeta?.dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT,
|
||||
distributionMethod:
|
||||
template.templateMeta?.distributionMethod || DocumentDistributionMethod.EMAIL,
|
||||
redirectUrl: template.templateMeta?.redirectUrl ?? '',
|
||||
language: template.templateMeta?.language ?? 'en',
|
||||
emailSettings: ZDocumentEmailSettingsSchema.parse(template?.templateMeta?.emailSettings),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { stepIndex, currentStep, totalSteps, previousStep } = useStep();
|
||||
|
||||
const distributionMethod = form.watch('meta.distributionMethod');
|
||||
const emailSettings = form.watch('meta.emailSettings');
|
||||
|
||||
// We almost always want to set the timezone to the user's local timezone to avoid confusion
|
||||
// when the document is signed.
|
||||
useEffect(() => {
|
||||
if (!form.formState.touchedFields.meta?.timezone) {
|
||||
if (!form.formState.touchedFields.meta?.timezone && !template.templateMeta?.timezone) {
|
||||
form.setValue('meta.timezone', Intl.DateTimeFormat().resolvedOptions().timeZone);
|
||||
}
|
||||
}, [form, form.setValue, form.formState.touchedFields.meta?.timezone]);
|
||||
@ -142,6 +153,46 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="meta.language"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="inline-flex items-center">
|
||||
<Trans>Language</Trans>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-foreground max-w-md space-y-2 p-4">
|
||||
Controls the language for the document, including the language to be used
|
||||
for email notifications, and the final certificate that is generated and
|
||||
attached to the document.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Select {...field} onValueChange={field.onChange}>
|
||||
<SelectTrigger className="bg-background">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent>
|
||||
{Object.entries(SUPPORTED_LANGUAGES).map(([code, language]) => (
|
||||
<SelectItem key={code} value={code}>
|
||||
{language.full}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="globalAccessAuth"
|
||||
@ -159,6 +210,77 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="meta.distributionMethod"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
<Trans>Document Distribution Method</Trans>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-foreground max-w-md space-y-2 p-4">
|
||||
<h2>
|
||||
<strong>
|
||||
<Trans>Document Distribution Method</Trans>
|
||||
</strong>
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
<Trans>
|
||||
This is how the document will reach the recipients once the document is
|
||||
ready for signing.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<ul className="ml-3.5 list-outside list-disc space-y-0.5 py-2">
|
||||
<li>
|
||||
<Trans>
|
||||
<strong>Email</strong> - The recipient will be emailed the document to
|
||||
sign, approve, etc.
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>
|
||||
<strong>Links</strong> - We will generate links which you can send to
|
||||
the recipients manually.
|
||||
</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<Trans>
|
||||
<strong>Note</strong> - If you use Links in combination with direct
|
||||
templates, you will need to manually send the links to the remaining
|
||||
recipients.
|
||||
</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Select {...field} onValueChange={field.onChange}>
|
||||
<SelectTrigger className="bg-background text-muted-foreground">
|
||||
<SelectValue data-testid="documentDistributionMethodSelectValue" />
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent position="popper">
|
||||
{Object.values(DOCUMENT_DISTRIBUTION_METHODS).map(
|
||||
({ value, description }) => (
|
||||
<SelectItem key={value} value={value}>
|
||||
{_(description)}
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{isEnterprise && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
@ -178,59 +300,66 @@ 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">
|
||||
<Trans>Email Options</Trans>
|
||||
</AccordionTrigger>
|
||||
{distributionMethod === DocumentDistributionMethod.EMAIL && (
|
||||
<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">
|
||||
<Trans>Email Options</Trans>
|
||||
</AccordionTrigger>
|
||||
|
||||
<AccordionContent className="text-muted-foreground -mx-1 px-1 pt-4 text-sm leading-relaxed [&>div]:pb-0">
|
||||
<div className="flex flex-col space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="meta.subject"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans>
|
||||
Subject <span className="text-muted-foreground">(Optional)</span>
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
<AccordionContent className="text-muted-foreground -mx-1 px-1 pt-4 text-sm leading-relaxed [&>div]:pb-0">
|
||||
<div className="flex flex-col space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="meta.subject"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans>
|
||||
Subject <span className="text-muted-foreground">(Optional)</span>
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="meta.message"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans>
|
||||
Message <span className="text-muted-foreground">(Optional)</span>
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="meta.message"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans>
|
||||
Message <span className="text-muted-foreground">(Optional)</span>
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Textarea className="bg-background h-32 resize-none" {...field} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<Textarea className="bg-background h-32 resize-none" {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<DocumentSendEmailMessageHelper />
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<DocumentSendEmailMessageHelper />
|
||||
|
||||
<DocumentEmailCheckboxes
|
||||
value={emailSettings}
|
||||
onChange={(value) => form.setValue('meta.emailSettings', value)}
|
||||
/>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)}
|
||||
|
||||
<Accordion type="multiple">
|
||||
<AccordionItem value="advanced-options" className="border-none">
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
||||
import {
|
||||
ZDocumentAccessAuthTypesSchema,
|
||||
ZDocumentActionAuthTypesSchema,
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
|
||||
|
||||
import { ZMapNegativeOneToUndefinedSchema } from '../document-flow/add-settings.types';
|
||||
import { DocumentDistributionMethod } from '.prisma/client';
|
||||
|
||||
export const ZAddTemplateSettingsFormSchema = z.object({
|
||||
title: z.string().trim().min(1, { message: "Title can't be empty" }),
|
||||
@ -24,6 +27,10 @@ export const ZAddTemplateSettingsFormSchema = z.object({
|
||||
message: z.string(),
|
||||
timezone: z.string().optional().default(DEFAULT_DOCUMENT_TIME_ZONE),
|
||||
dateFormat: z.string().optional().default(DEFAULT_DOCUMENT_DATE_FORMAT),
|
||||
distributionMethod: z
|
||||
.nativeEnum(DocumentDistributionMethod)
|
||||
.optional()
|
||||
.default(DocumentDistributionMethod.EMAIL),
|
||||
redirectUrl: z
|
||||
.string()
|
||||
.optional()
|
||||
@ -31,6 +38,11 @@ export const ZAddTemplateSettingsFormSchema = z.object({
|
||||
message:
|
||||
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
|
||||
}),
|
||||
language: z
|
||||
.union([z.string(), z.enum(SUPPORTED_LANGUAGE_CODES)])
|
||||
.optional()
|
||||
.default('en'),
|
||||
emailSettings: ZDocumentEmailSettingsSchema,
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user