Merge branch 'main' into exp/autoplace-fields

This commit is contained in:
Ephraim Duncan
2025-11-19 00:44:51 +00:00
committed by GitHub
213 changed files with 1920 additions and 1439 deletions

View File

@ -1,5 +1,6 @@
import React, { forwardRef } from 'react';
import { useLingui } from '@lingui/react/macro';
import { TeamMemberRole } from '@prisma/client';
import type { SelectProps } from '@radix-ui/react-select';
import { InfoIcon } from 'lucide-react';
@ -27,6 +28,8 @@ export const DocumentVisibilitySelect = forwardRef<HTMLButtonElement, DocumentVi
{ currentTeamMemberRole, isTeamSettings = false, disabled, canUpdateVisibility, ...props },
ref,
) => {
const { t } = useLingui();
const isAdmin = currentTeamMemberRole === TeamMemberRole.ADMIN;
const isManager = currentTeamMemberRole === TeamMemberRole.MANAGER;
const canEdit = isTeamSettings || canUpdateVisibility;
@ -34,7 +37,7 @@ export const DocumentVisibilitySelect = forwardRef<HTMLButtonElement, DocumentVi
return (
<Select {...props} disabled={!canEdit || disabled}>
<SelectTrigger ref={ref} className="bg-background text-muted-foreground">
<SelectValue data-testid="documentVisibilitySelectValue" placeholder="Everyone" />
<SelectValue data-testid="documentVisibilitySelectValue" placeholder={t`Everyone`} />
</SelectTrigger>
<SelectContent position="popper">

View File

@ -419,7 +419,7 @@ export const AddSettingsFormPartial = ({
void handleAutoSave();
}}
className="bg-background w-full"
emptySelectionPlaceholder="Select signature types"
emptySelectionPlaceholder={t`Select signature types`}
/>
</FormControl>

View File

@ -1,6 +1,4 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { Trans, useLingui } from '@lingui/react/macro';
import { validateFields as validateDateFields } from '@documenso/lib/advanced-fields-validation/validate-fields';
import { type TDateFieldMeta as DateFieldMeta } from '@documenso/lib/types/field-meta';
@ -25,7 +23,7 @@ export const DateFieldAdvancedSettings = ({
handleFieldChange,
handleErrors,
}: DateFieldAdvancedSettingsProps) => {
const { _ } = useLingui();
const { t } = useLingui();
// const handleInput = (field: keyof DateFieldMeta, value: string | boolean) => {
// if (field === 'fontSize') {
@ -67,7 +65,7 @@ export const DateFieldAdvancedSettings = ({
id="fontSize"
type="number"
className="bg-background mt-2"
placeholder={_(msg`Field font size`)}
placeholder={t`Field font size`}
value={fieldState.fontSize}
onChange={(e) => handleInput('fontSize', e.target.value)}
min={8}
@ -85,7 +83,7 @@ export const DateFieldAdvancedSettings = ({
onValueChange={(value) => handleInput('textAlign', value)}
>
<SelectTrigger className="bg-background mt-2">
<SelectValue placeholder="Select text align" />
<SelectValue placeholder={t`Select text align`} />
</SelectTrigger>
<SelectContent>

View File

@ -1,6 +1,4 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { Trans, useLingui } from '@lingui/react/macro';
import { validateFields as validateEmailFields } from '@documenso/lib/advanced-fields-validation/validate-fields';
import { type TEmailFieldMeta as EmailFieldMeta } from '@documenso/lib/types/field-meta';
@ -25,7 +23,7 @@ export const EmailFieldAdvancedSettings = ({
handleFieldChange,
handleErrors,
}: EmailFieldAdvancedSettingsProps) => {
const { _ } = useLingui();
const { t } = useLingui();
const handleInput = (field: keyof EmailFieldMeta, value: string | boolean) => {
const fontSize = field === 'fontSize' ? Number(value) : Number(fieldState.fontSize ?? 14);
@ -49,7 +47,7 @@ export const EmailFieldAdvancedSettings = ({
id="fontSize"
type="number"
className="bg-background mt-2"
placeholder={_(msg`Field font size`)}
placeholder={t`Field font size`}
value={fieldState.fontSize}
onChange={(e) => handleInput('fontSize', e.target.value)}
min={8}
@ -67,7 +65,7 @@ export const EmailFieldAdvancedSettings = ({
onValueChange={(value) => handleInput('textAlign', value)}
>
<SelectTrigger className="bg-background mt-2">
<SelectValue placeholder="Select text align" />
<SelectValue placeholder={t`Select text align`} />
</SelectTrigger>
<SelectContent>

View File

@ -1,6 +1,4 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { Trans, useLingui } from '@lingui/react/macro';
import { validateFields as validateInitialsFields } from '@documenso/lib/advanced-fields-validation/validate-fields';
import { type TInitialsFieldMeta as InitialsFieldMeta } from '@documenso/lib/types/field-meta';
@ -20,7 +18,7 @@ export const InitialsFieldAdvancedSettings = ({
handleFieldChange,
handleErrors,
}: InitialsFieldAdvancedSettingsProps) => {
const { _ } = useLingui();
const { t } = useLingui();
const handleInput = (field: keyof InitialsFieldMeta, value: string | boolean) => {
const fontSize = field === 'fontSize' ? Number(value) : Number(fieldState.fontSize ?? 14);
@ -44,7 +42,7 @@ export const InitialsFieldAdvancedSettings = ({
id="fontSize"
type="number"
className="bg-background mt-2"
placeholder={_(msg`Field font size`)}
placeholder={t`Field font size`}
value={fieldState.fontSize}
onChange={(e) => handleInput('fontSize', e.target.value)}
min={8}
@ -62,7 +60,7 @@ export const InitialsFieldAdvancedSettings = ({
onValueChange={(value) => handleInput('textAlign', value)}
>
<SelectTrigger className="bg-background mt-2">
<SelectValue placeholder="Select text align" />
<SelectValue placeholder={t`Select text align`} />
</SelectTrigger>
<SelectContent>

View File

@ -1,6 +1,4 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { Trans, useLingui } from '@lingui/react/macro';
import { validateFields as validateNameFields } from '@documenso/lib/advanced-fields-validation/validate-fields';
import { type TNameFieldMeta as NameFieldMeta } from '@documenso/lib/types/field-meta';
@ -25,7 +23,7 @@ export const NameFieldAdvancedSettings = ({
handleFieldChange,
handleErrors,
}: NameFieldAdvancedSettingsProps) => {
const { _ } = useLingui();
const { t } = useLingui();
const handleInput = (field: keyof NameFieldMeta, value: string | boolean) => {
const fontSize = field === 'fontSize' ? Number(value) : Number(fieldState.fontSize ?? 14);
@ -49,7 +47,7 @@ export const NameFieldAdvancedSettings = ({
id="fontSize"
type="number"
className="bg-background mt-2"
placeholder={_(msg`Field font size`)}
placeholder={t`Field font size`}
value={fieldState.fontSize}
onChange={(e) => handleInput('fontSize', e.target.value)}
min={8}
@ -67,7 +65,7 @@ export const NameFieldAdvancedSettings = ({
onValueChange={(value) => handleInput('textAlign', value)}
>
<SelectTrigger className="bg-background mt-2">
<SelectValue placeholder="Select text align" />
<SelectValue placeholder={t`Select text align`} />
</SelectTrigger>
<SelectContent>

View File

@ -1,8 +1,6 @@
import { useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { Trans, useLingui } from '@lingui/react/macro';
import { ChevronDown, ChevronUp } from 'lucide-react';
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
@ -32,7 +30,7 @@ export const NumberFieldAdvancedSettings = ({
handleFieldChange,
handleErrors,
}: NumberFieldAdvancedSettingsProps) => {
const { _ } = useLingui();
const { t } = useLingui();
const [showValidation, setShowValidation] = useState(false);
@ -68,7 +66,7 @@ export const NumberFieldAdvancedSettings = ({
<Input
id="label"
className="bg-background mt-2"
placeholder={_(msg`Label`)}
placeholder={t`Label`}
value={fieldState.label}
onChange={(e) => handleFieldChange('label', e.target.value)}
/>
@ -80,7 +78,7 @@ export const NumberFieldAdvancedSettings = ({
<Input
id="placeholder"
className="bg-background mt-2"
placeholder={_(msg`Placeholder`)}
placeholder={t`Placeholder`}
value={fieldState.placeholder}
onChange={(e) => handleFieldChange('placeholder', e.target.value)}
/>
@ -92,7 +90,7 @@ export const NumberFieldAdvancedSettings = ({
<Input
id="value"
className="bg-background mt-2"
placeholder={_(msg`Value`)}
placeholder={t`Value`}
value={fieldState.value}
onChange={(e) => handleInput('value', e.target.value)}
/>
@ -106,7 +104,7 @@ export const NumberFieldAdvancedSettings = ({
onValueChange={(val) => handleInput('numberFormat', val)}
>
<SelectTrigger className="text-muted-foreground bg-background mt-2 w-full">
<SelectValue placeholder={_(msg`Field format`)} />
<SelectValue placeholder={t`Field format`} />
</SelectTrigger>
<SelectContent position="popper">
{numberFormatValues.map((item, index) => (
@ -126,7 +124,7 @@ export const NumberFieldAdvancedSettings = ({
id="fontSize"
type="number"
className="bg-background mt-2"
placeholder={_(msg`Field font size`)}
placeholder={t`Field font size`}
value={fieldState.fontSize}
onChange={(e) => handleInput('fontSize', e.target.value)}
min={8}
@ -144,7 +142,7 @@ export const NumberFieldAdvancedSettings = ({
onValueChange={(value) => handleInput('textAlign', value)}
>
<SelectTrigger className="bg-background mt-2">
<SelectValue placeholder="Select text align" />
<SelectValue placeholder={t`Select text align`} />
</SelectTrigger>
<SelectContent>
@ -198,7 +196,7 @@ export const NumberFieldAdvancedSettings = ({
<Input
id="minValue"
className="bg-background mt-2"
placeholder="E.g. 0"
placeholder={t`E.g. 0`}
value={fieldState.minValue ?? ''}
onChange={(e) => handleInput('minValue', e.target.value)}
/>
@ -210,7 +208,7 @@ export const NumberFieldAdvancedSettings = ({
<Input
id="maxValue"
className="bg-background mt-2"
placeholder="E.g. 100"
placeholder={t`E.g. 100`}
value={fieldState.maxValue ?? ''}
onChange={(e) => handleInput('maxValue', e.target.value)}
/>

View File

@ -1,6 +1,4 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { Trans, useLingui } from '@lingui/react/macro';
import { validateTextField } from '@documenso/lib/advanced-fields-validation/validate-text';
import { type TTextFieldMeta as TextFieldMeta } from '@documenso/lib/types/field-meta';
@ -27,7 +25,7 @@ export const TextFieldAdvancedSettings = ({
handleFieldChange,
handleErrors,
}: TextFieldAdvancedSettingsProps) => {
const { _ } = useLingui();
const { t } = useLingui();
const handleInput = (field: keyof TextFieldMeta, value: string | boolean) => {
const text = field === 'text' ? String(value) : (fieldState.text ?? '');
@ -58,7 +56,7 @@ export const TextFieldAdvancedSettings = ({
<Input
id="label"
className="bg-background mt-2"
placeholder={_(msg`Field label`)}
placeholder={t`Field label`}
value={fieldState.label}
onChange={(e) => handleFieldChange('label', e.target.value)}
/>
@ -70,7 +68,7 @@ export const TextFieldAdvancedSettings = ({
<Input
id="placeholder"
className="bg-background mt-2"
placeholder={_(msg`Field placeholder`)}
placeholder={t`Field placeholder`}
value={fieldState.placeholder}
onChange={(e) => handleFieldChange('placeholder', e.target.value)}
/>
@ -83,7 +81,7 @@ export const TextFieldAdvancedSettings = ({
<Textarea
id="text"
className="bg-background mt-2"
placeholder={_(msg`Add text to the field`)}
placeholder={t`Add text to the field`}
value={fieldState.text}
onChange={(e) => handleInput('text', e.target.value)}
/>
@ -98,7 +96,7 @@ export const TextFieldAdvancedSettings = ({
type="number"
min={0}
className="bg-background mt-2"
placeholder={_(msg`Field character limit`)}
placeholder={t`Field character limit`}
value={fieldState.characterLimit}
onChange={(e) => handleInput('characterLimit', e.target.value)}
/>
@ -112,7 +110,7 @@ export const TextFieldAdvancedSettings = ({
id="fontSize"
type="number"
className="bg-background mt-2"
placeholder={_(msg`Field font size`)}
placeholder={t`Field font size`}
value={fieldState.fontSize}
onChange={(e) => handleInput('fontSize', e.target.value)}
min={8}
@ -136,7 +134,7 @@ export const TextFieldAdvancedSettings = ({
}}
>
<SelectTrigger className="bg-background mt-2">
<SelectValue placeholder="Select text align" />
<SelectValue placeholder={t`Select text align`} />
</SelectTrigger>
<SelectContent>

View File

@ -1,6 +1,7 @@
import * as React from 'react';
import type { MessageDescriptor } from '@lingui/core';
import { t } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { AnimatePresence } from 'framer-motion';
@ -43,7 +44,7 @@ type MultiSelectComboboxProps<T = OptionValue> = {
* - Clear all button
*/
export function MultiSelectCombobox<T = OptionValue>({
emptySelectionPlaceholder = 'Select values...',
emptySelectionPlaceholder = t`Select values...`,
enableClearAllButton,
enableSearch = true,
className,

View File

@ -1,4 +1,4 @@
import { useCallback, useState } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
@ -34,8 +34,8 @@ export const RecipientSelector = ({
const { _ } = useLingui();
const [showRecipientsSelector, setShowRecipientsSelector] = useState(false);
const recipientsByRole = useCallback(() => {
const recipientsByRole: Record<RecipientRole, Recipient[]> = {
const recipientsByRole = useMemo(() => {
const recipientsWithRole: Record<RecipientRole, Recipient[]> = {
CC: [],
VIEWER: [],
SIGNER: [],
@ -44,14 +44,14 @@ export const RecipientSelector = ({
};
recipients.forEach((recipient) => {
recipientsByRole[recipient.role].push(recipient);
recipientsWithRole[recipient.role].push(recipient);
});
return recipientsByRole;
return recipientsWithRole;
}, [recipients]);
const recipientsByRoleToDisplay = useCallback(() => {
return Object.entries(recipientsByRole())
const recipientsByRoleToDisplay = useMemo(() => {
return Object.entries(recipientsByRole)
.filter(
([role]) =>
role !== RecipientRole.CC &&
@ -71,6 +71,28 @@ export const RecipientSelector = ({
);
}, [recipientsByRole]);
const getRecipientLabel = useCallback(
(recipient: Recipient) => {
if (recipient.name && recipient.email) {
return `${recipient.name} (${recipient.email})`;
}
if (recipient.name) {
return recipient.name;
}
if (recipient.email) {
return recipient.email;
}
// Since objects are basically pointers we can use `indexOf` rather than `findIndex`
const index = recipients.indexOf(recipient);
return `Recipient ${index + 1}`;
},
[recipients, selectedRecipient],
);
return (
<Popover open={showRecipientsSelector} onOpenChange={setShowRecipientsSelector}>
<PopoverTrigger asChild>
@ -89,16 +111,12 @@ export const RecipientSelector = ({
className,
)}
>
{selectedRecipient?.email && (
{selectedRecipient && (
<span className="flex-1 truncate text-left">
{selectedRecipient?.name} ({selectedRecipient?.email})
{getRecipientLabel(selectedRecipient)}
</span>
)}
{!selectedRecipient?.email && (
<span className="flex-1 truncate text-left">{selectedRecipient?.email}</span>
)}
<ChevronsUpDown className="ml-2 h-4 w-4" />
</Button>
</PopoverTrigger>
@ -113,7 +131,7 @@ export const RecipientSelector = ({
</span>
</CommandEmpty>
{recipientsByRoleToDisplay().map(([role, roleRecipients], roleIndex) => (
{recipientsByRoleToDisplay.map(([role, roleRecipients], roleIndex) => (
<CommandGroup key={roleIndex}>
<div className="text-muted-foreground mb-1 ml-2 mt-2 text-xs font-medium">
{_(RECIPIENT_ROLES_DESCRIPTION[role].roleNamePlural)}
@ -154,13 +172,7 @@ export const RecipientSelector = ({
'text-foreground/80': recipient.id === selectedRecipient?.id,
})}
>
{recipient.name && (
<span title={`${recipient.name} (${recipient.email})`}>
{recipient.name} ({recipient.email})
</span>
)}
{!recipient.name && <span title={recipient.email}>{recipient.email}</span>}
{getRecipientLabel(recipient)}
</span>
<div className="ml-auto flex items-center justify-center">

View File

@ -1,5 +1,7 @@
import { useState } from 'react';
import { useLingui } from '@lingui/react/macro';
import { cn } from '../../lib/utils';
export type SignaturePadTypeProps = {
@ -9,6 +11,7 @@ export type SignaturePadTypeProps = {
};
export const SignaturePadType = ({ className, value, onChange }: SignaturePadTypeProps) => {
const { t } = useLingui();
// Colors don't actually work for text.
const [selectedColor, setSelectedColor] = useState('black');
@ -16,7 +19,7 @@ export const SignaturePadType = ({ className, value, onChange }: SignaturePadTyp
<div className={cn('flex h-full w-full items-center justify-center', className)}>
<input
data-testid="signature-pad-type-input"
placeholder="Type your signature"
placeholder={t`Type your signature`}
className="font-signature w-full bg-transparent px-4 text-center text-7xl text-black placeholder:text-4xl focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 dark:text-white"
// style={{ color: selectedColor }}
value={value}

View File

@ -147,21 +147,21 @@ export const SignaturePad = ({
{drawSignatureEnabled && (
<TabsTrigger value="draw">
<SignatureIcon className="mr-2 size-4" />
<Trans>Draw</Trans>
<Trans context="Draw signature">Draw</Trans>
</TabsTrigger>
)}
{typedSignatureEnabled && (
<TabsTrigger value="text">
<KeyboardIcon className="mr-2 size-4" />
<Trans>Type</Trans>
<Trans context="Type signature">Type</Trans>
</TabsTrigger>
)}
{uploadSignatureEnabled && (
<TabsTrigger value="image">
<UploadCloudIcon className="mr-2 size-4" />
<Trans>Upload</Trans>
<Trans context="Upload signature">Upload</Trans>
</TabsTrigger>
)}
</TabsList>

View File

@ -1,8 +1,7 @@
import { useEffect } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import { Trans, useLingui } from '@lingui/react/macro';
import { DocumentVisibility, TeamMemberRole } from '@prisma/client';
import { DocumentDistributionMethod, type Field, type Recipient } from '@prisma/client';
import { InfoIcon } from 'lucide-react';
@ -425,7 +424,7 @@ export const AddTemplateSettingsFormPartial = ({
void handleAutoSave();
}}
className="bg-background w-full"
emptySelectionPlaceholder="Select signature types"
emptySelectionPlaceholder={t`Select signature types`}
/>
</FormControl>