mirror of
https://github.com/documenso/documenso.git
synced 2025-11-15 17:21:41 +10:00
chore: merged main
This commit is contained in:
@ -1,5 +1,7 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Check, ChevronDown } from 'lucide-react';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
@ -24,6 +26,8 @@ const Combobox = ({
|
||||
disabled = false,
|
||||
placeholder,
|
||||
}: ComboboxProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
const onOptionSelected = (newValue: string) => {
|
||||
@ -31,7 +35,7 @@ const Combobox = ({
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const placeholderValue = placeholder ?? 'Select an option';
|
||||
const placeholderValue = placeholder ?? _(msg`Select an option`);
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
@ -52,7 +56,9 @@ const Combobox = ({
|
||||
<Command>
|
||||
<CommandInput placeholder={value || placeholderValue} />
|
||||
|
||||
<CommandEmpty>No value found.</CommandEmpty>
|
||||
<CommandEmpty>
|
||||
<Trans>No value found.</Trans>
|
||||
</CommandEmpty>
|
||||
|
||||
<CommandGroup className="max-h-[250px] overflow-y-auto">
|
||||
{options.map((option, index) => (
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { Plural, Trans } from '@lingui/macro';
|
||||
import type { Table } from '@tanstack/react-table';
|
||||
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';
|
||||
import { match } from 'ts-pattern';
|
||||
@ -26,8 +27,10 @@ export function DataTablePagination<TData>({
|
||||
{match(additionalInformation)
|
||||
.with('SelectedCount', () => (
|
||||
<span>
|
||||
{table.getFilteredSelectedRowModel().rows.length} of{' '}
|
||||
{table.getFilteredRowModel().rows.length} row(s) selected.
|
||||
<Trans>
|
||||
{table.getFilteredSelectedRowModel().rows.length} of{' '}
|
||||
{table.getFilteredRowModel().rows.length} row(s) selected.
|
||||
</Trans>
|
||||
</span>
|
||||
))
|
||||
.with('VisibleCount', () => {
|
||||
@ -35,7 +38,11 @@ export function DataTablePagination<TData>({
|
||||
|
||||
return (
|
||||
<span data-testid="data-table-count">
|
||||
Showing {visibleRows} result{visibleRows > 1 && 's'}.
|
||||
<Plural
|
||||
value={visibleRows}
|
||||
one={`Showing # result.`}
|
||||
other={`Showing # results.`}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
})
|
||||
@ -44,7 +51,9 @@ export function DataTablePagination<TData>({
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-x-2">
|
||||
<p className="whitespace-nowrap text-sm font-medium">Rows per page</p>
|
||||
<p className="whitespace-nowrap text-sm font-medium">
|
||||
<Trans>Rows per page</Trans>
|
||||
</p>
|
||||
<Select
|
||||
value={`${table.getState().pagination.pageSize}`}
|
||||
onValueChange={(value) => {
|
||||
@ -65,7 +74,9 @@ export function DataTablePagination<TData>({
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-x-6 gap-y-4 lg:gap-x-8">
|
||||
<div className="flex items-center text-sm font-medium md:justify-center">
|
||||
Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount() || 1}
|
||||
<Trans>
|
||||
Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount() || 1}
|
||||
</Trans>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-x-2">
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import type {
|
||||
ColumnDef,
|
||||
PaginationState,
|
||||
@ -16,6 +17,8 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '.
|
||||
|
||||
export type DataTableChildren<TData> = (_table: TTable<TData>) => React.ReactNode;
|
||||
|
||||
export type { ColumnDef as DataTableColumnDef } from '@tanstack/react-table';
|
||||
|
||||
export interface DataTableProps<TData, TValue> {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
columnVisibility?: VisibilityState;
|
||||
@ -130,7 +133,7 @@ export function DataTable<TData, TValue>({
|
||||
<TableRow>
|
||||
{error.component ?? (
|
||||
<TableCell colSpan={columns.length} className="h-32 text-center">
|
||||
Something went wrong.
|
||||
<Trans>Something went wrong.</Trans>
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
@ -141,14 +144,16 @@ export function DataTable<TData, TValue>({
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-32 text-center">
|
||||
<p>No results found</p>
|
||||
<p>
|
||||
<Trans>No results found</Trans>
|
||||
</p>
|
||||
|
||||
{hasFilters && onClearFilters !== undefined && (
|
||||
<button
|
||||
onClick={() => onClearFilters()}
|
||||
className="text-foreground mt-1 text-sm"
|
||||
>
|
||||
Clear filters
|
||||
<Trans>Clear filters</Trans>
|
||||
</button>
|
||||
)}
|
||||
</TableCell>
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
|
||||
import Link from 'next/link';
|
||||
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { AlertTriangle, Plus } from 'lucide-react';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
@ -25,7 +28,7 @@ import { Card, CardContent } from './card';
|
||||
export type DocumentDropzoneProps = {
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
disabledMessage?: string;
|
||||
disabledMessage?: MessageDescriptor;
|
||||
onDrop?: (_file: File) => void | Promise<void>;
|
||||
onDropRejected?: () => void | Promise<void>;
|
||||
type?: 'document' | 'template';
|
||||
@ -37,10 +40,12 @@ export const DocumentDropzone = ({
|
||||
onDrop,
|
||||
onDropRejected,
|
||||
disabled,
|
||||
disabledMessage = 'You cannot upload documents at this time.',
|
||||
disabledMessage = msg`You cannot upload documents at this time.`,
|
||||
type = 'document',
|
||||
...props
|
||||
}: DocumentDropzoneProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const { getRootProps, getInputProps } = useDropzone({
|
||||
accept: {
|
||||
'application/pdf': ['.pdf'],
|
||||
@ -61,8 +66,8 @@ export const DocumentDropzone = ({
|
||||
});
|
||||
|
||||
const heading = {
|
||||
document: disabled ? 'You have reached your document limit.' : 'Add a document',
|
||||
template: 'Upload Template Document',
|
||||
document: disabled ? msg`You have reached your document limit.` : msg`Add a document`,
|
||||
template: msg`Upload Template Document`,
|
||||
};
|
||||
|
||||
return (
|
||||
@ -151,15 +156,17 @@ export const DocumentDropzone = ({
|
||||
|
||||
<input {...getInputProps()} />
|
||||
|
||||
<p className="text-foreground mt-8 font-medium">{heading[type]}</p>
|
||||
<p className="text-foreground mt-8 font-medium">{_(heading[type])}</p>
|
||||
|
||||
<p className="text-muted-foreground/80 mt-1 text-center text-sm">
|
||||
{disabled ? disabledMessage : 'Drag & drop your PDF here.'}
|
||||
{_(disabled ? disabledMessage : msg`Drag & drop your PDF here.`)}
|
||||
</p>
|
||||
|
||||
{disabled && IS_BILLING_ENABLED() && (
|
||||
<Button className="hover:bg-warning/80 bg-warning mt-4 w-32" asChild>
|
||||
<Link href="/settings/billing">Upgrade</Link>
|
||||
<Link href="/settings/billing">
|
||||
<Trans>Upgrade</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</CardContent>
|
||||
|
||||
@ -4,6 +4,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { Caveat } from 'next/font/google';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import {
|
||||
CalendarDays,
|
||||
Check,
|
||||
@ -24,7 +25,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||
import { useDocumentElement } from '@documenso/lib/client-only/hooks/use-document-element';
|
||||
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||
import { RECIPIENT_ROLES_DESCRIPTION_ENG } from '@documenso/lib/constants/recipient-roles';
|
||||
import {
|
||||
type TFieldMetaSchema as FieldMeta,
|
||||
ZFieldMetaSchema,
|
||||
@ -506,8 +507,8 @@ export const AddFieldsFormPartial = ({
|
||||
<>
|
||||
{showAdvancedSettings && currentField ? (
|
||||
<FieldAdvancedSettings
|
||||
title="Advanced settings"
|
||||
description={`Configure the ${FRIENDLY_FIELD_TYPE[currentField.type]} field`}
|
||||
title={msg`Advanced settings`}
|
||||
description={msg`Configure the ${FRIENDLY_FIELD_TYPE[currentField.type]} field`}
|
||||
field={currentField}
|
||||
fields={localFields}
|
||||
onAdvancedSettings={handleAdvancedSettings}
|
||||
@ -609,14 +610,15 @@ export const AddFieldsFormPartial = ({
|
||||
|
||||
<CommandEmpty>
|
||||
<span className="text-muted-foreground inline-block px-4">
|
||||
No recipient matching this description was found.
|
||||
<Trans>No recipient matching this description was found.</Trans>
|
||||
</span>
|
||||
</CommandEmpty>
|
||||
|
||||
{recipientsByRoleToDisplay.map(([role, roleRecipients], roleIndex) => (
|
||||
<CommandGroup key={roleIndex}>
|
||||
<div className="text-muted-foreground mb-1 ml-2 mt-2 text-xs font-medium">
|
||||
{`${RECIPIENT_ROLES_DESCRIPTION[role].roleName}s`}
|
||||
{/* Todo: Translations - Add plural translations. */}
|
||||
{`${RECIPIENT_ROLES_DESCRIPTION_ENG[role].roleName}s`}
|
||||
</div>
|
||||
|
||||
{roleRecipients.length === 0 && (
|
||||
@ -624,7 +626,7 @@ export const AddFieldsFormPartial = ({
|
||||
key={`${role}-empty`}
|
||||
className="text-muted-foreground/80 px-4 pb-4 pt-2.5 text-center text-xs"
|
||||
>
|
||||
No recipients with this role
|
||||
<Trans>No recipients with this role</Trans>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -680,8 +682,10 @@ export const AddFieldsFormPartial = ({
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-muted-foreground max-w-xs">
|
||||
This document has already been sent to this recipient. You can
|
||||
no longer edit this recipient.
|
||||
<Trans>
|
||||
This document has already been sent to this recipient. You
|
||||
can no longer edit this recipient.
|
||||
</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
@ -717,7 +721,7 @@ export const AddFieldsFormPartial = ({
|
||||
fontCaveat.className,
|
||||
)}
|
||||
>
|
||||
Signature
|
||||
<Trans>Signature</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -769,7 +773,7 @@ export const AddFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<Mail className="h-4 w-4" />
|
||||
Email
|
||||
<Trans>Email</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -795,7 +799,7 @@ export const AddFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<User className="h-4 w-4" />
|
||||
Name
|
||||
<Trans>Name</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -821,7 +825,7 @@ export const AddFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<CalendarDays className="h-4 w-4" />
|
||||
Date
|
||||
<Trans>Date</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -847,7 +851,7 @@ export const AddFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<Type className="h-4 w-4" />
|
||||
Text
|
||||
<Trans>Text</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -873,7 +877,7 @@ export const AddFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<Hash className="h-4 w-4" />
|
||||
Number
|
||||
<Trans>Number</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -899,7 +903,7 @@ export const AddFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<Disc className="h-4 w-4" />
|
||||
Radio
|
||||
<Trans>Radio</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -925,7 +929,7 @@ export const AddFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<CheckSquare className="h-4 w-4" />
|
||||
Checkbox
|
||||
<Trans>Checkbox</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -951,7 +955,7 @@ export const AddFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
Dropdown
|
||||
<Trans>Dropdown</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -964,23 +968,21 @@ export const AddFieldsFormPartial = ({
|
||||
<div className="mt-4">
|
||||
<ul>
|
||||
<li className="text-sm text-red-500">
|
||||
To proceed further, please set at least one value for the{' '}
|
||||
{emptyCheckboxFields.length > 0
|
||||
? 'Checkbox'
|
||||
: emptyRadioFields.length > 0
|
||||
? 'Radio'
|
||||
: 'Select'}{' '}
|
||||
field.
|
||||
<Trans>
|
||||
To proceed further, please set at least one value for the{' '}
|
||||
{emptyCheckboxFields.length > 0
|
||||
? 'Checkbox'
|
||||
: emptyRadioFields.length > 0
|
||||
? 'Radio'
|
||||
: 'Select'}{' '}
|
||||
field.
|
||||
</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
<DocumentFlowFormContainerFooter>
|
||||
<DocumentFlowFormContainerStep
|
||||
title={documentFlow.title}
|
||||
step={currentStep}
|
||||
maxStep={totalSteps}
|
||||
/>
|
||||
<DocumentFlowFormContainerStep step={currentStep} maxStep={totalSteps} />
|
||||
|
||||
<DocumentFlowFormContainerActions
|
||||
loading={isSubmitting}
|
||||
@ -991,7 +993,7 @@ export const AddFieldsFormPartial = ({
|
||||
remove();
|
||||
documentFlow.onBackStep?.();
|
||||
}}
|
||||
goBackLabel={canRenderBackButtonAsRemove ? 'Remove' : undefined}
|
||||
goBackLabel={canRenderBackButtonAsRemove ? msg`Remove` : undefined}
|
||||
onGoNextClick={handleGoNextClick}
|
||||
/>
|
||||
</DocumentFlowFormContainerFooter>
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
@ -140,7 +141,9 @@ export const AddSettingsFormPartial = ({
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required>Title</FormLabel>
|
||||
<FormLabel required>
|
||||
<Trans>Title</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
@ -160,7 +163,7 @@ export const AddSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
Document access
|
||||
<Trans>Document access</Trans>
|
||||
<DocumentGlobalAuthAccessTooltip />
|
||||
</FormLabel>
|
||||
|
||||
@ -178,7 +181,7 @@ export const AddSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
Recipient action authentication
|
||||
<Trans>Recipient action authentication</Trans>
|
||||
<DocumentGlobalAuthActionTooltip />
|
||||
</FormLabel>
|
||||
|
||||
@ -193,7 +196,7 @@ 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">
|
||||
Advanced Options
|
||||
<Trans>Advanced Options</Trans>
|
||||
</AccordionTrigger>
|
||||
|
||||
<AccordionContent className="text-muted-foreground -mx-1 px-1 pt-2 text-sm leading-relaxed">
|
||||
@ -204,15 +207,17 @@ export const AddSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
External ID{' '}
|
||||
<Trans>External ID</Trans>{' '}
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-muted-foreground max-w-xs">
|
||||
Add an external ID to the document. This can be used to identify the
|
||||
document in external systems.
|
||||
<Trans>
|
||||
Add an external ID to the document. This can be used to identify
|
||||
the document in external systems.
|
||||
</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormLabel>
|
||||
@ -231,7 +236,9 @@ export const AddSettingsFormPartial = ({
|
||||
name="meta.dateFormat"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Date Format</FormLabel>
|
||||
<FormLabel>
|
||||
<Trans>Date Format</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Select
|
||||
@ -263,7 +270,9 @@ export const AddSettingsFormPartial = ({
|
||||
name="meta.timezone"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Time Zone</FormLabel>
|
||||
<FormLabel>
|
||||
<Trans>Time Zone</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Combobox
|
||||
@ -286,14 +295,16 @@ export const AddSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
Redirect URL{' '}
|
||||
<Trans>Redirect URL</Trans>{' '}
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-muted-foreground max-w-xs">
|
||||
Add a URL to redirect the user to once the document is signed
|
||||
<Trans>
|
||||
Add a URL to redirect the user to once the document is signed
|
||||
</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormLabel>
|
||||
@ -315,11 +326,7 @@ export const AddSettingsFormPartial = ({
|
||||
</DocumentFlowFormContainerContent>
|
||||
|
||||
<DocumentFlowFormContainerFooter>
|
||||
<DocumentFlowFormContainerStep
|
||||
title={documentFlow.title}
|
||||
step={currentStep}
|
||||
maxStep={totalSteps}
|
||||
/>
|
||||
<DocumentFlowFormContainerStep step={currentStep} maxStep={totalSteps} />
|
||||
|
||||
<DocumentFlowFormContainerActions
|
||||
loading={form.formState.isSubmitting}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { DateTime } from 'luxon';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { match } from 'ts-pattern';
|
||||
@ -267,7 +268,9 @@ export const AddSignatureFormPartial = ({
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required>Email</FormLabel>
|
||||
<FormLabel required>
|
||||
<Trans>Email</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="bg-background"
|
||||
@ -291,7 +294,9 @@ export const AddSignatureFormPartial = ({
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required={requireName}>Name</FormLabel>
|
||||
<FormLabel required={requireName}>
|
||||
<Trans>Name</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="bg-background"
|
||||
@ -314,7 +319,9 @@ export const AddSignatureFormPartial = ({
|
||||
name="signature"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required={requireSignature}>Signature</FormLabel>
|
||||
<FormLabel required={requireSignature}>
|
||||
<Trans>Signature</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Card
|
||||
className={cn('mt-2', {
|
||||
@ -349,7 +356,9 @@ export const AddSignatureFormPartial = ({
|
||||
name="customText"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required={requireCustomText}>Custom Text</FormLabel>
|
||||
<FormLabel required={requireCustomText}>
|
||||
<Trans>Custom Text</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="bg-background"
|
||||
@ -369,11 +378,7 @@ export const AddSignatureFormPartial = ({
|
||||
</DocumentFlowFormContainerContent>
|
||||
|
||||
<DocumentFlowFormContainerFooter>
|
||||
<DocumentFlowFormContainerStep
|
||||
title={documentFlow.title}
|
||||
step={currentStep}
|
||||
maxStep={totalSteps}
|
||||
/>
|
||||
<DocumentFlowFormContainerStep step={currentStep} maxStep={totalSteps} />
|
||||
|
||||
<DocumentFlowFormContainerActions
|
||||
loading={form.formState.isSubmitting}
|
||||
@ -386,7 +391,7 @@ export const AddSignatureFormPartial = ({
|
||||
|
||||
{validateUninsertedFields && uninsertedFields[0] && (
|
||||
<FieldToolTip key={uninsertedFields[0].id} field={uninsertedFields[0]} color="warning">
|
||||
Click to insert field
|
||||
<Trans>Click to insert field</Trans>
|
||||
</FieldToolTip>
|
||||
)}
|
||||
|
||||
|
||||
@ -5,6 +5,8 @@ import React, { useId, useMemo, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Plus, Trash } from 'lucide-react';
|
||||
import { useSession } from 'next-auth/react';
|
||||
@ -63,10 +65,12 @@ export const AddSignersFormPartial = ({
|
||||
isDocumentPdfLoaded,
|
||||
teamId,
|
||||
}: AddSignersFormProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const { remaining } = useLimits();
|
||||
const { data: session } = useSession();
|
||||
const router = useRouter();
|
||||
|
||||
const user = session?.user;
|
||||
|
||||
const initialId = useId();
|
||||
@ -318,13 +322,15 @@ export const AddSignersFormPartial = ({
|
||||
})}
|
||||
>
|
||||
{!showAdvancedSettings && index === 0 && (
|
||||
<FormLabel required>Email</FormLabel>
|
||||
<FormLabel required>
|
||||
<Trans>Email</Trans>
|
||||
</FormLabel>
|
||||
)}
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
placeholder={_(msg`Email`)}
|
||||
{...field}
|
||||
disabled={isSubmitting || hasBeenSentToRecipientId(signer.nativeId)}
|
||||
onKeyDown={onKeyDown}
|
||||
@ -351,7 +357,7 @@ export const AddSignersFormPartial = ({
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Name"
|
||||
placeholder={_(msg`Name`)}
|
||||
{...field}
|
||||
disabled={isSubmitting || hasBeenSentToRecipientId(signer.nativeId)}
|
||||
onKeyDown={onKeyDown}
|
||||
@ -435,7 +441,7 @@ export const AddSignersFormPartial = ({
|
||||
onClick={() => onAddSigner()}
|
||||
>
|
||||
<Plus className="-ml-1 mr-2 h-5 w-5" />
|
||||
Add Signer
|
||||
<Trans>Add Signer</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -446,7 +452,7 @@ export const AddSignersFormPartial = ({
|
||||
onClick={() => onAddSelfSigner()}
|
||||
>
|
||||
<Plus className="-ml-1 mr-2 h-5 w-5" />
|
||||
Add myself
|
||||
<Trans>Add myself</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -464,7 +470,7 @@ export const AddSignersFormPartial = ({
|
||||
className="text-muted-foreground ml-2 text-sm"
|
||||
htmlFor="showAdvancedRecipientSettings"
|
||||
>
|
||||
Show advanced settings
|
||||
<Trans>Show advanced settings</Trans>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
@ -473,11 +479,7 @@ export const AddSignersFormPartial = ({
|
||||
</DocumentFlowFormContainerContent>
|
||||
|
||||
<DocumentFlowFormContainerFooter>
|
||||
<DocumentFlowFormContainerStep
|
||||
title={documentFlow.title}
|
||||
step={currentStep}
|
||||
maxStep={totalSteps}
|
||||
/>
|
||||
<DocumentFlowFormContainerStep step={currentStep} maxStep={totalSteps} />
|
||||
|
||||
<DocumentFlowFormContainerActions
|
||||
loading={isSubmitting}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
import type { Field, Recipient } from '@documenso/prisma/client';
|
||||
@ -74,7 +75,9 @@ export const AddSubjectFormPartial = ({
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<div>
|
||||
<Label htmlFor="subject">
|
||||
Subject <span className="text-muted-foreground">(Optional)</span>
|
||||
<Trans>
|
||||
Subject <span className="text-muted-foreground">(Optional)</span>
|
||||
</Trans>
|
||||
</Label>
|
||||
|
||||
<Input
|
||||
@ -89,7 +92,9 @@ export const AddSubjectFormPartial = ({
|
||||
|
||||
<div>
|
||||
<Label htmlFor="message">
|
||||
Message <span className="text-muted-foreground">(Optional)</span>
|
||||
<Trans>
|
||||
Message <span className="text-muted-foreground">(Optional)</span>
|
||||
</Trans>
|
||||
</Label>
|
||||
|
||||
<Textarea
|
||||
@ -111,16 +116,12 @@ export const AddSubjectFormPartial = ({
|
||||
</DocumentFlowFormContainerContent>
|
||||
|
||||
<DocumentFlowFormContainerFooter>
|
||||
<DocumentFlowFormContainerStep
|
||||
title={documentFlow.title}
|
||||
step={currentStep}
|
||||
maxStep={totalSteps}
|
||||
/>
|
||||
<DocumentFlowFormContainerStep step={currentStep} maxStep={totalSteps} />
|
||||
|
||||
<DocumentFlowFormContainerActions
|
||||
loading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
goNextLabel={document.status === DocumentStatus.DRAFT ? 'Send' : 'Update'}
|
||||
goNextLabel={document.status === DocumentStatus.DRAFT ? msg`Send` : msg`Update`}
|
||||
onGoBackClick={previousStep}
|
||||
onGoNextClick={() => void onFormSubmit()}
|
||||
/>
|
||||
|
||||
@ -3,6 +3,9 @@
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
@ -33,19 +36,21 @@ export const DocumentFlowFormContainer = ({
|
||||
};
|
||||
|
||||
export type DocumentFlowFormContainerHeaderProps = {
|
||||
title: string;
|
||||
description: string;
|
||||
title: MessageDescriptor;
|
||||
description: MessageDescriptor;
|
||||
};
|
||||
|
||||
export const DocumentFlowFormContainerHeader = ({
|
||||
title,
|
||||
description,
|
||||
}: DocumentFlowFormContainerHeaderProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className="text-foreground text-2xl font-semibold">{title}</h3>
|
||||
<h3 className="text-foreground text-2xl font-semibold">{_(title)}</h3>
|
||||
|
||||
<p className="text-muted-foreground mt-2 text-sm">{description}</p>
|
||||
<p className="text-muted-foreground mt-2 text-sm">{_(description)}</p>
|
||||
|
||||
<hr className="border-border mb-8 mt-4" />
|
||||
</>
|
||||
@ -88,7 +93,6 @@ export const DocumentFlowFormContainerFooter = ({
|
||||
};
|
||||
|
||||
export type DocumentFlowFormContainerStepProps = {
|
||||
title: string;
|
||||
step: number;
|
||||
maxStep: number;
|
||||
};
|
||||
@ -100,7 +104,9 @@ export const DocumentFlowFormContainerStep = ({
|
||||
return (
|
||||
<div>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Step <span>{`${step} of ${maxStep}`}</span>
|
||||
<Trans>
|
||||
Step <span>{`${step} of ${maxStep}`}</span>
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<div className="bg-muted relative mt-4 h-[2px] rounded-md">
|
||||
@ -120,8 +126,8 @@ export const DocumentFlowFormContainerStep = ({
|
||||
export type DocumentFlowFormContainerActionsProps = {
|
||||
canGoBack?: boolean;
|
||||
canGoNext?: boolean;
|
||||
goNextLabel?: string;
|
||||
goBackLabel?: string;
|
||||
goNextLabel?: MessageDescriptor;
|
||||
goBackLabel?: MessageDescriptor;
|
||||
onGoBackClick?: () => void;
|
||||
onGoNextClick?: () => void;
|
||||
loading?: boolean;
|
||||
@ -132,14 +138,15 @@ export type DocumentFlowFormContainerActionsProps = {
|
||||
export const DocumentFlowFormContainerActions = ({
|
||||
canGoBack = true,
|
||||
canGoNext = true,
|
||||
goNextLabel = 'Continue',
|
||||
goBackLabel = 'Go Back',
|
||||
goNextLabel = msg`Continue`,
|
||||
goBackLabel = msg`Go Back`,
|
||||
onGoBackClick,
|
||||
onGoNextClick,
|
||||
loading,
|
||||
disabled,
|
||||
disableNextStep = false,
|
||||
}: DocumentFlowFormContainerActionsProps) => {
|
||||
const { _ } = useLingui();
|
||||
return (
|
||||
<div className="mt-4 flex gap-x-4">
|
||||
<Button
|
||||
@ -150,7 +157,7 @@ export const DocumentFlowFormContainerActions = ({
|
||||
disabled={disabled || loading || !canGoBack || !onGoBackClick}
|
||||
onClick={onGoBackClick}
|
||||
>
|
||||
{goBackLabel}
|
||||
{_(goBackLabel)}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -161,7 +168,7 @@ export const DocumentFlowFormContainerActions = ({
|
||||
loading={loading}
|
||||
onClick={onGoNextClick}
|
||||
>
|
||||
{goNextLabel}
|
||||
{_(goNextLabel)}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
import {
|
||||
CalendarDays,
|
||||
CheckSquare,
|
||||
@ -48,7 +49,7 @@ export const FieldIcon = ({
|
||||
fontCaveatClassName,
|
||||
)}
|
||||
>
|
||||
Signature
|
||||
<Trans>Signature</Trans>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
|
||||
@ -5,6 +5,9 @@ import { forwardRef, useEffect, useState } from 'react';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import {
|
||||
@ -37,8 +40,8 @@ import { TextFieldAdvancedSettings } from './field-items-advanced-settings/text-
|
||||
|
||||
export type FieldAdvancedSettingsProps = {
|
||||
teamId?: number;
|
||||
title: string;
|
||||
description: string;
|
||||
title: MessageDescriptor;
|
||||
description: MessageDescriptor;
|
||||
field: FieldFormType;
|
||||
fields: FieldFormType[];
|
||||
onAdvancedSettings?: () => void;
|
||||
@ -121,7 +124,9 @@ export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSet
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const params = useParams();
|
||||
const pathname = usePathname();
|
||||
const id = params?.id;
|
||||
@ -150,17 +155,7 @@ export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSet
|
||||
|
||||
const doesFieldExist = (!!document || !!template) && field.nativeId !== undefined;
|
||||
|
||||
const { data: fieldData } = trpc.field.getField.useQuery(
|
||||
{
|
||||
fieldId: Number(field.nativeId),
|
||||
teamId,
|
||||
},
|
||||
{
|
||||
enabled: doesFieldExist,
|
||||
},
|
||||
);
|
||||
|
||||
const fieldMeta = fieldData?.fieldMeta;
|
||||
const fieldMeta = field?.fieldMeta;
|
||||
|
||||
const localStorageKey = `field_${field.formId}_${field.type}`;
|
||||
|
||||
@ -218,8 +213,8 @@ export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSet
|
||||
console.error('Failed to save to localStorage:', error);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Failed to save settings.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`Failed to save settings.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -288,8 +283,8 @@ export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSet
|
||||
</DocumentFlowFormContainerContent>
|
||||
<DocumentFlowFormContainerFooter className="mt-auto">
|
||||
<DocumentFlowFormContainerActions
|
||||
goNextLabel="Save"
|
||||
goBackLabel="Cancel"
|
||||
goNextLabel={msg`Save`}
|
||||
goBackLabel={msg`Cancel`}
|
||||
onGoBackClick={onAdvancedSettings}
|
||||
onGoNextClick={handleOnGoNextClick}
|
||||
disableNextStep={errors.length > 0}
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { ChevronDown, ChevronUp, Trash } from 'lucide-react';
|
||||
|
||||
import { validateCheckboxField } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
|
||||
@ -35,6 +37,8 @@ export const CheckboxFieldAdvancedSettings = ({
|
||||
handleFieldChange,
|
||||
handleErrors,
|
||||
}: CheckboxFieldAdvancedSettingsProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const [showValidation, setShowValidation] = useState(false);
|
||||
const [values, setValues] = useState(fieldState.values ?? [{ id: 1, checked: false, value: '' }]);
|
||||
const [readOnly, setReadOnly] = useState(fieldState.readOnly ?? false);
|
||||
@ -122,13 +126,15 @@ export const CheckboxFieldAdvancedSettings = ({
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-row items-center gap-x-4">
|
||||
<div className="flex w-2/3 flex-col">
|
||||
<Label>Validation</Label>
|
||||
<Label>
|
||||
<Trans>Validation</Trans>
|
||||
</Label>
|
||||
<Select
|
||||
value={fieldState.validationRule}
|
||||
onValueChange={(val) => handleToggleChange('validationRule', val)}
|
||||
>
|
||||
<SelectTrigger className="text-muted-foreground bg-background mt-2 w-full">
|
||||
<SelectValue placeholder="Select at least" />
|
||||
<SelectValue placeholder={_(msg`Select at least`)} />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="popper">
|
||||
{checkboxValidationRules.map((item, index) => (
|
||||
@ -145,7 +151,7 @@ export const CheckboxFieldAdvancedSettings = ({
|
||||
onValueChange={(val) => handleToggleChange('validationLength', val)}
|
||||
>
|
||||
<SelectTrigger className="text-muted-foreground bg-background mt-2 w-full">
|
||||
<SelectValue placeholder="Pick a number" />
|
||||
<SelectValue placeholder={_(msg`Pick a number`)} />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="popper">
|
||||
{checkboxValidationLength.map((item, index) => (
|
||||
@ -164,7 +170,9 @@ export const CheckboxFieldAdvancedSettings = ({
|
||||
checked={fieldState.required}
|
||||
onCheckedChange={(checked) => handleToggleChange('required', checked)}
|
||||
/>
|
||||
<Label>Required field</Label>
|
||||
<Label>
|
||||
<Trans>Required field</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Switch
|
||||
@ -172,7 +180,9 @@ export const CheckboxFieldAdvancedSettings = ({
|
||||
checked={fieldState.readOnly}
|
||||
onCheckedChange={(checked) => handleToggleChange('readOnly', checked)}
|
||||
/>
|
||||
<Label>Read only</Label>
|
||||
<Label>
|
||||
<Trans>Read only</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
@ -181,7 +191,9 @@ export const CheckboxFieldAdvancedSettings = ({
|
||||
onClick={() => setShowValidation((prev) => !prev)}
|
||||
>
|
||||
<span className="flex w-full flex-row justify-between">
|
||||
<span className="flex items-center">Checkbox values</span>
|
||||
<span className="flex items-center">
|
||||
<Trans>Checkbox values</Trans>
|
||||
</span>
|
||||
{showValidation ? <ChevronUp /> : <ChevronDown />}
|
||||
</span>
|
||||
</Button>
|
||||
@ -215,7 +227,7 @@ export const CheckboxFieldAdvancedSettings = ({
|
||||
variant="outline"
|
||||
onClick={addValue}
|
||||
>
|
||||
Add another value
|
||||
<Trans>Add another value</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { ChevronDown, ChevronUp, Trash } from 'lucide-react';
|
||||
|
||||
import { validateDropdownField } from '@documenso/lib/advanced-fields-validation/validate-dropdown';
|
||||
@ -32,6 +34,8 @@ export const DropdownFieldAdvancedSettings = ({
|
||||
handleFieldChange,
|
||||
handleErrors,
|
||||
}: DropdownFieldAdvancedSettingsProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const [showValidation, setShowValidation] = useState(false);
|
||||
const [values, setValues] = useState(fieldState.values ?? [{ value: 'Option 1' }]);
|
||||
const [readOnly, setReadOnly] = useState(fieldState.readOnly ?? false);
|
||||
@ -87,7 +91,9 @@ export const DropdownFieldAdvancedSettings = ({
|
||||
return (
|
||||
<div className="text-dark flex flex-col gap-4">
|
||||
<div>
|
||||
<Label>Select default option</Label>
|
||||
<Label>
|
||||
<Trans>Select default option</Trans>
|
||||
</Label>
|
||||
<Select
|
||||
defaultValue={defaultValue}
|
||||
onValueChange={(val) => {
|
||||
@ -96,7 +102,7 @@ export const DropdownFieldAdvancedSettings = ({
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="text-muted-foreground bg-background mt-2 w-full">
|
||||
<SelectValue defaultValue={defaultValue} placeholder="-- Select --" />
|
||||
<SelectValue defaultValue={defaultValue} placeholder={`-- ${_(msg`Select`)} --`} />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="popper">
|
||||
{values.map((item, index) => (
|
||||
@ -117,7 +123,9 @@ export const DropdownFieldAdvancedSettings = ({
|
||||
checked={fieldState.required}
|
||||
onCheckedChange={(checked) => handleToggleChange('required', checked)}
|
||||
/>
|
||||
<Label>Required field</Label>
|
||||
<Label>
|
||||
<Trans>Required field</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Switch
|
||||
@ -125,7 +133,9 @@ export const DropdownFieldAdvancedSettings = ({
|
||||
checked={fieldState.readOnly}
|
||||
onCheckedChange={(checked) => handleToggleChange('readOnly', checked)}
|
||||
/>
|
||||
<Label>Read only</Label>
|
||||
<Label>
|
||||
<Trans>Read only</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
@ -134,7 +144,9 @@ export const DropdownFieldAdvancedSettings = ({
|
||||
onClick={() => setShowValidation((prev) => !prev)}
|
||||
>
|
||||
<span className="flex w-full flex-row justify-between">
|
||||
<span className="flex items-center">Dropdown options</span>
|
||||
<span className="flex items-center">
|
||||
<Trans>Dropdown options</Trans>
|
||||
</span>
|
||||
{showValidation ? <ChevronUp /> : <ChevronDown />}
|
||||
</span>
|
||||
</Button>
|
||||
@ -162,7 +174,7 @@ export const DropdownFieldAdvancedSettings = ({
|
||||
variant="outline"
|
||||
onClick={addValue}
|
||||
>
|
||||
Add another option
|
||||
<Trans>Add another option</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||
|
||||
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
|
||||
@ -31,6 +33,8 @@ export const NumberFieldAdvancedSettings = ({
|
||||
handleFieldChange,
|
||||
handleErrors,
|
||||
}: NumberFieldAdvancedSettingsProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const [showValidation, setShowValidation] = useState(false);
|
||||
|
||||
const handleInput = (field: keyof NumberFieldMeta, value: string | boolean) => {
|
||||
@ -56,43 +60,51 @@ export const NumberFieldAdvancedSettings = ({
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<Label>Label</Label>
|
||||
<Label>
|
||||
<Trans>Label</Trans>
|
||||
</Label>
|
||||
<Input
|
||||
id="label"
|
||||
className="bg-background mt-2"
|
||||
placeholder="Label"
|
||||
placeholder={_(msg`Label`)}
|
||||
value={fieldState.label}
|
||||
onChange={(e) => handleFieldChange('label', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="mt-4">Placeholder</Label>
|
||||
<Label className="mt-4">
|
||||
<Trans>Placeholder</Trans>
|
||||
</Label>
|
||||
<Input
|
||||
id="placeholder"
|
||||
className="bg-background mt-2"
|
||||
placeholder="Placeholder"
|
||||
placeholder={_(msg`Placeholder`)}
|
||||
value={fieldState.placeholder}
|
||||
onChange={(e) => handleFieldChange('placeholder', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="mt-4">Value</Label>
|
||||
<Label className="mt-4">
|
||||
<Trans>Value</Trans>
|
||||
</Label>
|
||||
<Input
|
||||
id="value"
|
||||
className="bg-background mt-2"
|
||||
placeholder="Value"
|
||||
placeholder={_(msg`Value`)}
|
||||
value={fieldState.value}
|
||||
onChange={(e) => handleInput('value', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Number format</Label>
|
||||
<Label>
|
||||
<Trans>Number format</Trans>
|
||||
</Label>
|
||||
<Select
|
||||
value={fieldState.numberFormat}
|
||||
onValueChange={(val) => handleInput('numberFormat', val)}
|
||||
>
|
||||
<SelectTrigger className="text-muted-foreground bg-background mt-2 w-full">
|
||||
<SelectValue placeholder="Field format" />
|
||||
<SelectValue placeholder={_(msg`Field format`)} />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="popper">
|
||||
{numberFormatValues.map((item, index) => (
|
||||
@ -110,7 +122,9 @@ export const NumberFieldAdvancedSettings = ({
|
||||
checked={fieldState.required}
|
||||
onCheckedChange={(checked) => handleInput('required', checked)}
|
||||
/>
|
||||
<Label>Required field</Label>
|
||||
<Label>
|
||||
<Trans>Required field</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Switch
|
||||
@ -118,7 +132,9 @@ export const NumberFieldAdvancedSettings = ({
|
||||
checked={fieldState.readOnly}
|
||||
onCheckedChange={(checked) => handleInput('readOnly', checked)}
|
||||
/>
|
||||
<Label>Read only</Label>
|
||||
<Label>
|
||||
<Trans>Read only</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
@ -127,14 +143,18 @@ export const NumberFieldAdvancedSettings = ({
|
||||
onClick={() => setShowValidation((prev) => !prev)}
|
||||
>
|
||||
<span className="flex w-full flex-row justify-between">
|
||||
<span className="flex items-center">Validation</span>
|
||||
<span className="flex items-center">
|
||||
<Trans>Validation</Trans>
|
||||
</span>
|
||||
{showValidation ? <ChevronUp /> : <ChevronDown />}
|
||||
</span>
|
||||
</Button>
|
||||
{showValidation && (
|
||||
<div className="mb-4 flex flex-row gap-x-4">
|
||||
<div className="flex flex-col">
|
||||
<Label className="mt-4">Min</Label>
|
||||
<Label className="mt-4">
|
||||
<Trans>Min</Trans>
|
||||
</Label>
|
||||
<Input
|
||||
id="minValue"
|
||||
className="bg-background mt-2"
|
||||
@ -144,7 +164,9 @@ export const NumberFieldAdvancedSettings = ({
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<Label className="mt-4">Max</Label>
|
||||
<Label className="mt-4">
|
||||
<Trans>Max</Trans>
|
||||
</Label>
|
||||
<Input
|
||||
id="maxValue"
|
||||
className="bg-background mt-2"
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { ChevronDown, ChevronUp, Trash } from 'lucide-react';
|
||||
|
||||
import { validateRadioField } from '@documenso/lib/advanced-fields-validation/validate-radio';
|
||||
@ -107,7 +108,9 @@ export const RadioFieldAdvancedSettings = ({
|
||||
checked={fieldState.required}
|
||||
onCheckedChange={(checked) => handleToggleChange('required', checked)}
|
||||
/>
|
||||
<Label>Required field</Label>
|
||||
<Label>
|
||||
<Trans>Required field</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Switch
|
||||
@ -115,7 +118,9 @@ export const RadioFieldAdvancedSettings = ({
|
||||
checked={fieldState.readOnly}
|
||||
onCheckedChange={(checked) => handleToggleChange('readOnly', checked)}
|
||||
/>
|
||||
<Label>Read only</Label>
|
||||
<Label>
|
||||
<Trans>Read only</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
@ -124,7 +129,9 @@ export const RadioFieldAdvancedSettings = ({
|
||||
onClick={() => setShowValidation((prev) => !prev)}
|
||||
>
|
||||
<span className="flex w-full flex-row justify-between">
|
||||
<span className="flex items-center">Radio values</span>
|
||||
<span className="flex items-center">
|
||||
<Trans>Radio values</Trans>
|
||||
</span>
|
||||
{showValidation ? <ChevronUp /> : <ChevronDown />}
|
||||
</span>
|
||||
</Button>
|
||||
@ -157,7 +164,7 @@ export const RadioFieldAdvancedSettings = ({
|
||||
variant="outline"
|
||||
onClick={addValue}
|
||||
>
|
||||
Add another value
|
||||
<Trans>Add another value</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { validateTextField } from '@documenso/lib/advanced-fields-validation/validate-text';
|
||||
import { type TTextFieldMeta as TextFieldMeta } from '@documenso/lib/types/field-meta';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
@ -16,6 +19,8 @@ export const TextFieldAdvancedSettings = ({
|
||||
handleFieldChange,
|
||||
handleErrors,
|
||||
}: TextFieldAdvancedSettingsProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const handleInput = (field: keyof TextFieldMeta, value: string | boolean) => {
|
||||
const text = field === 'text' ? String(value) : fieldState.text || '';
|
||||
const limit =
|
||||
@ -36,45 +41,53 @@ export const TextFieldAdvancedSettings = ({
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<Label>Label</Label>
|
||||
<Label>
|
||||
<Trans>Label</Trans>
|
||||
</Label>
|
||||
<Input
|
||||
id="label"
|
||||
className="bg-background mt-2"
|
||||
placeholder="Field label"
|
||||
placeholder={_(msg`Field label`)}
|
||||
value={fieldState.label}
|
||||
onChange={(e) => handleFieldChange('label', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="mt-4">Placeholder</Label>
|
||||
<Label className="mt-4">
|
||||
<Trans>Placeholder</Trans>
|
||||
</Label>
|
||||
<Input
|
||||
id="placeholder"
|
||||
className="bg-background mt-2"
|
||||
placeholder="Field placeholder"
|
||||
placeholder={_(msg`Field placeholder`)}
|
||||
value={fieldState.placeholder}
|
||||
onChange={(e) => handleFieldChange('placeholder', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className="mt-4">Add text</Label>
|
||||
<Label className="mt-4">
|
||||
<Trans>Add text</Trans>
|
||||
</Label>
|
||||
<Textarea
|
||||
id="text"
|
||||
className="bg-background mt-2"
|
||||
placeholder="Add text to the field"
|
||||
placeholder={_(msg`Add text to the field`)}
|
||||
value={fieldState.text}
|
||||
onChange={(e) => handleInput('text', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>Character Limit</Label>
|
||||
<Label>
|
||||
<Trans>Character Limit</Trans>
|
||||
</Label>
|
||||
<Input
|
||||
id="characterLimit"
|
||||
type="number"
|
||||
min={0}
|
||||
className="bg-background mt-2"
|
||||
placeholder="Field character limit"
|
||||
placeholder={_(msg`Field character limit`)}
|
||||
value={fieldState.characterLimit}
|
||||
onChange={(e) => handleInput('characterLimit', e.target.value)}
|
||||
/>
|
||||
@ -87,7 +100,9 @@ export const TextFieldAdvancedSettings = ({
|
||||
checked={fieldState.required}
|
||||
onCheckedChange={(checked) => handleInput('required', checked)}
|
||||
/>
|
||||
<Label>Required field</Label>
|
||||
<Label>
|
||||
<Trans>Required field</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Switch
|
||||
@ -95,7 +110,9 @@ export const TextFieldAdvancedSettings = ({
|
||||
checked={fieldState.readOnly}
|
||||
onCheckedChange={(checked) => handleInput('readOnly', checked)}
|
||||
/>
|
||||
<Label>Read only</Label>
|
||||
<Label>
|
||||
<Trans>Read only</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { DialogClose } from '@radix-ui/react-dialog';
|
||||
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@ -25,18 +26,22 @@ export const MissingSignatureFieldDialog = ({
|
||||
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-lg" position="center">
|
||||
<DialogHeader>
|
||||
<DialogTitle>No signature field found</DialogTitle>
|
||||
<DialogTitle>
|
||||
<Trans>No signature field found</Trans>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
<p className="mt-2">
|
||||
Some signers have not been assigned a signature field. Please assign at least 1
|
||||
signature field to each signer before proceeding.
|
||||
<Trans>
|
||||
Some signers have not been assigned a signature field. Please assign at least 1
|
||||
signature field to each signer before proceeding.
|
||||
</Trans>
|
||||
</p>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button type="button" variant="secondary">
|
||||
Close
|
||||
<Trans>Close</Trans>
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { Loader } from 'lucide-react';
|
||||
|
||||
import type { ButtonProps } from '../button';
|
||||
@ -30,16 +31,20 @@ export const SendDocumentActionDialog = ({
|
||||
<DialogTrigger asChild>
|
||||
<Button type="button" className={className}>
|
||||
{loading && <Loader className="text-documenso mr-2 h-5 w-5 animate-spin" />}
|
||||
Send
|
||||
<Trans>Send</Trans>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-center text-lg font-semibold">Send Document</DialogTitle>
|
||||
<DialogTitle className="text-center text-lg font-semibold">
|
||||
<Trans>Send Document</Trans>
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-center text-base">
|
||||
You are about to send this document to the recipients. Are you sure you want to
|
||||
continue?
|
||||
<Trans>
|
||||
You are about to send this document to the recipients. Are you sure you want to
|
||||
continue?
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@ -50,13 +55,13 @@ export const SendDocumentActionDialog = ({
|
||||
variant="secondary"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
{/* We would use DialogAction here but it interrupts the submit action */}
|
||||
<Button className={className} {...props}>
|
||||
{loading && <Loader className="mr-2 h-5 w-5 animate-spin" />}
|
||||
Send
|
||||
<Trans>Send</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
@ -58,8 +59,8 @@ export const FRIENDLY_FIELD_TYPE: Record<FieldType, string> = {
|
||||
};
|
||||
|
||||
export interface DocumentFlowStep {
|
||||
title: string;
|
||||
description: string;
|
||||
title: MessageDescriptor;
|
||||
description: MessageDescriptor;
|
||||
stepIndex?: number;
|
||||
onBackStep?: () => unknown;
|
||||
onNextStep?: () => unknown;
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
@ -30,6 +32,8 @@ export const PasswordDialog = ({
|
||||
onPasswordSubmit,
|
||||
isError,
|
||||
}: PasswordDialogProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const form = useForm<TPasswordDialogFormSchema>({
|
||||
defaultValues: {
|
||||
password: defaultPassword ?? '',
|
||||
@ -45,7 +49,7 @@ export const PasswordDialog = ({
|
||||
if (isError) {
|
||||
form.setError('password', {
|
||||
type: 'manual',
|
||||
message: 'The password you have entered is incorrect. Please try again.',
|
||||
message: _(msg`The password you have entered is incorrect. Please try again.`),
|
||||
});
|
||||
}
|
||||
}, [form, isError]);
|
||||
@ -54,10 +58,14 @@ export const PasswordDialog = ({
|
||||
<Dialog open={open}>
|
||||
<DialogContent className="w-full max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Password Required</DialogTitle>
|
||||
<DialogTitle>
|
||||
<Trans>Password Required</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription className="text-muted-foreground">
|
||||
This document is password protected. Please enter the password to view the document.
|
||||
<Trans>
|
||||
This document is password protected. Please enter the password to view the document.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@ -73,7 +81,7 @@ export const PasswordDialog = ({
|
||||
<Input
|
||||
type="password"
|
||||
className="bg-background"
|
||||
placeholder="Enter password"
|
||||
placeholder={_(msg`Enter password`)}
|
||||
autoComplete="off"
|
||||
{...field}
|
||||
/>
|
||||
@ -85,7 +93,9 @@ export const PasswordDialog = ({
|
||||
/>
|
||||
|
||||
<div>
|
||||
<Button>Submit</Button>
|
||||
<Button>
|
||||
<Trans>Submit</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
import { Check, ChevronsUpDown, Loader, XIcon } from 'lucide-react';
|
||||
|
||||
@ -24,7 +27,7 @@ type MultiSelectComboboxProps<T = OptionValue> = {
|
||||
emptySelectionPlaceholder?: React.ReactNode | string;
|
||||
enableClearAllButton?: boolean;
|
||||
loading?: boolean;
|
||||
inputPlaceholder?: string;
|
||||
inputPlaceholder?: MessageDescriptor;
|
||||
onChange: (_values: T[]) => void;
|
||||
options: ComboBoxOption<T>[];
|
||||
selectedValues: T[];
|
||||
@ -46,6 +49,8 @@ export function MultiSelectCombobox<T = OptionValue>({
|
||||
options,
|
||||
selectedValues,
|
||||
}: MultiSelectComboboxProps<T>) {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
const handleSelect = (selectedOption: T) => {
|
||||
@ -143,8 +148,10 @@ export function MultiSelectCombobox<T = OptionValue>({
|
||||
|
||||
<PopoverContent className="w-[200px] p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder={inputPlaceholder} />
|
||||
<CommandEmpty>No value found.</CommandEmpty>
|
||||
<CommandInput placeholder={inputPlaceholder && _(inputPlaceholder)} />
|
||||
<CommandEmpty>
|
||||
<Trans>No value found.</Trans>
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{options.map((option, i) => (
|
||||
<CommandItem key={i} onSelect={() => handleSelect(option.value)}>
|
||||
|
||||
@ -3,11 +3,19 @@
|
||||
import type { HTMLAttributes, MouseEvent, PointerEvent, TouchEvent } from 'react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { Undo2 } from 'lucide-react';
|
||||
import type { StrokeOptions } from 'perfect-freehand';
|
||||
import { getStroke } from 'perfect-freehand';
|
||||
|
||||
import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
import { getSvgPathFromStroke } from './helper';
|
||||
@ -35,6 +43,7 @@ export const SignaturePad = ({
|
||||
const [isPressed, setIsPressed] = useState(false);
|
||||
const [lines, setLines] = useState<Point[][]>([]);
|
||||
const [currentLine, setCurrentLine] = useState<Point[]>([]);
|
||||
const [selectedColor, setSelectedColor] = useState('black');
|
||||
|
||||
const perfectFreehandOptions = useMemo(() => {
|
||||
const size = $el.current ? Math.min($el.current.height, $el.current.width) * 0.03 : 10;
|
||||
@ -84,6 +93,7 @@ export const SignaturePad = ({
|
||||
ctx.restore();
|
||||
ctx.imageSmoothingEnabled = true;
|
||||
ctx.imageSmoothingQuality = 'high';
|
||||
ctx.fillStyle = selectedColor;
|
||||
|
||||
lines.forEach((line) => {
|
||||
const pathData = new Path2D(
|
||||
@ -128,6 +138,7 @@ export const SignaturePad = ({
|
||||
|
||||
ctx.imageSmoothingEnabled = true;
|
||||
ctx.imageSmoothingQuality = 'high';
|
||||
ctx.fillStyle = selectedColor;
|
||||
|
||||
newLines.forEach((line) => {
|
||||
const pathData = new Path2D(
|
||||
@ -236,7 +247,13 @@ export const SignaturePad = ({
|
||||
>
|
||||
<canvas
|
||||
ref={$el}
|
||||
className={cn('relative block dark:invert', className)}
|
||||
className={cn(
|
||||
'relative block',
|
||||
{
|
||||
'dark:hue-rotate-180 dark:invert': selectedColor === 'black',
|
||||
},
|
||||
className,
|
||||
)}
|
||||
style={{ touchAction: 'none' }}
|
||||
onPointerMove={(event) => onMouseMove(event)}
|
||||
onPointerDown={(event) => onMouseDown(event)}
|
||||
@ -246,13 +263,51 @@ export const SignaturePad = ({
|
||||
{...props}
|
||||
/>
|
||||
|
||||
<div className="text-foreground absolute right-2 top-2 filter">
|
||||
<Select defaultValue={selectedColor} onValueChange={(value) => setSelectedColor(value)}>
|
||||
<SelectTrigger className="h-auto w-auto border-none p-1">
|
||||
<SelectValue placeholder="" />
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent className="w-[150px]" align="end">
|
||||
<SelectItem value="black">
|
||||
<div className="text-muted-foreground flex items-center px-1 text-sm">
|
||||
<div className="border-border mr-2 h-5 w-5 rounded-full border-2 bg-black shadow-sm" />
|
||||
<Trans>Black</Trans>
|
||||
</div>
|
||||
</SelectItem>
|
||||
|
||||
<SelectItem value="red">
|
||||
<div className="text-muted-foreground flex items-center px-1 text-sm">
|
||||
<div className="border-border mr-2 h-5 w-5 rounded-full border-2 bg-[red] shadow-sm" />
|
||||
<Trans>Red</Trans>
|
||||
</div>
|
||||
</SelectItem>
|
||||
|
||||
<SelectItem value="blue">
|
||||
<div className="text-muted-foreground flex items-center px-1 text-sm">
|
||||
<div className="border-border mr-2 h-5 w-5 rounded-full border-2 bg-[blue] shadow-sm" />
|
||||
<Trans>Blue</Trans>
|
||||
</div>
|
||||
</SelectItem>
|
||||
|
||||
<SelectItem value="green">
|
||||
<div className="text-muted-foreground flex items-center px-1 text-sm">
|
||||
<div className="border-border mr-2 h-5 w-5 rounded-full border-2 bg-[green] shadow-sm" />
|
||||
<Trans>Green</Trans>
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-4 right-4 flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="focus-visible:ring-ring ring-offset-background text-muted-foreground/60 hover:text-muted-foreground rounded-full p-0 text-xs focus-visible:outline-none focus-visible:ring-2"
|
||||
onClick={() => onClearClick()}
|
||||
>
|
||||
Clear Signature
|
||||
<Trans>Clear Signature</Trans>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { Caveat } from 'next/font/google';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import {
|
||||
CalendarDays,
|
||||
CheckSquare,
|
||||
@ -21,7 +22,7 @@ import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||
import { useDocumentElement } from '@documenso/lib/client-only/hooks/use-document-element';
|
||||
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||
import { RECIPIENT_ROLES_DESCRIPTION_ENG } from '@documenso/lib/constants/recipient-roles';
|
||||
import {
|
||||
type TFieldMetaSchema as FieldMeta,
|
||||
ZFieldMetaSchema,
|
||||
@ -111,6 +112,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
recipients.find((recipient) => recipient.id === field.recipientId)?.email ?? '',
|
||||
signerToken:
|
||||
recipients.find((recipient) => recipient.id === field.recipientId)?.token ?? '',
|
||||
fieldMeta: field.fieldMeta ? ZFieldMetaSchema.parse(field.fieldMeta) : undefined,
|
||||
})),
|
||||
},
|
||||
});
|
||||
@ -155,6 +157,38 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
selectedSignerIndex === -1 ? 0 : selectedSignerIndex,
|
||||
);
|
||||
|
||||
const filterFieldsWithEmptyValues = (fields: typeof localFields, fieldType: string) =>
|
||||
fields
|
||||
.filter((field) => field.type === fieldType)
|
||||
.filter((field) => {
|
||||
if (field.fieldMeta && 'values' in field.fieldMeta) {
|
||||
return field.fieldMeta.values?.length === 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const emptyCheckboxFields = useMemo(
|
||||
() => filterFieldsWithEmptyValues(localFields, FieldType.CHECKBOX),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[localFields],
|
||||
);
|
||||
|
||||
const emptyRadioFields = useMemo(
|
||||
() => filterFieldsWithEmptyValues(localFields, FieldType.RADIO),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[localFields],
|
||||
);
|
||||
|
||||
const emptySelectFields = useMemo(
|
||||
() => filterFieldsWithEmptyValues(localFields, FieldType.DROPDOWN),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[localFields],
|
||||
);
|
||||
|
||||
const hasErrors =
|
||||
emptyCheckboxFields.length > 0 || emptyRadioFields.length > 0 || emptySelectFields.length > 0;
|
||||
|
||||
const [isFieldWithinBounds, setIsFieldWithinBounds] = useState(false);
|
||||
const [coords, setCoords] = useState({
|
||||
x: 0,
|
||||
@ -365,8 +399,8 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<>
|
||||
{showAdvancedSettings && currentField ? (
|
||||
<FieldAdvancedSettings
|
||||
title="Advanced settings"
|
||||
description={`Configure the ${FRIENDLY_FIELD_TYPE[currentField.type]} field`}
|
||||
title={msg`Advanced settings`}
|
||||
description={msg`Configure the ${FRIENDLY_FIELD_TYPE[currentField.type]} field`}
|
||||
field={currentField}
|
||||
fields={localFields}
|
||||
onAdvancedSettings={handleAdvancedSettings}
|
||||
@ -460,14 +494,15 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
|
||||
<CommandEmpty>
|
||||
<span className="text-muted-foreground inline-block px-4">
|
||||
No recipient matching this description was found.
|
||||
<Trans>No recipient matching this description was found.</Trans>
|
||||
</span>
|
||||
</CommandEmpty>
|
||||
|
||||
{recipientsByRoleToDisplay.map(([role, roleRecipients], roleIndex) => (
|
||||
<CommandGroup key={roleIndex}>
|
||||
<div className="text-muted-foreground mb-1 ml-2 mt-2 text-xs font-medium">
|
||||
{`${RECIPIENT_ROLES_DESCRIPTION[role].roleName}s`}
|
||||
{/* Todo: Translations - Add plural translations. */}
|
||||
{`${RECIPIENT_ROLES_DESCRIPTION_ENG[role].roleName}s`}
|
||||
</div>
|
||||
|
||||
{roleRecipients.length === 0 && (
|
||||
@ -475,7 +510,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
key={`${role}-empty`}
|
||||
className="text-muted-foreground/80 px-4 pb-4 pt-2.5 text-center text-xs"
|
||||
>
|
||||
No recipients with this role
|
||||
<Trans>No recipients with this role</Trans>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -542,7 +577,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
fontCaveat.className,
|
||||
)}
|
||||
>
|
||||
Signature
|
||||
<Trans>Signature</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -594,7 +629,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<Mail className="h-4 w-4" />
|
||||
Email
|
||||
<Trans>Email</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -620,7 +655,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<User className="h-4 w-4" />
|
||||
Name
|
||||
<Trans>Name</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -646,7 +681,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<CalendarDays className="h-4 w-4" />
|
||||
Date
|
||||
<Trans>Date</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -672,7 +707,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<Type className="h-4 w-4" />
|
||||
Text
|
||||
<Trans>Text</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -698,7 +733,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<Hash className="h-4 w-4" />
|
||||
Number
|
||||
<Trans>Number</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -724,7 +759,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<Disc className="h-4 w-4" />
|
||||
Radio
|
||||
<Trans>Radio</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -750,7 +785,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<CheckSquare className="h-4 w-4" />
|
||||
Checkbox
|
||||
<Trans>Checkbox</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -776,7 +811,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
Dropdown
|
||||
<Trans>Dropdown</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -786,17 +821,32 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
</div>
|
||||
</DocumentFlowFormContainerContent>
|
||||
|
||||
{hasErrors && (
|
||||
<div className="mt-4">
|
||||
<ul>
|
||||
<li className="text-sm text-red-500">
|
||||
<Trans>
|
||||
To proceed further, please set at least one value for the{' '}
|
||||
{emptyCheckboxFields.length > 0
|
||||
? 'Checkbox'
|
||||
: emptyRadioFields.length > 0
|
||||
? 'Radio'
|
||||
: 'Select'}{' '}
|
||||
field.
|
||||
</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DocumentFlowFormContainerFooter>
|
||||
<DocumentFlowFormContainerStep
|
||||
title={documentFlow.title}
|
||||
step={currentStep}
|
||||
maxStep={totalSteps}
|
||||
/>
|
||||
<DocumentFlowFormContainerStep step={currentStep} maxStep={totalSteps} />
|
||||
|
||||
<DocumentFlowFormContainerActions
|
||||
loading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
goNextLabel="Save Template"
|
||||
goNextLabel={msg`Save Template`}
|
||||
disableNextStep={hasErrors}
|
||||
onGoBackClick={() => {
|
||||
previousStep();
|
||||
remove();
|
||||
|
||||
@ -5,6 +5,8 @@ import React, { useEffect, useId, useMemo, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Link2Icon, Plus, Trash } from 'lucide-react';
|
||||
import { useSession } from 'next-auth/react';
|
||||
@ -67,6 +69,8 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
const initialId = useId();
|
||||
|
||||
const { _ } = useLingui();
|
||||
const { data: session } = useSession();
|
||||
|
||||
const user = session?.user;
|
||||
@ -290,13 +294,15 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
})}
|
||||
>
|
||||
{!showAdvancedSettings && index === 0 && (
|
||||
<FormLabel required>Email</FormLabel>
|
||||
<FormLabel required>
|
||||
<Trans>Email</Trans>
|
||||
</FormLabel>
|
||||
)}
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
placeholder={_(msg`Email`)}
|
||||
{...field}
|
||||
disabled={field.disabled || isSubmitting}
|
||||
onBlur={() => void handleOnBlur(index)}
|
||||
@ -318,11 +324,15 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
'col-span-4': showAdvancedSettings,
|
||||
})}
|
||||
>
|
||||
{!showAdvancedSettings && index === 0 && <FormLabel>Name</FormLabel>}
|
||||
{!showAdvancedSettings && index === 0 && (
|
||||
<FormLabel>
|
||||
<Trans>Name</Trans>
|
||||
</FormLabel>
|
||||
)}
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Name"
|
||||
placeholder={_(msg`Name`)}
|
||||
{...field}
|
||||
disabled={field.disabled || isSubmitting}
|
||||
onBlur={() => void handleOnBlur(index)}
|
||||
@ -382,12 +392,14 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="text-foreground z-9999 max-w-md p-4">
|
||||
<h3 className="text-foreground text-lg font-semibold">
|
||||
Direct link receiver
|
||||
<Trans>Direct link receiver</Trans>
|
||||
</h3>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
This field cannot be modified or deleted. When you share this template's
|
||||
direct link or add it to your public profile, anyone who accesses it can
|
||||
input their name and email, and fill in the fields assigned to them.
|
||||
<Trans>
|
||||
This field cannot be modified or deleted. When you share this template's
|
||||
direct link or add it to your public profile, anyone who accesses it can
|
||||
input their name and email, and fill in the fields assigned to them.
|
||||
</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@ -423,7 +435,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
onClick={() => onAddPlaceholderRecipient()}
|
||||
>
|
||||
<Plus className="-ml-1 mr-2 h-5 w-5" />
|
||||
Add Placeholder Recipient
|
||||
<Trans>Add Placeholder Recipient</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -437,7 +449,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
onClick={() => onAddPlaceholderSelfRecipient()}
|
||||
>
|
||||
<Plus className="-ml-1 mr-2 h-5 w-5" />
|
||||
Add Myself
|
||||
<Trans>Add Myself</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -455,7 +467,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
className="text-muted-foreground ml-2 text-sm"
|
||||
htmlFor="showAdvancedRecipientSettings"
|
||||
>
|
||||
Show advanced settings
|
||||
<Trans>Show advanced settings</Trans>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
@ -464,11 +476,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
</DocumentFlowFormContainerContent>
|
||||
|
||||
<DocumentFlowFormContainerFooter>
|
||||
<DocumentFlowFormContainerStep
|
||||
title={documentFlow.title}
|
||||
step={currentStep}
|
||||
maxStep={totalSteps}
|
||||
/>
|
||||
<DocumentFlowFormContainerStep step={currentStep} maxStep={totalSteps} />
|
||||
|
||||
<DocumentFlowFormContainerActions
|
||||
loading={isSubmitting}
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
@ -72,6 +74,8 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
template,
|
||||
onSubmit,
|
||||
}: AddTemplateSettingsFormProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const { documentAuthOption } = extractDocumentAuthMethods({
|
||||
documentAuth: template.authOptions,
|
||||
});
|
||||
@ -126,7 +130,9 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required>Template title</FormLabel>
|
||||
<FormLabel required>
|
||||
<Trans>Template title</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Input className="bg-background" {...field} />
|
||||
@ -142,7 +148,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
Document access
|
||||
<Trans>Document access</Trans>
|
||||
<DocumentGlobalAuthAccessTooltip />
|
||||
</FormLabel>
|
||||
|
||||
@ -160,7 +166,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
Recipient action authentication
|
||||
<Trans>Recipient action authentication</Trans>
|
||||
<DocumentGlobalAuthActionTooltip />
|
||||
</FormLabel>
|
||||
|
||||
@ -175,7 +181,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
<Accordion type="multiple">
|
||||
<AccordionItem value="email-options" className="border-none">
|
||||
<AccordionTrigger className="text-foreground rounded border px-3 py-2 text-left hover:bg-neutral-200/30 hover:no-underline">
|
||||
Email Options
|
||||
<Trans>Email Options</Trans>
|
||||
</AccordionTrigger>
|
||||
|
||||
<AccordionContent className="text-muted-foreground -mx-1 px-1 pt-4 text-sm leading-relaxed [&>div]:pb-0">
|
||||
@ -186,7 +192,9 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Subject <span className="text-muted-foreground">(Optional)</span>
|
||||
<Trans>
|
||||
Subject <span className="text-muted-foreground">(Optional)</span>
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
@ -204,7 +212,9 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Message <span className="text-muted-foreground">(Optional)</span>
|
||||
<Trans>
|
||||
Message <span className="text-muted-foreground">(Optional)</span>
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
@ -225,7 +235,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
<Accordion type="multiple">
|
||||
<AccordionItem value="advanced-options" className="border-none">
|
||||
<AccordionTrigger className="text-foreground rounded border px-3 py-2 text-left hover:bg-neutral-200/30 hover:no-underline">
|
||||
Advanced Options
|
||||
<Trans>Advanced Options</Trans>
|
||||
</AccordionTrigger>
|
||||
|
||||
<AccordionContent className="text-muted-foreground -mx-1 px-1 pt-4 text-sm leading-relaxed">
|
||||
@ -236,15 +246,17 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
External ID{' '}
|
||||
<Trans>External ID</Trans>{' '}
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-muted-foreground max-w-xs">
|
||||
Add an external ID to the template. This can be used to identify in
|
||||
external systems.
|
||||
<Trans>
|
||||
Add an external ID to the template. This can be used to identify
|
||||
in external systems.
|
||||
</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormLabel>
|
||||
@ -263,7 +275,9 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
name="meta.dateFormat"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Date Format</FormLabel>
|
||||
<FormLabel>
|
||||
<Trans>Date Format</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Select {...field} onValueChange={field.onChange}>
|
||||
@ -291,7 +305,9 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
name="meta.timezone"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Time Zone</FormLabel>
|
||||
<FormLabel>
|
||||
<Trans>Time Zone</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Combobox
|
||||
@ -313,14 +329,16 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
Redirect URL{' '}
|
||||
<Trans>Redirect URL</Trans>{' '}
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-muted-foreground max-w-xs">
|
||||
Add a URL to redirect the user to once the document is signed
|
||||
<Trans>
|
||||
Add a URL to redirect the user to once the document is signed
|
||||
</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormLabel>
|
||||
@ -342,11 +360,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
</DocumentFlowFormContainerContent>
|
||||
|
||||
<DocumentFlowFormContainerFooter>
|
||||
<DocumentFlowFormContainerStep
|
||||
title={documentFlow.title}
|
||||
step={currentStep}
|
||||
maxStep={totalSteps}
|
||||
/>
|
||||
<DocumentFlowFormContainerStep step={currentStep} maxStep={totalSteps} />
|
||||
|
||||
<DocumentFlowFormContainerActions
|
||||
loading={form.formState.isSubmitting}
|
||||
|
||||
Reference in New Issue
Block a user