mirror of
https://github.com/documenso/documenso.git
synced 2026-07-26 09:54:51 +10:00
chore: merge main, resolve biome formatting conflicts
Merge origin/main into feat/external-2fa-codes. Resolve formatting conflicts caused by biome rollout; preserve both feature streams: PR's external 2FA token + signing-session 2FA proof additions plus main's RateLimit/RecipientExpired/signingReminders/date-auto-insert. In complete-document-with-token.ts, drop the duplicate early field-fetching block introduced when main moved that logic later with date auto-insert support; keep the EXTERNAL_TWO_FACTOR_AUTH check using derivedRecipientActionAuth.
This commit is contained in:
+20
-27
@@ -1,9 +1,3 @@
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { DocumentDistributionMethod } from '@prisma/client';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
import type { Control } from 'react-hook-form';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
|
||||
import { DATE_FORMATS } from '@documenso/lib/constants/date-formats';
|
||||
import { DOCUMENT_SIGNATURE_TYPES } from '@documenso/lib/constants/document';
|
||||
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
|
||||
@@ -21,16 +15,17 @@ import {
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { MultiSelectCombobox } from '@documenso/ui/primitives/multi-select-combobox';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
|
||||
import { Textarea } from '@documenso/ui/primitives/textarea';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { DocumentDistributionMethod } from '@prisma/client';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
import type { Control } from 'react-hook-form';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
|
||||
import { useConfigureDocument } from './configure-document-context';
|
||||
import type { TConfigureEmbedFormSchema } from './configure-document-view.types';
|
||||
@@ -44,7 +39,7 @@ export const ConfigureDocumentAdvancedSettings = ({
|
||||
control,
|
||||
isSubmitting,
|
||||
}: ConfigureDocumentAdvancedSettingsProps) => {
|
||||
const { t } = useLingui();
|
||||
const { _ } = useLingui();
|
||||
|
||||
const form = useFormContext<TConfigureEmbedFormSchema>();
|
||||
const { features } = useConfigureDocument();
|
||||
@@ -58,11 +53,11 @@ export const ConfigureDocumentAdvancedSettings = ({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 className="text-foreground mb-1 text-lg font-medium">
|
||||
<h3 className="mb-1 font-medium text-foreground text-lg">
|
||||
<Trans>Advanced Settings</Trans>
|
||||
</h3>
|
||||
|
||||
<p className="text-muted-foreground mb-6 text-sm">
|
||||
<p className="mb-6 text-muted-foreground text-sm">
|
||||
<Trans>Configure additional options and preferences</Trans>
|
||||
</p>
|
||||
|
||||
@@ -93,12 +88,12 @@ export const ConfigureDocumentAdvancedSettings = ({
|
||||
<FormControl>
|
||||
<MultiSelectCombobox
|
||||
options={Object.values(DOCUMENT_SIGNATURE_TYPES).map((option) => ({
|
||||
label: t(option.label),
|
||||
label: _(option.label),
|
||||
value: option.value,
|
||||
}))}
|
||||
selectedValues={field.value}
|
||||
onChange={field.onChange}
|
||||
className="bg-background w-full"
|
||||
className="w-full bg-background"
|
||||
emptySelectionPlaceholder={t`Select signature types`}
|
||||
/>
|
||||
</FormControl>
|
||||
@@ -125,7 +120,7 @@ export const ConfigureDocumentAdvancedSettings = ({
|
||||
<SelectContent>
|
||||
{Object.entries(SUPPORTED_LANGUAGES).map(([code, language]) => (
|
||||
<SelectItem key={code} value={code}>
|
||||
{language.full}
|
||||
{_(language.full)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
@@ -202,10 +197,8 @@ export const ConfigureDocumentAdvancedSettings = ({
|
||||
<TooltipTrigger>
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="text-muted-foreground max-w-xs">
|
||||
<Trans>
|
||||
Add a URL to redirect the user to once the document is signed
|
||||
</Trans>
|
||||
<TooltipContent className="max-w-xs text-muted-foreground">
|
||||
<Trans>Add a URL to redirect the user to once the document is signed</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormLabel>
|
||||
@@ -250,8 +243,8 @@ export const ConfigureDocumentAdvancedSettings = ({
|
||||
|
||||
<FormDescription>
|
||||
<Trans>
|
||||
Choose how to distribute your document to recipients. Email will send
|
||||
notifications, None will generate signing links for manual distribution.
|
||||
Choose how to distribute your document to recipients. Email will send notifications, None will
|
||||
generate signing links for manual distribution.
|
||||
</Trans>
|
||||
</FormDescription>
|
||||
|
||||
@@ -277,7 +270,7 @@ export const ConfigureDocumentAdvancedSettings = ({
|
||||
<FormControl>
|
||||
<Input
|
||||
id="subject"
|
||||
className="bg-background mt-2"
|
||||
className="mt-2 bg-background"
|
||||
disabled={isSubmitting || !isEmailDistribution}
|
||||
{...field}
|
||||
/>
|
||||
@@ -300,7 +293,7 @@ export const ConfigureDocumentAdvancedSettings = ({
|
||||
<FormControl>
|
||||
<Textarea
|
||||
id="message"
|
||||
className="bg-background mt-2 h-32 resize-none"
|
||||
className="mt-2 h-32 resize-none bg-background"
|
||||
disabled={isSubmitting || !isEmailDistribution}
|
||||
{...field}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
import { RecipientRoleSelect } from '@documenso/ui/components/recipient/recipient-role-select';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Checkbox } from '@documenso/ui/primitives/checkbox';
|
||||
import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
import type { DropResult, SensorAPI } from '@hello-pangea/dnd';
|
||||
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
@@ -9,23 +14,10 @@ import { DocumentSigningOrder, RecipientRole } from '@prisma/client';
|
||||
import { motion } from 'framer-motion';
|
||||
import { GripVertical, HelpCircle, Plus, Trash } from 'lucide-react';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import type { Control } from 'react-hook-form';
|
||||
import { useFieldArray, useFormContext, useFormState } from 'react-hook-form';
|
||||
|
||||
import { RecipientRoleSelect } from '@documenso/ui/components/recipient/recipient-role-select';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Checkbox } from '@documenso/ui/primitives/checkbox';
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
|
||||
import { useConfigureDocument } from './configure-document-context';
|
||||
import type { TConfigureEmbedFormSchema } from './configure-document-view.types';
|
||||
|
||||
@@ -37,10 +29,7 @@ export interface ConfigureDocumentRecipientsProps {
|
||||
isSubmitting: boolean;
|
||||
}
|
||||
|
||||
export const ConfigureDocumentRecipients = ({
|
||||
control,
|
||||
isSubmitting,
|
||||
}: ConfigureDocumentRecipientsProps) => {
|
||||
export const ConfigureDocumentRecipients = ({ control, isSubmitting }: ConfigureDocumentRecipientsProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { isTemplate } = useConfigureDocument();
|
||||
|
||||
@@ -66,16 +55,14 @@ export const ConfigureDocumentRecipients = ({
|
||||
});
|
||||
|
||||
const onAddSigner = useCallback(() => {
|
||||
const recipientSigningOrder =
|
||||
signers.length > 0 ? (signers[signers.length - 1]?.signingOrder || 0) + 1 : 1;
|
||||
const recipientSigningOrder = signers.length > 0 ? (signers[signers.length - 1]?.signingOrder || 0) + 1 : 1;
|
||||
|
||||
appendSigner({
|
||||
formId: nanoid(8),
|
||||
name: '',
|
||||
email: '',
|
||||
role: RecipientRole.SIGNER,
|
||||
signingOrder:
|
||||
signingOrder === DocumentSigningOrder.SEQUENTIAL ? recipientSigningOrder : undefined,
|
||||
signingOrder: signingOrder === DocumentSigningOrder.SEQUENTIAL ? recipientSigningOrder : undefined,
|
||||
});
|
||||
}, [appendSigner, signers]);
|
||||
|
||||
@@ -116,7 +103,9 @@ export const ConfigureDocumentRecipients = ({
|
||||
|
||||
const onDragEnd = useCallback(
|
||||
(result: DropResult) => {
|
||||
if (!result.destination) return;
|
||||
if (!result.destination) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the move function from useFieldArray which preserves input values
|
||||
move(result.source.index, result.destination.index);
|
||||
@@ -146,11 +135,11 @@ export const ConfigureDocumentRecipients = ({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 className="text-foreground mb-1 text-lg font-medium">
|
||||
<h3 className="mb-1 font-medium text-foreground text-lg">
|
||||
<Trans>Recipients</Trans>
|
||||
</h3>
|
||||
|
||||
<p className="text-muted-foreground mb-6 text-sm">
|
||||
<p className="mb-6 text-muted-foreground text-sm">
|
||||
<Trans>Add signers and configure signing preferences</Trans>
|
||||
</p>
|
||||
|
||||
@@ -165,9 +154,7 @@ export const ConfigureDocumentRecipients = ({
|
||||
id="signingOrder"
|
||||
checked={field.value === DocumentSigningOrder.SEQUENTIAL}
|
||||
onCheckedChange={(checked) =>
|
||||
onSigningOrderChange(
|
||||
checked ? DocumentSigningOrder.SEQUENTIAL : DocumentSigningOrder.PARALLEL,
|
||||
)
|
||||
onSigningOrderChange(checked ? DocumentSigningOrder.SEQUENTIAL : DocumentSigningOrder.PARALLEL)
|
||||
}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
@@ -206,15 +193,15 @@ export const ConfigureDocumentRecipients = ({
|
||||
</FormLabel>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="text-muted-foreground ml-1 cursor-help">
|
||||
<span className="ml-1 cursor-help text-muted-foreground">
|
||||
<HelpCircle className="h-3.5 w-3.5" />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-80 p-4">
|
||||
<p>
|
||||
<Trans>
|
||||
When enabled, signers can choose who should sign next in the sequence instead
|
||||
of following the predefined order.
|
||||
When enabled, signers can choose who should sign next in the sequence instead of following the
|
||||
predefined order.
|
||||
</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
@@ -249,8 +236,7 @@ export const ConfigureDocumentRecipients = ({
|
||||
{...provided.dragHandleProps}
|
||||
disabled={signer.disabled}
|
||||
className={cn('py-1', {
|
||||
'bg-widget-foreground pointer-events-none rounded-md pt-2':
|
||||
snapshot.isDragging,
|
||||
'pointer-events-none rounded-md bg-widget-foreground pt-2': snapshot.isDragging,
|
||||
})}
|
||||
>
|
||||
<motion.div
|
||||
@@ -265,9 +251,7 @@ export const ConfigureDocumentRecipients = ({
|
||||
render={({ field }) => (
|
||||
<FormItem
|
||||
className={cn('flex w-16 flex-none items-center gap-x-1', {
|
||||
'mb-6':
|
||||
errors?.signers?.[index] &&
|
||||
!errors?.signers?.[index]?.signingOrder,
|
||||
'mb-6': errors?.signers?.[index] && !errors?.signers?.[index]?.signingOrder,
|
||||
})}
|
||||
>
|
||||
<GripVertical className="h-5 w-5 flex-shrink-0 opacity-40" />
|
||||
@@ -325,8 +309,7 @@ export const ConfigureDocumentRecipients = ({
|
||||
render={({ field }) => (
|
||||
<FormItem
|
||||
className={cn('flex-1', {
|
||||
'mb-6':
|
||||
errors?.signers?.[index] && !errors?.signers?.[index]?.email,
|
||||
'mb-6': errors?.signers?.[index] && !errors?.signers?.[index]?.email,
|
||||
})}
|
||||
>
|
||||
<FormLabel className="sr-only">
|
||||
@@ -374,12 +357,7 @@ export const ConfigureDocumentRecipients = ({
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
disabled={
|
||||
isSubmitting ||
|
||||
signers.length === 1 ||
|
||||
snapshot.isDragging ||
|
||||
signer.disabled
|
||||
}
|
||||
disabled={isSubmitting || signers.length === 1 || snapshot.isDragging || signer.disabled}
|
||||
onClick={() => removeSigner(index)}
|
||||
>
|
||||
<Trash className="h-4 w-4" />
|
||||
@@ -396,14 +374,8 @@ export const ConfigureDocumentRecipients = ({
|
||||
</DragDropContext>
|
||||
|
||||
<div className="mt-4 flex justify-end">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="w-auto"
|
||||
disabled={isSubmitting}
|
||||
onClick={onAddSigner}
|
||||
>
|
||||
<Plus className="-ml-1 mr-2 h-5 w-5" />
|
||||
<Button type="button" variant="outline" className="w-auto" disabled={isSubmitting} onClick={onAddSigner}>
|
||||
<Plus className="mr-2 -ml-1 h-5 w-5" />
|
||||
<Trans>Add Signer</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
|
||||
import { buildDropzoneRejectionDescription } from '@documenso/ui/lib/handle-dropzone-rejection';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { Cloud, FileText, Loader, X } from 'lucide-react';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
import { useState } from 'react';
|
||||
import { type FileRejection, useDropzone } from 'react-dropzone';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
|
||||
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { useConfigureDocument } from './configure-document-context';
|
||||
import type { TConfigureEmbedFormSchema } from './configure-document-view.types';
|
||||
|
||||
@@ -82,10 +75,10 @@ export const ConfigureDocumentUpload = ({ isSubmitting = false }: ConfigureDocum
|
||||
}
|
||||
};
|
||||
|
||||
const onDropRejected = () => {
|
||||
const onDropRejected = (fileRejections: FileRejection[]) => {
|
||||
toast({
|
||||
title: _(msg`Your document failed to upload.`),
|
||||
description: _(msg`File cannot be larger than ${APP_DOCUMENT_UPLOAD_SIZE_LIMIT}MB`),
|
||||
description: _(buildDropzoneRejectionDescription(fileRejections)),
|
||||
duration: 5000,
|
||||
variant: 'destructive',
|
||||
});
|
||||
@@ -106,11 +99,13 @@ export const ConfigureDocumentUpload = ({ isSubmitting = false }: ConfigureDocum
|
||||
};
|
||||
|
||||
const formatFileSize = (bytes: number) => {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
if (bytes === 0) {
|
||||
return '0 Bytes';
|
||||
}
|
||||
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
return `${parseFloat((bytes / Math.pow(1024, i)).toFixed(2))} ${sizes[i]}`;
|
||||
return `${parseFloat((bytes / 1024 ** i).toFixed(2))} ${sizes[i]}`;
|
||||
};
|
||||
|
||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||
@@ -144,11 +139,10 @@ export const ConfigureDocumentUpload = ({ isSubmitting = false }: ConfigureDocum
|
||||
<div
|
||||
{...getRootProps()}
|
||||
className={cn(
|
||||
'border-border bg-background relative flex min-h-[160px] cursor-pointer flex-col items-center justify-center rounded-lg border border-dashed transition',
|
||||
'relative flex min-h-[160px] cursor-pointer flex-col items-center justify-center rounded-lg border border-border border-dashed bg-background transition',
|
||||
{
|
||||
'border-primary/50 bg-primary/5': isDragActive,
|
||||
'hover:bg-muted/30':
|
||||
!isDragActive && !isSubmitting && !isLoading && !isPersisted,
|
||||
'hover:bg-muted/30': !isDragActive && !isSubmitting && !isLoading && !isPersisted,
|
||||
'cursor-not-allowed opacity-60': isSubmitting || isLoading || isPersisted,
|
||||
},
|
||||
)}
|
||||
@@ -169,7 +163,7 @@ export const ConfigureDocumentUpload = ({ isSubmitting = false }: ConfigureDocum
|
||||
'text-muted-foreground': !isDragActive,
|
||||
})}
|
||||
>
|
||||
<p className="text-sm font-medium">
|
||||
<p className="font-medium text-sm">
|
||||
{isDragActive ? (
|
||||
<Trans>Drop your document here</Trans>
|
||||
) : isPersisted ? (
|
||||
@@ -182,9 +176,7 @@ export const ConfigureDocumentUpload = ({ isSubmitting = false }: ConfigureDocum
|
||||
{isPersisted ? (
|
||||
<Trans>This document cannot be changed</Trans>
|
||||
) : (
|
||||
<Trans>
|
||||
.PDF documents accepted (max {APP_DOCUMENT_UPLOAD_SIZE_LIMIT}MB)
|
||||
</Trans>
|
||||
<Trans>.PDF documents accepted (max {APP_DOCUMENT_UPLOAD_SIZE_LIMIT}MB)</Trans>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -193,23 +185,21 @@ export const ConfigureDocumentUpload = ({ isSubmitting = false }: ConfigureDocum
|
||||
</FormControl>
|
||||
|
||||
{isLoading && (
|
||||
<div className="bg-background/50 absolute inset-0 flex items-center justify-center rounded-lg">
|
||||
<Loader className="text-muted-foreground h-10 w-10 animate-spin" />
|
||||
<div className="absolute inset-0 flex items-center justify-center rounded-lg bg-background/50">
|
||||
<Loader className="h-10 w-10 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-2 rounded-lg border p-4">
|
||||
<div className="flex items-center gap-x-4">
|
||||
<div className="bg-primary/10 text-primary flex h-12 w-12 items-center justify-center rounded-md">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-md bg-primary/10 text-primary">
|
||||
<FileText className="h-6 w-6" />
|
||||
</div>
|
||||
|
||||
<div className="flex-1">
|
||||
<div className="text-sm font-medium">{documentData.name}</div>
|
||||
<div className="text-muted-foreground text-xs">
|
||||
{formatFileSize(documentData.size)}
|
||||
</div>
|
||||
<div className="font-medium text-sm">{documentData.name}</div>
|
||||
<div className="text-muted-foreground text-xs">{formatFileSize(documentData.size)}</div>
|
||||
</div>
|
||||
|
||||
{!isPersisted && (
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { DocumentDistributionMethod, DocumentSigningOrder, RecipientRole } from '@prisma/client';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
|
||||
import { ConfigureDocumentAdvancedSettings } from './configure-document-advanced-settings';
|
||||
import { useConfigureDocument } from './configure-document-context';
|
||||
import { ConfigureDocumentRecipients } from './configure-document-recipients';
|
||||
@@ -45,9 +37,7 @@ export const ConfigureDocumentView = ({
|
||||
const { isTemplate } = useConfigureDocument();
|
||||
|
||||
const form = useForm<TConfigureEmbedFormSchema>({
|
||||
resolver: zodResolver(
|
||||
type === 'template' ? ZConfigureTemplateEmbedFormSchema : ZConfigureEmbedFormSchema,
|
||||
),
|
||||
resolver: zodResolver(type === 'template' ? ZConfigureTemplateEmbedFormSchema : ZConfigureEmbedFormSchema),
|
||||
defaultValues: {
|
||||
title: defaultValues?.title || '',
|
||||
signers: defaultValues?.signers || [
|
||||
@@ -63,8 +53,7 @@ export const ConfigureDocumentView = ({
|
||||
meta: {
|
||||
subject: defaultValues?.meta?.subject || '',
|
||||
message: defaultValues?.meta?.message || '',
|
||||
distributionMethod:
|
||||
defaultValues?.meta?.distributionMethod || DocumentDistributionMethod.EMAIL,
|
||||
distributionMethod: defaultValues?.meta?.distributionMethod || DocumentDistributionMethod.EMAIL,
|
||||
emailSettings: defaultValues?.meta?.emailSettings || ZDocumentEmailSettingsSchema.parse({}),
|
||||
dateFormat: defaultValues?.meta?.dateFormat || DEFAULT_DOCUMENT_DATE_FORMAT,
|
||||
timezone: defaultValues?.meta?.timezone || DEFAULT_DOCUMENT_TIME_ZONE,
|
||||
@@ -88,7 +77,7 @@ export const ConfigureDocumentView = ({
|
||||
return (
|
||||
<div className="flex w-full flex-col space-y-8">
|
||||
<div>
|
||||
<h2 className="text-foreground mb-1 text-xl font-semibold">
|
||||
<h2 className="mb-1 font-semibold text-foreground text-xl">
|
||||
{isTemplate ? <Trans>Configure Template</Trans> : <Trans>Configure Document</Trans>}
|
||||
</h2>
|
||||
|
||||
@@ -126,12 +115,7 @@ export const ConfigureDocumentView = ({
|
||||
<ConfigureDocumentAdvancedSettings control={control} isSubmitting={isSubmitting} />
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={onFormSubmit}
|
||||
disabled={isSubmitting}
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
<Button type="button" onClick={onFormSubmit} disabled={isSubmitting} className="w-full sm:w-auto">
|
||||
<Trans>Continue</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import {
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaLanguageSchema,
|
||||
} from '@documenso/lib/types/document-meta';
|
||||
import { ZDocumentMetaDateFormatSchema, ZDocumentMetaLanguageSchema } from '@documenso/lib/types/document-meta';
|
||||
import { ZRecipientEmailSchema } from '@documenso/lib/types/recipient';
|
||||
import { zEmail } from '@documenso/lib/utils/zod';
|
||||
import { DocumentDistributionMethod } from '@documenso/prisma/generated/types';
|
||||
import { z } from 'zod';
|
||||
|
||||
// Define the schema for configuration
|
||||
export type TConfigureEmbedFormSchema = z.infer<typeof ZConfigureEmbedFormSchema>;
|
||||
@@ -19,7 +16,7 @@ export const ZConfigureEmbedFormSchema = z.object({
|
||||
nativeId: z.number().optional(),
|
||||
formId: z.string(),
|
||||
name: z.string(),
|
||||
email: z.string().email('Invalid email address'),
|
||||
email: zEmail('Invalid email address'),
|
||||
role: z.enum(['SIGNER', 'CC', 'APPROVER', 'VIEWER', 'ASSISTANT']),
|
||||
signingOrder: z.number().optional(),
|
||||
disabled: z.boolean().optional(),
|
||||
|
||||
@@ -1,22 +1,12 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { EnvelopeItem, FieldType } from '@prisma/client';
|
||||
import { ReadStatus, type Recipient, SendStatus, SigningStatus } from '@prisma/client';
|
||||
import { base64 } from '@scure/base';
|
||||
import { ChevronsUpDown } from 'lucide-react';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
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 { getPdfPagesCount, PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
import { type TFieldMetaSchema, ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import type { TRecipientLite } from '@documenso/lib/types/recipient';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { ADVANCED_FIELD_TYPES_WITH_OPTIONAL_SETTING } from '@documenso/lib/utils/advanced-fields-helpers';
|
||||
import { useRecipientColors } from '@documenso/ui/lib/recipient-colors';
|
||||
import { getDocumentDataUrlForPdfViewer } from '@documenso/lib/utils/envelope-download';
|
||||
import { getRecipientColorStyles } from '@documenso/ui/lib/recipient-colors';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { FieldItem } from '@documenso/ui/primitives/document-flow/field-item';
|
||||
@@ -24,14 +14,24 @@ import { FRIENDLY_FIELD_TYPE } from '@documenso/ui/primitives/document-flow/type
|
||||
import { ElementVisible } from '@documenso/ui/primitives/element-visible';
|
||||
import { FieldSelector } from '@documenso/ui/primitives/field-selector';
|
||||
import { Form } from '@documenso/ui/primitives/form/form';
|
||||
import { PDFViewerLazy } from '@documenso/ui/primitives/pdf-viewer/lazy';
|
||||
import { RecipientSelector } from '@documenso/ui/primitives/recipient-selector';
|
||||
import { Sheet, SheetContent, SheetTrigger } from '@documenso/ui/primitives/sheet';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { EnvelopeItem, FieldType } from '@prisma/client';
|
||||
import { ReadStatus, SendStatus, SigningStatus } from '@prisma/client';
|
||||
import { ChevronsUpDown } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
|
||||
import { FieldAdvancedSettingsDrawer } from '~/components/embed/authoring/field-advanced-settings-drawer';
|
||||
import PDFViewerLazy from '~/components/general/pdf-viewer/pdf-viewer-lazy';
|
||||
|
||||
import type { TConfigureEmbedFormSchema } from './configure-document-view.types';
|
||||
import type { TConfigureFieldsFormSchema } from './configure-fields-view.types';
|
||||
import { FieldAdvancedSettingsDrawer } from './field-advanced-settings-drawer';
|
||||
|
||||
const MIN_HEIGHT_PX = 12;
|
||||
const MIN_WIDTH_PX = 36;
|
||||
@@ -42,7 +42,7 @@ const DEFAULT_WIDTH_PX = MIN_WIDTH_PX * 2.5;
|
||||
export type ConfigureFieldsViewProps = {
|
||||
configData: TConfigureEmbedFormSchema;
|
||||
presignToken?: string | undefined;
|
||||
envelopeItem?: Pick<EnvelopeItem, 'id' | 'envelopeId'>;
|
||||
envelopeItem?: Pick<EnvelopeItem, 'id' | 'envelopeId' | 'documentDataId'>;
|
||||
defaultValues?: Partial<TConfigureFieldsFormSchema>;
|
||||
onBack?: (data: TConfigureFieldsFormSchema) => void;
|
||||
onSubmit: (data: TConfigureFieldsFormSchema) => void;
|
||||
@@ -86,26 +86,25 @@ export const ConfigureFieldsView = ({
|
||||
|
||||
const normalizedDocumentData = useMemo(() => {
|
||||
if (envelopeItem) {
|
||||
return undefined;
|
||||
return getDocumentDataUrlForPdfViewer({
|
||||
envelopeId: envelopeItem.envelopeId,
|
||||
envelopeItemId: envelopeItem.id,
|
||||
documentDataId: envelopeItem.documentDataId,
|
||||
version: 'current',
|
||||
token: undefined,
|
||||
presignToken,
|
||||
});
|
||||
}
|
||||
|
||||
if (!configData.documentData) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return base64.encode(configData.documentData.data);
|
||||
}, [configData.documentData]);
|
||||
|
||||
const normalizedEnvelopeItem = useMemo(() => {
|
||||
if (envelopeItem) {
|
||||
return envelopeItem;
|
||||
}
|
||||
|
||||
return { id: '', envelopeId: '' };
|
||||
}, [envelopeItem]);
|
||||
return configData.documentData.data;
|
||||
}, [configData.documentData, envelopeItem, presignToken]);
|
||||
|
||||
const recipients = useMemo(() => {
|
||||
return configData.signers.map<Recipient>((signer, index) => ({
|
||||
return configData.signers.map<TRecipientLite>((signer, index) => ({
|
||||
id: signer.nativeId || index,
|
||||
name: signer.name || '',
|
||||
email: signer.email || '',
|
||||
@@ -115,7 +114,9 @@ export const ConfigureFieldsView = ({
|
||||
templateId: null,
|
||||
token: '',
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
expired: null, // !: deprecated Not in use. To be removed in a future migration.
|
||||
expiresAt: null,
|
||||
expirationNotifiedAt: null,
|
||||
signedAt: null,
|
||||
authOptions: null,
|
||||
rejectionReason: null,
|
||||
@@ -126,7 +127,7 @@ export const ConfigureFieldsView = ({
|
||||
}));
|
||||
}, [configData.signers]);
|
||||
|
||||
const [selectedRecipient, setSelectedRecipient] = useState<Recipient | null>(
|
||||
const [selectedRecipient, setSelectedRecipient] = useState<TRecipientLite | null>(
|
||||
() => recipients.find((r) => r.signingStatus === SigningStatus.NOT_SIGNED) || null,
|
||||
);
|
||||
const [selectedField, setSelectedField] = useState<FieldType | null>(null);
|
||||
@@ -136,16 +137,10 @@ export const ConfigureFieldsView = ({
|
||||
y: 0,
|
||||
});
|
||||
const [activeFieldId, setActiveFieldId] = useState<string | null>(null);
|
||||
const [lastActiveField, setLastActiveField] = useState<
|
||||
TConfigureFieldsFormSchema['fields'][0] | null
|
||||
>(null);
|
||||
const [fieldClipboard, setFieldClipboard] = useState<
|
||||
TConfigureFieldsFormSchema['fields'][0] | null
|
||||
>(null);
|
||||
const [lastActiveField, setLastActiveField] = useState<TConfigureFieldsFormSchema['fields'][0] | null>(null);
|
||||
const [fieldClipboard, setFieldClipboard] = useState<TConfigureFieldsFormSchema['fields'][0] | null>(null);
|
||||
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
|
||||
const [currentField, setCurrentField] = useState<TConfigureFieldsFormSchema['fields'][0] | null>(
|
||||
null,
|
||||
);
|
||||
const [currentField, setCurrentField] = useState<TConfigureFieldsFormSchema['fields'][0] | null>(null);
|
||||
|
||||
const fieldBounds = useRef({
|
||||
height: DEFAULT_HEIGHT_PX,
|
||||
@@ -153,9 +148,7 @@ export const ConfigureFieldsView = ({
|
||||
});
|
||||
|
||||
const selectedRecipientIndex = recipients.findIndex((r) => r.id === selectedRecipient?.id);
|
||||
const selectedRecipientStyles = useRecipientColors(
|
||||
selectedRecipientIndex === -1 ? 0 : selectedRecipientIndex,
|
||||
);
|
||||
const selectedRecipientStyles = getRecipientColorStyles(selectedRecipientIndex);
|
||||
|
||||
const form = useForm<TConfigureFieldsFormSchema>({
|
||||
defaultValues: {
|
||||
@@ -177,8 +170,6 @@ export const ConfigureFieldsView = ({
|
||||
name: 'fields',
|
||||
});
|
||||
|
||||
const highestPageNumber = Math.max(...localFields.map((field) => field.pageNumber));
|
||||
|
||||
const onFieldCopy = useCallback(
|
||||
(event?: KeyboardEvent | null, options?: { duplicate?: boolean; duplicateAll?: boolean }) => {
|
||||
const { duplicate = false, duplicateAll = false } = options ?? {};
|
||||
@@ -203,13 +194,15 @@ export const ConfigureFieldsView = ({
|
||||
}
|
||||
|
||||
if (duplicateAll) {
|
||||
const pages = Array.from(document.querySelectorAll(PDF_VIEWER_PAGE_SELECTOR));
|
||||
const totalPages = getPdfPagesCount();
|
||||
|
||||
pages.forEach((_, index) => {
|
||||
const pageNumber = index + 1;
|
||||
if (totalPages < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let pageNumber = 1; pageNumber <= totalPages; pageNumber += 1) {
|
||||
if (pageNumber === lastActiveField.pageNumber) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
const newField: TConfigureFieldsFormSchema['fields'][0] = {
|
||||
@@ -222,7 +215,7 @@ export const ConfigureFieldsView = ({
|
||||
};
|
||||
|
||||
append(newField);
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -265,15 +258,12 @@ export const ConfigureFieldsView = ({
|
||||
|
||||
const onMouseMove = useCallback(
|
||||
(event: MouseEvent) => {
|
||||
if (!selectedField) return;
|
||||
if (!selectedField) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsFieldWithinBounds(
|
||||
isWithinPageBounds(
|
||||
event,
|
||||
PDF_VIEWER_PAGE_SELECTOR,
|
||||
fieldBounds.current.width,
|
||||
fieldBounds.current.height,
|
||||
),
|
||||
isWithinPageBounds(event, PDF_VIEWER_PAGE_SELECTOR, fieldBounds.current.width, fieldBounds.current.height),
|
||||
);
|
||||
|
||||
setCoords({
|
||||
@@ -294,12 +284,7 @@ export const ConfigureFieldsView = ({
|
||||
|
||||
if (
|
||||
!$page ||
|
||||
!isWithinPageBounds(
|
||||
event,
|
||||
PDF_VIEWER_PAGE_SELECTOR,
|
||||
fieldBounds.current.width,
|
||||
fieldBounds.current.height,
|
||||
)
|
||||
!isWithinPageBounds(event, PDF_VIEWER_PAGE_SELECTOR, fieldBounds.current.width, fieldBounds.current.height)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -358,12 +343,7 @@ export const ConfigureFieldsView = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
x: pageX,
|
||||
y: pageY,
|
||||
width: pageWidth,
|
||||
height: pageHeight,
|
||||
} = getFieldPosition($page, node);
|
||||
const { x: pageX, y: pageY, width: pageWidth, height: pageHeight } = getFieldPosition($page, node);
|
||||
|
||||
update(index, {
|
||||
...field,
|
||||
@@ -465,11 +445,11 @@ export const ConfigureFieldsView = ({
|
||||
{!isMobile && (
|
||||
<div className="order-2 col-span-12 md:order-1 md:col-span-4">
|
||||
<div className="sticky top-4 max-h-[calc(100vh-2rem)] rounded-lg border border-border bg-widget p-4 pb-6">
|
||||
<h2 className="mb-1 text-lg font-medium">
|
||||
<h2 className="mb-1 font-medium text-lg">
|
||||
<Trans>Configure Fields</Trans>
|
||||
</h2>
|
||||
|
||||
<p className="mb-6 text-sm text-muted-foreground">
|
||||
<p className="mb-6 text-muted-foreground text-sm">
|
||||
<Trans>Configure the fields you want to place on the document.</Trans>
|
||||
</p>
|
||||
|
||||
@@ -523,7 +503,7 @@ export const ConfigureFieldsView = ({
|
||||
{selectedField && (
|
||||
<div
|
||||
className={cn(
|
||||
'dark:text-muted-background pointer-events-none fixed z-50 flex cursor-pointer flex-col items-center justify-center bg-white text-muted-foreground transition duration-200 [container-type:size]',
|
||||
'pointer-events-none fixed z-50 flex cursor-pointer flex-col items-center justify-center bg-white text-muted-foreground transition duration-200 [container-type:size] dark:text-muted-background',
|
||||
selectedRecipientStyles.base,
|
||||
{
|
||||
'-rotate-6 scale-90 opacity-50 dark:bg-black/20': !isFieldWithinBounds,
|
||||
@@ -538,25 +518,15 @@ export const ConfigureFieldsView = ({
|
||||
width: fieldBounds.current.width,
|
||||
}}
|
||||
>
|
||||
<span className="text-[clamp(0.425rem,25cqw,0.825rem)]">
|
||||
{_(FRIENDLY_FIELD_TYPE[selectedField])}
|
||||
</span>
|
||||
<span className="text-[clamp(0.425rem,25cqw,0.825rem)]">{_(FRIENDLY_FIELD_TYPE[selectedField])}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Form {...form}>
|
||||
<div>
|
||||
<PDFViewerLazy
|
||||
presignToken={presignToken}
|
||||
overrideData={normalizedDocumentData}
|
||||
envelopeItem={normalizedEnvelopeItem}
|
||||
token={undefined}
|
||||
version="signed"
|
||||
/>
|
||||
{normalizedDocumentData && <PDFViewerLazy data={normalizedDocumentData} scrollParentRef="window" />}
|
||||
|
||||
<ElementVisible
|
||||
target={`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${highestPageNumber}"]`}
|
||||
>
|
||||
<ElementVisible target={PDF_VIEWER_PAGE_SELECTOR}>
|
||||
{localFields.map((field, index) => {
|
||||
const recipientIndex = recipients.findIndex((r) => r.id === field.recipientId);
|
||||
|
||||
@@ -598,8 +568,8 @@ export const ConfigureFieldsView = ({
|
||||
{isMobile && (
|
||||
<Sheet open={isDrawerOpen} onOpenChange={setIsDrawerOpen}>
|
||||
<SheetTrigger asChild>
|
||||
<div className="fixed bottom-6 left-6 right-6 z-50 flex items-center justify-between gap-2 rounded-lg border border-border bg-widget p-4">
|
||||
<span className="text-lg font-medium">
|
||||
<div className="fixed right-6 bottom-6 left-6 z-50 flex items-center justify-between gap-2 rounded-lg border border-border bg-widget p-4">
|
||||
<span className="font-medium text-lg">
|
||||
<Trans>Configure Fields</Trans>
|
||||
</span>
|
||||
|
||||
@@ -617,11 +587,11 @@ export const ConfigureFieldsView = ({
|
||||
size="xl"
|
||||
className="h-fit max-h-[80vh] overflow-y-auto rounded-t-xl bg-widget p-4"
|
||||
>
|
||||
<h2 className="mb-1 text-lg font-medium">
|
||||
<h2 className="mb-1 font-medium text-lg">
|
||||
<Trans>Configure Fields</Trans>
|
||||
</h2>
|
||||
|
||||
<p className="mb-6 text-sm text-muted-foreground">
|
||||
<p className="mb-6 text-muted-foreground text-sm">
|
||||
<Trans>Configure the fields you want to place on the document.</Trans>
|
||||
</p>
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import { FieldType } from '@documenso/prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZConfigureFieldsFormSchema = z.object({
|
||||
fields: z.array(
|
||||
@@ -24,6 +23,4 @@ export const ZConfigureFieldsFormSchema = z.object({
|
||||
|
||||
export type TConfigureFieldsFormSchema = z.infer<typeof ZConfigureFieldsFormSchema>;
|
||||
|
||||
export type TConfigureFieldsFormSchemaField = z.infer<
|
||||
typeof ZConfigureFieldsFormSchema
|
||||
>['fields'][number];
|
||||
export type TConfigureFieldsFormSchemaField = z.infer<typeof ZConfigureFieldsFormSchema>['fields'][number];
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { type TFieldMetaSchema as FieldMeta } from '@documenso/lib/types/field-meta';
|
||||
import type { TFieldMetaSchema as FieldMeta } from '@documenso/lib/types/field-meta';
|
||||
import { parseMessageDescriptor } from '@documenso/lib/utils/i18n';
|
||||
import { FieldAdvancedSettings } from '@documenso/ui/primitives/document-flow/field-item-advanced-settings';
|
||||
import { FRIENDLY_FIELD_TYPE } from '@documenso/ui/primitives/document-flow/types';
|
||||
import { Sheet, SheetContent, SheetTitle } from '@documenso/ui/primitives/sheet';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import type { TConfigureFieldsFormSchemaField } from './configure-fields-view.types';
|
||||
|
||||
@@ -42,10 +41,7 @@ export const FieldAdvancedSettingsDrawer = ({
|
||||
|
||||
<FieldAdvancedSettings
|
||||
title={msg`Advanced settings`}
|
||||
description={msg`Configure the ${parseMessageDescriptor(
|
||||
_,
|
||||
FRIENDLY_FIELD_TYPE[currentField.type],
|
||||
)} field`}
|
||||
description={msg`Configure the ${parseMessageDescriptor(_, FRIENDLY_FIELD_TYPE[currentField.type])} field`}
|
||||
field={currentField}
|
||||
fields={fields}
|
||||
onAdvancedSettings={() => onOpenChange(false)}
|
||||
|
||||
Reference in New Issue
Block a user