mirror of
https://github.com/documenso/documenso.git
synced 2026-07-26 01:45:08 +10:00
Merge branch 'main' into feat/prefetch-intent-navigation-links
This commit is contained in:
@@ -20,6 +20,8 @@ const badgeVariants = cva(
|
||||
'bg-green-50 text-green-700 ring-green-600/20 dark:bg-green-500/10 dark:text-green-400 dark:ring-green-500/20',
|
||||
secondary:
|
||||
'bg-blue-50 text-blue-700 ring-blue-700/10 dark:bg-blue-400/10 dark:text-blue-400 dark:ring-blue-400/30',
|
||||
orange:
|
||||
'bg-orange-50 text-orange-700 ring-orange-700/10 dark:bg-orange-400/10 dark:text-orange-400 dark:ring-orange-400/30',
|
||||
},
|
||||
size: {
|
||||
small: 'px-1.5 py-0.5 text-xs',
|
||||
|
||||
@@ -4,7 +4,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { Field, Recipient } from '@prisma/client';
|
||||
import type { Field } from '@prisma/client';
|
||||
import { FieldType, Prisma, RecipientRole, SendStatus } from '@prisma/client';
|
||||
import {
|
||||
CalendarDays,
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
type TFieldMetaSchema as FieldMeta,
|
||||
ZFieldMetaSchema,
|
||||
} from '@documenso/lib/types/field-meta';
|
||||
import type { TRecipientLite } from '@documenso/lib/types/recipient';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { ADVANCED_FIELD_TYPES_WITH_OPTIONAL_SETTING } from '@documenso/lib/utils/advanced-fields-helpers';
|
||||
import { validateFieldsUninserted } from '@documenso/lib/utils/fields';
|
||||
@@ -83,7 +84,7 @@ export type FieldFormType = {
|
||||
export type AddFieldsFormProps = {
|
||||
documentFlow: DocumentFlowStep;
|
||||
hideRecipients?: boolean;
|
||||
recipients: Recipient[];
|
||||
recipients: TRecipientLite[];
|
||||
fields: Field[];
|
||||
onSubmit: (_data: TAddFieldsFormSchema) => void;
|
||||
onAutoSave: (_data: TAddFieldsFormSchema) => Promise<void>;
|
||||
@@ -172,7 +173,7 @@ export const AddFieldsFormPartial = ({
|
||||
});
|
||||
|
||||
const [selectedField, setSelectedField] = useState<FieldType | null>(null);
|
||||
const [selectedSigner, setSelectedSigner] = useState<Recipient | null>(null);
|
||||
const [selectedSigner, setSelectedSigner] = useState<TRecipientLite | null>(null);
|
||||
const [lastActiveField, setLastActiveField] = useState<TAddFieldsFormSchema['fields'][0] | null>(
|
||||
null,
|
||||
);
|
||||
@@ -536,7 +537,7 @@ export const AddFieldsFormPartial = ({
|
||||
}, [recipients]);
|
||||
|
||||
const recipientsByRole = useMemo(() => {
|
||||
const recipientsByRole: Record<RecipientRole, Recipient[]> = {
|
||||
const recipientsByRole: Record<RecipientRole, TRecipientLite[]> = {
|
||||
CC: [],
|
||||
VIEWER: [],
|
||||
SIGNER: [],
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
DocumentStatus,
|
||||
DocumentVisibility,
|
||||
type Field,
|
||||
type Recipient,
|
||||
SendStatus,
|
||||
TeamMemberRole,
|
||||
} from '@prisma/client';
|
||||
@@ -21,6 +20,7 @@ import { DOCUMENT_SIGNATURE_TYPES } 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 type { TDocument } from '@documenso/lib/types/document';
|
||||
import type { TRecipientLite } from '@documenso/lib/types/recipient';
|
||||
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
||||
import { extractTeamSignatureSettings } from '@documenso/lib/utils/teams';
|
||||
import {
|
||||
@@ -74,7 +74,7 @@ import type { DocumentFlowStep } from './types';
|
||||
|
||||
export type AddSettingsFormProps = {
|
||||
documentFlow: DocumentFlowStep;
|
||||
recipients: Recipient[];
|
||||
recipients: TRecipientLite[];
|
||||
fields: Field[];
|
||||
isDocumentPdfLoaded: boolean;
|
||||
document: TDocument;
|
||||
@@ -242,7 +242,7 @@ export const AddSettingsFormPartial = ({
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-foreground max-w-md space-y-2 p-4">
|
||||
<TooltipContent className="max-w-md space-y-2 p-4 text-foreground">
|
||||
<Trans>
|
||||
Controls the language for the document, including the language to be used
|
||||
for email notifications, and the final certificate that is generated and
|
||||
@@ -361,11 +361,11 @@ export const AddSettingsFormPartial = ({
|
||||
|
||||
<Accordion type="multiple" className="mt-6">
|
||||
<AccordionItem value="advanced-options" className="border-none">
|
||||
<AccordionTrigger className="text-foreground mb-2 rounded border px-3 py-2 text-left hover:bg-neutral-200/30 hover:no-underline">
|
||||
<AccordionTrigger className="mb-2 rounded border px-3 py-2 text-left text-foreground hover:bg-neutral-200/30 hover:no-underline">
|
||||
<Trans>Advanced Options</Trans>
|
||||
</AccordionTrigger>
|
||||
|
||||
<AccordionContent className="text-muted-foreground -mx-1 px-1 pt-2 text-sm leading-relaxed">
|
||||
<AccordionContent className="-mx-1 px-1 pt-2 text-sm leading-relaxed text-muted-foreground">
|
||||
<div className="flex flex-col space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -379,7 +379,7 @@ export const AddSettingsFormPartial = ({
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-muted-foreground max-w-xs">
|
||||
<TooltipContent className="max-w-xs text-muted-foreground">
|
||||
<Trans>
|
||||
Add an external ID to the document. This can be used to identify
|
||||
the document in external systems.
|
||||
@@ -418,7 +418,7 @@ export const AddSettingsFormPartial = ({
|
||||
field.onChange(value);
|
||||
void handleAutoSave();
|
||||
}}
|
||||
className="bg-background w-full"
|
||||
className="w-full bg-background"
|
||||
emptySelectionPlaceholder={t`Select signature types`}
|
||||
/>
|
||||
</FormControl>
|
||||
@@ -506,7 +506,7 @@ export const AddSettingsFormPartial = ({
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-muted-foreground max-w-xs">
|
||||
<TooltipContent className="max-w-xs text-muted-foreground">
|
||||
<Trans>
|
||||
Add a URL to redirect the user to once the document is signed
|
||||
</Trans>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { Field, Recipient } from '@prisma/client';
|
||||
import type { Field } from '@prisma/client';
|
||||
import { DocumentSigningOrder, RecipientRole, SendStatus } from '@prisma/client';
|
||||
import { motion } from 'framer-motion';
|
||||
import { GripVerticalIcon, HelpCircle, Plus, Trash } from 'lucide-react';
|
||||
@@ -19,6 +19,7 @@ import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounce
|
||||
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 type { TRecipientLite } from '@documenso/lib/types/recipient';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { canRecipientBeModified as utilCanRecipientBeModified } from '@documenso/lib/utils/recipients';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
@@ -54,12 +55,12 @@ import { SigningOrderConfirmation } from './signing-order-confirmation';
|
||||
import type { DocumentFlowStep } from './types';
|
||||
|
||||
type AutoSaveResponse = {
|
||||
recipients: Recipient[];
|
||||
recipients: TRecipientLite[];
|
||||
};
|
||||
|
||||
export type AddSignersFormProps = {
|
||||
documentFlow: DocumentFlowStep;
|
||||
recipients: Recipient[];
|
||||
recipients: TRecipientLite[];
|
||||
fields: Field[];
|
||||
signingOrder?: DocumentSigningOrder | null;
|
||||
allowDictateNextSigner?: boolean;
|
||||
|
||||
@@ -3,16 +3,14 @@ import { DocumentSigningOrder, RecipientRole } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZRecipientActionAuthTypesSchema } from '@documenso/lib/types/document-auth';
|
||||
import { zEmail } from '@documenso/lib/utils/zod';
|
||||
|
||||
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),
|
||||
email: zEmail(msg`Invalid email`.id).min(1),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
|
||||
@@ -4,7 +4,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { Field, Recipient } from '@prisma/client';
|
||||
import type { Field } from '@prisma/client';
|
||||
import { DocumentDistributionMethod, DocumentStatus, RecipientRole } from '@prisma/client';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
@@ -15,6 +15,7 @@ import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/org
|
||||
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||
import type { TDocument } from '@documenso/lib/types/document';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import type { TRecipientLite } from '@documenso/lib/types/recipient';
|
||||
import { formatSigningLink } from '@documenso/lib/utils/recipients';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { DocumentSendEmailMessageHelper } from '@documenso/ui/components/document/document-send-email-message-helper';
|
||||
@@ -59,7 +60,7 @@ import type { DocumentFlowStep } from './types';
|
||||
|
||||
export type AddSubjectFormProps = {
|
||||
documentFlow: DocumentFlowStep;
|
||||
recipients: Recipient[];
|
||||
recipients: TRecipientLite[];
|
||||
fields: Field[];
|
||||
document: TDocument;
|
||||
onSubmit: (_data: TAddSubjectFormSchema) => void;
|
||||
|
||||
@@ -2,14 +2,12 @@ import { DocumentDistributionMethod } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { zEmail } from '@documenso/lib/utils/zod';
|
||||
|
||||
export const ZAddSubjectFormSchema = z.object({
|
||||
meta: z.object({
|
||||
emailId: z.string().nullable(),
|
||||
emailReplyTo: z.preprocess(
|
||||
(val) => (val === '' ? undefined : val),
|
||||
z.string().email().optional(),
|
||||
),
|
||||
emailReplyTo: z.preprocess((val) => (val === '' ? undefined : val), zEmail().optional()),
|
||||
// emailReplyName: z.string().optional(),
|
||||
subject: z.string(),
|
||||
message: z.string(),
|
||||
|
||||
@@ -333,34 +333,44 @@ const FieldItemInner = ({
|
||||
</div>
|
||||
|
||||
{isDevMode && (
|
||||
<div className="absolute -top-20 left-1/2 z-50 -translate-x-1/2 rounded-md border border-border bg-background/95 px-2 py-1 shadow-sm backdrop-blur-sm">
|
||||
<div className="absolute bottom-full left-1/2 z-50 mb-1 -translate-x-1/2 rounded-md border border-border bg-background/95 px-2 py-1 shadow-sm backdrop-blur-sm">
|
||||
<div className="flex flex-col gap-0.5 text-[9px]">
|
||||
{field.nativeId && (
|
||||
<span>
|
||||
<span className="text-muted-foreground">
|
||||
<Trans>Field ID:</Trans>
|
||||
</span>{' '}
|
||||
<span className="font-mono text-foreground">{field.nativeId}</span>
|
||||
</span>
|
||||
)}
|
||||
<span>
|
||||
<span className="text-muted-foreground">
|
||||
<Trans>Recipient ID:</Trans>
|
||||
</span>{' '}
|
||||
<span className="font-mono text-foreground">{field.recipientId}</span>
|
||||
</span>
|
||||
<span>
|
||||
<span className="text-muted-foreground">
|
||||
<Trans>Pos X:</Trans>
|
||||
</span>
|
||||
|
||||
</span>{' '}
|
||||
<span className="font-mono text-foreground">{field.pageX.toFixed(2)}</span>
|
||||
</span>
|
||||
<span>
|
||||
<span className="text-muted-foreground">
|
||||
<Trans>Pos Y:</Trans>
|
||||
</span>
|
||||
|
||||
</span>{' '}
|
||||
<span className="font-mono text-foreground">{field.pageY.toFixed(2)}</span>
|
||||
</span>
|
||||
<span>
|
||||
<span className="text-muted-foreground">
|
||||
<Trans>Width:</Trans>
|
||||
</span>
|
||||
|
||||
</span>{' '}
|
||||
<span className="font-mono text-foreground">{field.pageWidth.toFixed(2)}</span>
|
||||
</span>
|
||||
<span>
|
||||
<span className="text-muted-foreground">
|
||||
<Trans>Height:</Trans>
|
||||
</span>
|
||||
|
||||
</span>{' '}
|
||||
<span className="font-mono text-foreground">{field.pageHeight.toFixed(2)}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
+3
-3
@@ -114,7 +114,7 @@ export const DropdownFieldAdvancedSettings = ({
|
||||
handleFieldChange('defaultValue', val);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="text-muted-foreground bg-background mt-2 w-full">
|
||||
<SelectTrigger className="mt-2 w-full bg-background text-muted-foreground">
|
||||
<SelectValue defaultValue={defaultValue} placeholder={`-- ${_(msg`Select`)} --`} />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="popper">
|
||||
@@ -152,7 +152,7 @@ export const DropdownFieldAdvancedSettings = ({
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className="bg-foreground/10 hover:bg-foreground/5 border-foreground/10 mt-2 border"
|
||||
className="mt-2 border border-foreground/10 bg-foreground/10 hover:bg-foreground/5"
|
||||
variant="outline"
|
||||
onClick={() => setShowValidation((prev) => !prev)}
|
||||
>
|
||||
@@ -183,7 +183,7 @@ export const DropdownFieldAdvancedSettings = ({
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
className="bg-foreground/10 hover:bg-foreground/5 border-foreground/10 ml-9 mt-4 border"
|
||||
className="ml-9 mt-4 border border-foreground/10 bg-foreground/10 hover:bg-foreground/5"
|
||||
variant="outline"
|
||||
onClick={addValue}
|
||||
>
|
||||
|
||||
@@ -122,7 +122,7 @@ export const RadioFieldAdvancedSettings = ({
|
||||
</Label>
|
||||
<Input
|
||||
id="label"
|
||||
className="bg-background mt-2"
|
||||
className="mt-2 bg-background"
|
||||
placeholder={_(msg`Field label`)}
|
||||
value={fieldState.label}
|
||||
onChange={(e) => handleFieldChange('label', e.target.value)}
|
||||
@@ -150,7 +150,7 @@ export const RadioFieldAdvancedSettings = ({
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className="bg-foreground/10 hover:bg-foreground/5 border-foreground/10 mt-2 border"
|
||||
className="mt-2 border border-foreground/10 bg-foreground/10 hover:bg-foreground/5"
|
||||
variant="outline"
|
||||
onClick={() => setShowValidation((prev) => !prev)}
|
||||
>
|
||||
@@ -167,7 +167,7 @@ export const RadioFieldAdvancedSettings = ({
|
||||
{values.map((value) => (
|
||||
<div key={value.id} className="mt-2 flex items-center gap-4">
|
||||
<Checkbox
|
||||
className="data-[state=checked]:bg-documenso border-foreground/30 data-[state=checked]:ring-primary dark:data-[state=checked]:ring-offset-background h-5 w-5 rounded-full data-[state=checked]:ring-1 data-[state=checked]:ring-offset-2 data-[state=checked]:ring-offset-white"
|
||||
className="h-5 w-5 rounded-full border-foreground/30 data-[state=checked]:bg-documenso data-[state=checked]:ring-1 data-[state=checked]:ring-primary data-[state=checked]:ring-offset-2 data-[state=checked]:ring-offset-white dark:data-[state=checked]:ring-offset-background"
|
||||
checked={value.checked}
|
||||
onCheckedChange={(checked) => handleCheckedChange(Boolean(checked), value.id)}
|
||||
/>
|
||||
@@ -186,7 +186,7 @@ export const RadioFieldAdvancedSettings = ({
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
className="bg-foreground/10 hover:bg-foreground/5 border-foreground/10 ml-9 mt-4 border"
|
||||
className="ml-9 mt-4 border border-foreground/10 bg-foreground/10 hover:bg-foreground/5"
|
||||
variant="outline"
|
||||
onClick={addValue}
|
||||
>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { FieldType } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import { zEmail } from '@documenso/lib/utils/zod';
|
||||
|
||||
export const ZDocumentFlowFormSchema = z.object({
|
||||
title: z.string().min(1),
|
||||
@@ -12,7 +13,7 @@ export const ZDocumentFlowFormSchema = z.object({
|
||||
z.object({
|
||||
formId: z.string().min(1),
|
||||
nativeId: z.number().optional(),
|
||||
email: z.string().min(1).email(),
|
||||
email: zEmail().min(1),
|
||||
name: z.string(),
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -104,10 +104,10 @@ export const FieldSelector = ({
|
||||
)}
|
||||
>
|
||||
<CardContent className="relative flex items-center justify-center gap-x-2 px-6 py-4">
|
||||
{Icon && <Icon className="text-muted-foreground h-4 w-4" />}
|
||||
{Icon && <Icon className="h-4 w-4 text-muted-foreground" />}
|
||||
<span
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground text-sm',
|
||||
'text-sm text-muted-foreground group-data-[selected]:text-foreground',
|
||||
field.type === FieldType.SIGNATURE && 'invisible',
|
||||
)}
|
||||
>
|
||||
@@ -115,7 +115,7 @@ export const FieldSelector = ({
|
||||
</span>
|
||||
|
||||
{field.type === FieldType.SIGNATURE && (
|
||||
<div className="text-muted-foreground font-signature absolute inset-0 flex items-center justify-center text-lg">
|
||||
<div className="absolute inset-0 flex items-center justify-center font-signature text-lg text-muted-foreground">
|
||||
<Trans>Signature</Trans>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -2,12 +2,12 @@ import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { Recipient } from '@prisma/client';
|
||||
import { RecipientRole, SendStatus, SigningStatus } from '@prisma/client';
|
||||
import { Check, ChevronsUpDown, Info } from 'lucide-react';
|
||||
import { sortBy } from 'remeda';
|
||||
|
||||
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||
import type { TRecipientLite } from '@documenso/lib/types/recipient';
|
||||
|
||||
import { getRecipientColorStyles } from '../lib/recipient-colors';
|
||||
import { cn } from '../lib/utils';
|
||||
@@ -18,9 +18,9 @@ import { Tooltip, TooltipContent, TooltipTrigger } from './tooltip';
|
||||
|
||||
export interface RecipientSelectorProps {
|
||||
className?: string;
|
||||
selectedRecipient: Recipient | null;
|
||||
onSelectedRecipientChange: (recipient: Recipient) => void;
|
||||
recipients: Recipient[];
|
||||
selectedRecipient: TRecipientLite | null;
|
||||
onSelectedRecipientChange: (recipient: TRecipientLite) => void;
|
||||
recipients: TRecipientLite[];
|
||||
align?: 'center' | 'end' | 'start';
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export const RecipientSelector = ({
|
||||
const [showRecipientsSelector, setShowRecipientsSelector] = useState(false);
|
||||
|
||||
const recipientsByRole = useMemo(() => {
|
||||
const recipientsWithRole: Record<RecipientRole, Recipient[]> = {
|
||||
const recipientsWithRole: Record<RecipientRole, TRecipientLite[]> = {
|
||||
CC: [],
|
||||
VIEWER: [],
|
||||
SIGNER: [],
|
||||
@@ -67,12 +67,12 @@ export const RecipientSelector = ({
|
||||
[(r) => r.signingOrder || Number.MAX_SAFE_INTEGER, 'asc'],
|
||||
[(r) => r.id, 'asc'],
|
||||
),
|
||||
] as [RecipientRole, Recipient[]],
|
||||
] as [RecipientRole, TRecipientLite[]],
|
||||
);
|
||||
}, [recipientsByRole]);
|
||||
|
||||
const getRecipientLabel = useCallback(
|
||||
(recipient: Recipient) => {
|
||||
(recipient: TRecipientLite) => {
|
||||
if (recipient.name && recipient.email) {
|
||||
return `${recipient.name} (${recipient.email})`;
|
||||
}
|
||||
@@ -101,7 +101,7 @@ export const RecipientSelector = ({
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
className={cn(
|
||||
'bg-background text-muted-foreground hover:text-foreground justify-between font-normal',
|
||||
'justify-between bg-background font-normal text-muted-foreground hover:text-foreground',
|
||||
getRecipientColorStyles(recipients.findIndex((r) => r.id === selectedRecipient?.id))
|
||||
.comboBoxTrigger,
|
||||
className,
|
||||
@@ -122,21 +122,21 @@ export const RecipientSelector = ({
|
||||
<CommandInput />
|
||||
|
||||
<CommandEmpty>
|
||||
<span className="text-muted-foreground inline-block px-4">
|
||||
<span className="inline-block px-4 text-muted-foreground">
|
||||
<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">
|
||||
<div className="mb-1 ml-2 mt-2 text-xs font-medium text-muted-foreground">
|
||||
{_(RECIPIENT_ROLES_DESCRIPTION[role].roleNamePlural)}
|
||||
</div>
|
||||
|
||||
{roleRecipients.length === 0 && (
|
||||
<div
|
||||
key={`${role}-empty`}
|
||||
className="text-muted-foreground/80 px-4 pb-4 pt-2.5 text-center text-xs"
|
||||
className="px-4 pb-4 pt-2.5 text-center text-xs text-muted-foreground/80"
|
||||
>
|
||||
<Trans>No recipients with this role</Trans>
|
||||
</div>
|
||||
@@ -160,7 +160,7 @@ export const RecipientSelector = ({
|
||||
disabled={recipient.signingStatus !== SigningStatus.NOT_SIGNED}
|
||||
>
|
||||
<span
|
||||
className={cn('text-foreground/70 truncate', {
|
||||
className={cn('truncate text-foreground/70', {
|
||||
'text-foreground/80': recipient.id === selectedRecipient?.id,
|
||||
})}
|
||||
>
|
||||
@@ -182,7 +182,7 @@ export const RecipientSelector = ({
|
||||
<Info className="ml-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-muted-foreground max-w-xs">
|
||||
<TooltipContent className="max-w-xs text-muted-foreground">
|
||||
<Trans>
|
||||
This document has already been sent to this recipient. You can no longer
|
||||
edit this recipient.
|
||||
|
||||
@@ -23,7 +23,7 @@ const SelectTrigger = React.forwardRef<
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'border-input ring-offset-background placeholder:text-muted-foreground focus:ring-ring flex h-10 w-full items-center justify-between rounded-md border bg-transparent px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className,
|
||||
)}
|
||||
disabled={loading || props.disabled}
|
||||
@@ -56,7 +56,7 @@ const SelectContent = React.forwardRef<
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'bg-popover text-popover-foreground animate-in fade-in-80 relative z-[1001] min-w-[8rem] overflow-hidden rounded-md border shadow-md',
|
||||
'relative z-[1001] min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md animate-in fade-in-80',
|
||||
position === 'popper' && 'translate-y-1',
|
||||
className,
|
||||
)}
|
||||
@@ -65,7 +65,7 @@ const SelectContent = React.forwardRef<
|
||||
>
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
'p-1',
|
||||
'max-h-60 overflow-y-auto p-1',
|
||||
position === 'popper' &&
|
||||
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]',
|
||||
)}
|
||||
@@ -98,7 +98,7 @@ const SelectItem = React.forwardRef<
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'focus:bg-accent focus:text-accent-foreground relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -121,7 +121,7 @@ const SelectSeparator = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn('bg-muted -mx-1 my-1 h-px', className)}
|
||||
className={cn('-mx-1 my-1 h-px bg-muted', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
@@ -4,7 +4,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { Field, Recipient } from '@prisma/client';
|
||||
import type { Field } from '@prisma/client';
|
||||
import { FieldType, RecipientRole, SendStatus } from '@prisma/client';
|
||||
import {
|
||||
CalendarDays,
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
type TFieldMetaSchema as FieldMeta,
|
||||
ZFieldMetaSchema,
|
||||
} from '@documenso/lib/types/field-meta';
|
||||
import type { TRecipientLite } from '@documenso/lib/types/recipient';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { ADVANCED_FIELD_TYPES_WITH_OPTIONAL_SETTING } from '@documenso/lib/utils/advanced-fields-helpers';
|
||||
import { parseMessageDescriptor } from '@documenso/lib/utils/i18n';
|
||||
@@ -75,7 +76,7 @@ const DEFAULT_WIDTH_PX = MIN_WIDTH_PX * 2.5;
|
||||
|
||||
export type AddTemplateFieldsFormProps = {
|
||||
documentFlow: DocumentFlowStep;
|
||||
recipients: Recipient[];
|
||||
recipients: TRecipientLite[];
|
||||
fields: Field[];
|
||||
onSubmit: (_data: TAddTemplateFieldsFormSchema) => void;
|
||||
onAutoSave: (_data: TAddTemplateFieldsFormSchema) => Promise<void>;
|
||||
@@ -154,7 +155,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
});
|
||||
|
||||
const [selectedField, setSelectedField] = useState<FieldType | null>(null);
|
||||
const [selectedSigner, setSelectedSigner] = useState<Recipient | null>(null);
|
||||
const [selectedSigner, setSelectedSigner] = useState<TRecipientLite | null>(null);
|
||||
const [showRecipientsSelector, setShowRecipientsSelector] = useState(false);
|
||||
|
||||
const selectedSignerIndex = recipients.findIndex((r) => r.id === selectedSigner?.id);
|
||||
@@ -491,7 +492,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
}, [recipients]);
|
||||
|
||||
const recipientsByRole = useMemo(() => {
|
||||
const recipientsByRole: Record<RecipientRole, Recipient[]> = {
|
||||
const recipientsByRole: Record<RecipientRole, TRecipientLite[]> = {
|
||||
CC: [],
|
||||
VIEWER: [],
|
||||
SIGNER: [],
|
||||
@@ -520,7 +521,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
|
||||
const recipientsByRoleToDisplay = useMemo(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return (Object.entries(recipientsByRole) as [RecipientRole, Recipient[]][]).filter(
|
||||
return (Object.entries(recipientsByRole) as [RecipientRole, TRecipientLite[]][]).filter(
|
||||
([role]) =>
|
||||
role !== RecipientRole.CC &&
|
||||
role !== RecipientRole.VIEWER &&
|
||||
|
||||
@@ -7,7 +7,7 @@ import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { TemplateDirectLink } from '@prisma/client';
|
||||
import { DocumentSigningOrder, type Field, type Recipient, RecipientRole } from '@prisma/client';
|
||||
import { DocumentSigningOrder, type Field, RecipientRole } from '@prisma/client';
|
||||
import { motion } from 'framer-motion';
|
||||
import { GripVerticalIcon, HelpCircle, Link2Icon, Plus, Trash } from 'lucide-react';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
@@ -17,6 +17,7 @@ import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/org
|
||||
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 type { TRecipientLite } from '@documenso/lib/types/recipient';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { generateRecipientPlaceholder } from '@documenso/lib/utils/templates';
|
||||
import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out';
|
||||
@@ -49,12 +50,12 @@ import type { TAddTemplatePlacholderRecipientsFormSchema } from './add-template-
|
||||
import { ZAddTemplatePlacholderRecipientsFormSchema } from './add-template-placeholder-recipients.types';
|
||||
|
||||
type AutoSaveResponse = {
|
||||
recipients: Recipient[];
|
||||
recipients: TRecipientLite[];
|
||||
};
|
||||
|
||||
export type AddTemplatePlaceholderRecipientsFormProps = {
|
||||
documentFlow: DocumentFlowStep;
|
||||
recipients: Recipient[];
|
||||
recipients: TRecipientLite[];
|
||||
fields: Field[];
|
||||
signingOrder?: DocumentSigningOrder | null;
|
||||
allowDictateNextSigner?: boolean;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { DocumentSigningOrder, RecipientRole } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZRecipientActionAuthTypesSchema } from '@documenso/lib/types/document-auth';
|
||||
import { zEmail } from '@documenso/lib/utils/zod';
|
||||
|
||||
export const ZAddTemplatePlacholderRecipientsFormSchema = z
|
||||
.object({
|
||||
@@ -9,7 +10,7 @@ export const ZAddTemplatePlacholderRecipientsFormSchema = z
|
||||
z.object({
|
||||
formId: z.string().min(1),
|
||||
nativeId: z.number().optional(),
|
||||
email: z.string().min(1).email(),
|
||||
email: zEmail().min(1),
|
||||
name: z.string().min(1, { message: 'Name is required' }),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
|
||||
@@ -2,8 +2,8 @@ import { useEffect } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { DocumentVisibility, TeamMemberRole } from '@prisma/client';
|
||||
import { DocumentDistributionMethod, type Field, type Recipient } from '@prisma/client';
|
||||
import { DocumentVisibility, TeamMemberRole, TemplateType } from '@prisma/client';
|
||||
import { DocumentDistributionMethod, type Field } from '@prisma/client';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { match } from 'ts-pattern';
|
||||
@@ -19,6 +19,7 @@ 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 type { TDocumentMetaDateFormat } from '@documenso/lib/types/document-meta';
|
||||
import type { TRecipientLite } from '@documenso/lib/types/recipient';
|
||||
import type { TTemplate } from '@documenso/lib/types/template';
|
||||
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
||||
import { extractTeamSignatureSettings } from '@documenso/lib/utils/teams';
|
||||
@@ -36,6 +37,10 @@ import {
|
||||
DocumentVisibilitySelect,
|
||||
DocumentVisibilityTooltip,
|
||||
} from '@documenso/ui/components/document/document-visibility-select';
|
||||
import {
|
||||
TemplateTypeSelect,
|
||||
TemplateTypeTooltip,
|
||||
} from '@documenso/ui/components/template/template-type-select';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
@@ -77,7 +82,7 @@ import { ZAddTemplateSettingsFormSchema } from './add-template-settings.types';
|
||||
|
||||
export type AddTemplateSettingsFormProps = {
|
||||
documentFlow: DocumentFlowStep;
|
||||
recipients: Recipient[];
|
||||
recipients: TRecipientLite[];
|
||||
fields: Field[];
|
||||
isDocumentPdfLoaded: boolean;
|
||||
template: TTemplate;
|
||||
@@ -109,6 +114,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
defaultValues: {
|
||||
title: template.title,
|
||||
externalId: template.externalId || undefined,
|
||||
templateType: template.type || TemplateType.PRIVATE,
|
||||
visibility: template.visibility || '',
|
||||
globalAccessAuth: documentAuthOption?.globalAccessAuth || [],
|
||||
globalActionAuth: documentAuthOption?.globalActionAuth || [],
|
||||
@@ -325,6 +331,29 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="templateType"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
<Trans>Template type</Trans>
|
||||
<TemplateTypeTooltip organisationTeamCount={organisation.teams.length} />
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<TemplateTypeSelect
|
||||
{...field}
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value);
|
||||
void handleAutoSave();
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="meta.distributionMethod"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { DocumentDistributionMethod } from '@prisma/client';
|
||||
import { DocumentVisibility } from '@prisma/client';
|
||||
import { DocumentVisibility, TemplateType } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
@@ -17,10 +17,12 @@ import {
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
} from '@documenso/lib/types/document-meta';
|
||||
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
|
||||
import { zEmail } from '@documenso/lib/utils/zod';
|
||||
|
||||
export const ZAddTemplateSettingsFormSchema = z.object({
|
||||
title: z.string().trim().min(1, { message: "Title can't be empty" }),
|
||||
externalId: z.string().optional(),
|
||||
templateType: z.nativeEnum(TemplateType).optional(),
|
||||
visibility: z.nativeEnum(DocumentVisibility).optional(),
|
||||
globalAccessAuth: z
|
||||
.array(z.union([ZDocumentAccessAuthTypesSchema, z.literal('-1')]))
|
||||
@@ -49,10 +51,7 @@ export const ZAddTemplateSettingsFormSchema = z.object({
|
||||
.optional()
|
||||
.default('en'),
|
||||
emailId: z.string().nullable(),
|
||||
emailReplyTo: z.preprocess(
|
||||
(val) => (val === '' ? undefined : val),
|
||||
z.string().email().optional(),
|
||||
),
|
||||
emailReplyTo: z.preprocess((val) => (val === '' ? undefined : val), zEmail().optional()),
|
||||
emailSettings: ZDocumentEmailSettingsSchema,
|
||||
signatureTypes: z.array(z.nativeEnum(DocumentSignatureType)).min(1, {
|
||||
message: msg`At least one signature type must be enabled`.id,
|
||||
|
||||
@@ -8,7 +8,14 @@ const TooltipProvider = TooltipPrimitive.Provider;
|
||||
|
||||
const Tooltip = TooltipPrimitive.Root;
|
||||
|
||||
const TooltipTrigger = TooltipPrimitive.Trigger;
|
||||
const TooltipTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Trigger>
|
||||
>(({ type = 'button', ...props }, ref) => (
|
||||
<TooltipPrimitive.Trigger ref={ref} type={type} {...props} />
|
||||
));
|
||||
|
||||
TooltipTrigger.displayName = TooltipPrimitive.Trigger.displayName;
|
||||
|
||||
const TooltipContent = React.forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||
@@ -19,7 +26,7 @@ const TooltipContent = React.forwardRef<
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
'bg-popover z-9999 text-popover-foreground animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1 overflow-hidden rounded-md border px-3 py-1.5 text-sm shadow-md',
|
||||
'z-9999 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
Reference in New Issue
Block a user