fix: merge conflicts

This commit is contained in:
Ephraim Atta-Duncan
2024-11-15 11:03:52 +00:00
331 changed files with 23123 additions and 3629 deletions

View File

@@ -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>

View File

@@ -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"

View File

@@ -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'),
}),
});

View File

@@ -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(),

View File

@@ -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 ||

View File

@@ -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>;

View File

@@ -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()}
/>

View File

@@ -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,
}),
});

View File

@@ -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}

View File

@@ -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 {