mirror of
https://github.com/documenso/documenso.git
synced 2025-11-10 04:22:32 +10:00
Compare commits
3 Commits
v1.12.1
...
feat/check
| Author | SHA1 | Date | |
|---|---|---|---|
| 507e8482dc | |||
| aa951c1608 | |||
| 0f0f198b44 |
133
apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx
Normal file
133
apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx
Normal file
@ -0,0 +1,133 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useTransition } from 'react';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
|
||||
import type { Recipient } from '@documenso/prisma/client';
|
||||
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Checkbox } from '@documenso/ui/primitives/checkbox';
|
||||
import { Form, FormControl, FormField } from '@documenso/ui/primitives/form/form';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { SigningFieldContainer } from './signing-field-container';
|
||||
|
||||
export type CheckboxFieldProps = {
|
||||
field: FieldWithSignature;
|
||||
recipient: Recipient;
|
||||
};
|
||||
|
||||
const CheckBoxSchema = z.object({
|
||||
check: z.boolean().default(false).optional(),
|
||||
});
|
||||
|
||||
export const CheckboxField = ({ field, recipient }: CheckboxFieldProps) => {
|
||||
const router = useRouter();
|
||||
|
||||
const { toast } = useToast();
|
||||
|
||||
// const { executeActionAuthProcedure } = useRequiredDocumentAuthContext();
|
||||
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
const { mutateAsync: signFieldWithToken, isLoading: isSignFieldWithTokenLoading } =
|
||||
trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
||||
|
||||
const {
|
||||
mutateAsync: removeSignedFieldWithToken,
|
||||
isLoading: isRemoveSignedFieldWithTokenLoading,
|
||||
} = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
||||
|
||||
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending;
|
||||
|
||||
const form = useForm<z.infer<typeof CheckBoxSchema>>({
|
||||
resolver: zodResolver(CheckBoxSchema),
|
||||
defaultValues: {
|
||||
check: false,
|
||||
},
|
||||
});
|
||||
|
||||
const onSign = useCallback(
|
||||
async (authOptions?: TRecipientActionAuth) => {
|
||||
try {
|
||||
await signFieldWithToken({
|
||||
token: recipient.token,
|
||||
fieldId: field.id,
|
||||
value: 'checked',
|
||||
isBase64: true,
|
||||
authOptions,
|
||||
});
|
||||
|
||||
startTransition(() => router.refresh());
|
||||
} catch (err) {
|
||||
const error = AppError.parseError(err);
|
||||
|
||||
if (error.code === AppErrorCode.UNAUTHORIZED) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while signing the document.',
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
},
|
||||
[field.id, recipient.token, router, signFieldWithToken, toast],
|
||||
);
|
||||
|
||||
const onRemove = async () => {
|
||||
try {
|
||||
await removeSignedFieldWithToken({
|
||||
token: recipient.token,
|
||||
fieldId: field.id,
|
||||
});
|
||||
|
||||
startTransition(() => router.refresh());
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while removing the text.',
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SigningFieldContainer
|
||||
field={field}
|
||||
onSign={onSign}
|
||||
onRemove={onRemove}
|
||||
type="Checkbox"
|
||||
raw={true}
|
||||
>
|
||||
<Form {...form}>
|
||||
<form>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="check"
|
||||
render={({ field }) => (
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
className="h-8 w-8"
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
</SigningFieldContainer>
|
||||
);
|
||||
};
|
||||
@ -35,8 +35,10 @@ export type SignatureFieldProps = {
|
||||
*/
|
||||
onSign?: (documentAuthValue?: TRecipientActionAuth) => Promise<void> | void;
|
||||
onRemove?: () => Promise<void> | void;
|
||||
type?: 'Date' | 'Email' | 'Name' | 'Signature';
|
||||
type?: 'Date' | 'Email' | 'Name' | 'Signature' | 'Checkbox';
|
||||
tooltipText?: string | null;
|
||||
|
||||
raw?: boolean;
|
||||
};
|
||||
|
||||
export const SigningFieldContainer = ({
|
||||
@ -48,6 +50,7 @@ export const SigningFieldContainer = ({
|
||||
children,
|
||||
type,
|
||||
tooltipText,
|
||||
raw = false,
|
||||
}: SignatureFieldProps) => {
|
||||
const { executeActionAuthProcedure, isAuthRedirectRequired } = useRequiredDocumentAuthContext();
|
||||
|
||||
@ -103,8 +106,8 @@ export const SigningFieldContainer = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<FieldRootContainer field={field}>
|
||||
{!field.inserted && !loading && (
|
||||
<FieldRootContainer raw={raw} field={field}>
|
||||
{!field.inserted && !loading && !raw && (
|
||||
<button
|
||||
type="submit"
|
||||
className="absolute inset-0 z-10 h-full w-full"
|
||||
|
||||
@ -12,6 +12,7 @@ import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer';
|
||||
|
||||
import { truncateTitle } from '~/helpers/truncate-title';
|
||||
|
||||
import { CheckboxField } from './checkbox-field';
|
||||
import { DateField } from './date-field';
|
||||
import { EmailField } from './email-field';
|
||||
import { SigningForm } from './form';
|
||||
@ -94,6 +95,9 @@ export const SigningPageView = ({ document, recipient, fields }: SigningPageView
|
||||
.with(FieldType.TEXT, () => (
|
||||
<TextField key={field.id} field={field} recipient={recipient} />
|
||||
))
|
||||
.with(FieldType.CHECKBOX, () => (
|
||||
<CheckboxField key={field.id} field={field} recipient={recipient} />
|
||||
))
|
||||
.otherwise(() => null),
|
||||
)}
|
||||
</ElementVisible>
|
||||
|
||||
@ -188,10 +188,17 @@ export const signFieldWithToken = async ({
|
||||
type,
|
||||
data: signatureImageAsBase64 || typedSignature || '',
|
||||
}))
|
||||
.with(FieldType.DATE, FieldType.EMAIL, FieldType.NAME, FieldType.TEXT, (type) => ({
|
||||
type,
|
||||
data: updatedField.customText,
|
||||
}))
|
||||
.with(
|
||||
FieldType.DATE,
|
||||
FieldType.EMAIL,
|
||||
FieldType.NAME,
|
||||
FieldType.TEXT,
|
||||
FieldType.CHECKBOX,
|
||||
(type) => ({
|
||||
type,
|
||||
data: updatedField.customText,
|
||||
}),
|
||||
)
|
||||
.exhaustive(),
|
||||
fieldSecurity: derivedRecipientActionAuth
|
||||
? {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// https://github.com/Hopding/pdf-lib/issues/20#issuecomment-412852821
|
||||
import fontkit from '@pdf-lib/fontkit';
|
||||
import { PDFDocument, StandardFonts } from 'pdf-lib';
|
||||
import type { PDFDocument } from 'pdf-lib';
|
||||
import { StandardFonts } from 'pdf-lib';
|
||||
|
||||
import {
|
||||
DEFAULT_HANDWRITING_FONT_SIZE,
|
||||
@ -18,6 +19,7 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
|
||||
);
|
||||
|
||||
const isSignatureField = isSignatureFieldType(field.type);
|
||||
const isCheckboxField = field.type === FieldType.CHECKBOX;
|
||||
|
||||
pdf.registerFontkit(fontkit);
|
||||
|
||||
@ -73,6 +75,28 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
|
||||
width: imageWidth,
|
||||
height: imageHeight,
|
||||
});
|
||||
} else if (isCheckboxField) {
|
||||
const form = pdf.getForm();
|
||||
const checkBox = form.createCheckBox(`checkBox.field.${field.id}`);
|
||||
|
||||
const textX = fieldX + fieldWidth / 2;
|
||||
let textY = fieldY + fieldHeight / 2;
|
||||
|
||||
textY = pageHeight - textY;
|
||||
|
||||
checkBox.addToPage(page, {
|
||||
x: textX,
|
||||
y: textY,
|
||||
width: 16,
|
||||
height: 16,
|
||||
borderWidth: 1,
|
||||
});
|
||||
|
||||
if (field.customText === '✓') {
|
||||
checkBox.check();
|
||||
}
|
||||
|
||||
form.getField(`checkBox.field.${field.id}`).enableReadOnly();
|
||||
} else {
|
||||
const longestLineInTextForWidth = field.customText
|
||||
.split('\n')
|
||||
@ -102,14 +126,3 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
|
||||
|
||||
return pdf;
|
||||
};
|
||||
|
||||
export const insertFieldInPDFBytes = async (
|
||||
pdf: ArrayBuffer | Uint8Array | string,
|
||||
field: FieldWithSignature,
|
||||
) => {
|
||||
const pdfDoc = await PDFDocument.load(pdf);
|
||||
|
||||
await insertFieldInPDF(pdfDoc, field);
|
||||
|
||||
return await pdfDoc.save();
|
||||
};
|
||||
|
||||
@ -231,6 +231,10 @@ export const ZDocumentAuditLogEventDocumentFieldInsertedSchema = z.object({
|
||||
type: z.literal(FieldType.TEXT),
|
||||
data: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.CHECKBOX),
|
||||
data: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.union([z.literal(FieldType.SIGNATURE), z.literal(FieldType.FREE_SIGNATURE)]),
|
||||
data: z.string(),
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
-- AlterEnum
|
||||
ALTER TYPE "FieldType" ADD VALUE 'CHECKBOX';
|
||||
@ -379,6 +379,7 @@ enum FieldType {
|
||||
EMAIL
|
||||
DATE
|
||||
TEXT
|
||||
CHECKBOX
|
||||
}
|
||||
|
||||
model Field {
|
||||
|
||||
@ -23,6 +23,7 @@ export const mapField = (
|
||||
.with(FieldType.EMAIL, () => signer.email)
|
||||
.with(FieldType.NAME, () => signer.name)
|
||||
.with(FieldType.TEXT, () => signer.customText)
|
||||
.with(FieldType.CHECKBOX, () => signer.customText)
|
||||
.otherwise(() => '');
|
||||
|
||||
return {
|
||||
|
||||
@ -18,6 +18,7 @@ export type FieldRootContainerProps = {
|
||||
export type FieldContainerPortalProps = {
|
||||
field: Field;
|
||||
className?: string;
|
||||
raw?: boolean;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
@ -44,7 +45,7 @@ export function FieldContainerPortal({
|
||||
);
|
||||
}
|
||||
|
||||
export function FieldRootContainer({ field, children }: FieldContainerPortalProps) {
|
||||
export function FieldRootContainer({ field, children, raw = false }: FieldContainerPortalProps) {
|
||||
const [isValidating, setIsValidating] = useState(false);
|
||||
|
||||
const ref = React.useRef<HTMLDivElement>(null);
|
||||
@ -71,21 +72,36 @@ export function FieldRootContainer({ field, children }: FieldContainerPortalProp
|
||||
|
||||
return (
|
||||
<FieldContainerPortal field={field}>
|
||||
<Card
|
||||
id={`field-${field.id}`}
|
||||
className={cn(
|
||||
'field-card-container bg-background relative z-20 h-full w-full transition-all',
|
||||
{
|
||||
{!raw && (
|
||||
<Card
|
||||
id={`field-${field.id}`}
|
||||
className={cn(
|
||||
'field-card-container bg-background relative z-20 h-full w-full transition-all',
|
||||
{
|
||||
'border-orange-300 ring-1 ring-orange-300': !field.inserted && isValidating,
|
||||
},
|
||||
)}
|
||||
ref={ref}
|
||||
data-inserted={field.inserted ? 'true' : 'false'}
|
||||
>
|
||||
<CardContent className="text-foreground hover:shadow-primary-foreground group flex h-full w-full flex-col items-center justify-center p-2">
|
||||
{children}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{raw && (
|
||||
<div
|
||||
id={`field-${field.id}`}
|
||||
className={cn('field-card-container bg-background relative z-20 transition-all', {
|
||||
'border-orange-300 ring-1 ring-orange-300': !field.inserted && isValidating,
|
||||
},
|
||||
)}
|
||||
ref={ref}
|
||||
data-inserted={field.inserted ? 'true' : 'false'}
|
||||
>
|
||||
<CardContent className="text-foreground hover:shadow-primary-foreground group flex h-full w-full flex-col items-center justify-center p-2">
|
||||
})}
|
||||
ref={ref}
|
||||
data-inserted={field.inserted ? 'true' : 'false'}
|
||||
>
|
||||
{children}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</FieldContainerPortal>
|
||||
);
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ import { FieldType, SendStatus } from '@documenso/prisma/client';
|
||||
import { cn } from '../../lib/utils';
|
||||
import { Button } from '../button';
|
||||
import { Card, CardContent } from '../card';
|
||||
import { Checkbox } from '../checkbox';
|
||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from '../command';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '../popover';
|
||||
import { useStep } from '../stepper';
|
||||
@ -135,11 +136,17 @@ export const AddFieldsFormPartial = ({
|
||||
);
|
||||
|
||||
setCoords({
|
||||
x: event.clientX - fieldBounds.current.width / 2,
|
||||
y: event.clientY - fieldBounds.current.height / 2,
|
||||
x:
|
||||
selectedField === FieldType.CHECKBOX
|
||||
? event.clientX - 16
|
||||
: event.clientX - fieldBounds.current.width / 2,
|
||||
y:
|
||||
selectedField === FieldType.CHECKBOX
|
||||
? event.clientY - 16
|
||||
: event.clientY - fieldBounds.current.height / 2,
|
||||
});
|
||||
},
|
||||
[isWithinPageBounds],
|
||||
[isWithinPageBounds, selectedField],
|
||||
);
|
||||
|
||||
const onMouseClick = useCallback(
|
||||
@ -149,6 +156,7 @@ export const AddFieldsFormPartial = ({
|
||||
}
|
||||
|
||||
const $page = getPage(event, PDF_VIEWER_PAGE_SELECTOR);
|
||||
const isCheckboxField = selectedField === FieldType.CHECKBOX;
|
||||
|
||||
if (
|
||||
!$page ||
|
||||
@ -172,8 +180,8 @@ export const AddFieldsFormPartial = ({
|
||||
let pageY = ((event.pageY - top) / height) * 100;
|
||||
|
||||
// Get the bounds as a percentage of the page width and height
|
||||
const fieldPageWidth = (fieldBounds.current.width / width) * 100;
|
||||
const fieldPageHeight = (fieldBounds.current.height / height) * 100;
|
||||
const fieldPageWidth = ((isCheckboxField ? 32 : fieldBounds.current.width) / width) * 100;
|
||||
const fieldPageHeight = ((isCheckboxField ? 32 : fieldBounds.current.height) / height) * 100;
|
||||
|
||||
// And center it based on the bounds
|
||||
pageX -= fieldPageWidth / 2;
|
||||
@ -322,7 +330,8 @@ export const AddFieldsFormPartial = ({
|
||||
|
||||
<DocumentFlowFormContainerContent>
|
||||
<div className="flex flex-col">
|
||||
{selectedField && (
|
||||
{/* When it is not a checkbox field */}
|
||||
{selectedField && selectedField !== FieldType.CHECKBOX && (
|
||||
<Card
|
||||
className={cn(
|
||||
'bg-field-card/80 pointer-events-none fixed z-50 cursor-pointer border-2 backdrop-blur-[1px]',
|
||||
@ -344,6 +353,27 @@ export const AddFieldsFormPartial = ({
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Checkbox Field */}
|
||||
{selectedField && selectedField === FieldType.CHECKBOX && (
|
||||
<div
|
||||
className="pointer-events-none fixed z-50"
|
||||
style={{
|
||||
top: coords.y,
|
||||
left: coords.x,
|
||||
height: 6 * 4,
|
||||
width: 6 * 4,
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
className={cn(
|
||||
'bg-field-card/80 h-8 w-8 border-2 backdrop-blur-[1px]',
|
||||
'shadow-[0_0_0_4px_theme(colors.gray.100/70%),0_0_0_1px_theme(colors.gray.100/70%),0_0_0_0.5px_theme(colors.primary.DEFAULT/70%)]',
|
||||
'border-field-card-border',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isDocumentPdfLoaded &&
|
||||
localFields.map((field, index) => (
|
||||
<FieldItem
|
||||
@ -577,6 +607,28 @@ export const AddFieldsFormPartial = ({
|
||||
</CardContent>
|
||||
</Card>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="group h-full w-full"
|
||||
onClick={() => setSelectedField(FieldType.CHECKBOX)}
|
||||
onMouseDown={() => setSelectedField(FieldType.CHECKBOX)}
|
||||
data-selected={selectedField === FieldType.CHECKBOX ? true : undefined}
|
||||
>
|
||||
<Card className="group-data-[selected]:border-documenso h-full w-full cursor-pointer group-disabled:opacity-50">
|
||||
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
|
||||
<p
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground text-xl font-medium',
|
||||
)}
|
||||
>
|
||||
{'Checkbox'}
|
||||
</p>
|
||||
|
||||
<p className="text-muted-foreground mt-2 text-xs">Checkbox</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</button>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -147,6 +147,11 @@ export const AddSignatureFormPartial = ({
|
||||
return !form.formState.errors.customText;
|
||||
}
|
||||
|
||||
if (fieldType === FieldType.CHECKBOX) {
|
||||
await form.trigger('customText');
|
||||
return !form.formState.errors.customText;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
@ -10,8 +10,10 @@ import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
import { Card, CardContent } from '../card';
|
||||
import { Checkbox } from '../checkbox';
|
||||
import type { TDocumentFlowFormSchema } from './types';
|
||||
import { FRIENDLY_FIELD_TYPE } from './types';
|
||||
import { FieldType } from '.prisma/client';
|
||||
|
||||
type Field = TDocumentFlowFormSchema['fields'][0];
|
||||
|
||||
@ -44,6 +46,8 @@ export const FieldItem = ({
|
||||
pageWidth: 0,
|
||||
});
|
||||
|
||||
const isCheckboxField = field.type === FieldType.CHECKBOX;
|
||||
|
||||
const calculateCoords = useCallback(() => {
|
||||
const $page = document.querySelector<HTMLElement>(
|
||||
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.pageNumber}"]`,
|
||||
@ -102,8 +106,8 @@ export const FieldItem = ({
|
||||
default={{
|
||||
x: coords.pageX,
|
||||
y: coords.pageY,
|
||||
height: coords.pageHeight,
|
||||
width: coords.pageWidth,
|
||||
height: isCheckboxField ? 32 : coords.pageHeight,
|
||||
width: isCheckboxField ? 32 : coords.pageWidth,
|
||||
}}
|
||||
bounds={`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.pageNumber}"]`}
|
||||
onDragStart={() => setActive(true)}
|
||||
@ -119,7 +123,13 @@ export const FieldItem = ({
|
||||
>
|
||||
{!disabled && (
|
||||
<button
|
||||
className="text-muted-foreground/50 hover:text-muted-foreground/80 bg-background absolute -right-2 -top-2 z-20 flex h-8 w-8 items-center justify-center rounded-full border"
|
||||
className={cn(
|
||||
'text-muted-foreground/50 hover:text-muted-foreground/80 bg-background absolute -right-2 -top-2 z-20 flex items-center justify-center rounded-full border',
|
||||
{
|
||||
'h-8 w-8': !isCheckboxField,
|
||||
'h-6 w-6': isCheckboxField,
|
||||
},
|
||||
)}
|
||||
onClick={() => onRemove?.()}
|
||||
onTouchEnd={() => onRemove?.()}
|
||||
>
|
||||
@ -127,25 +137,40 @@ export const FieldItem = ({
|
||||
</button>
|
||||
)}
|
||||
|
||||
<Card
|
||||
className={cn('bg-field-card/80 h-full w-full backdrop-blur-[1px]', {
|
||||
'border-field-card-border': !disabled,
|
||||
'border-field-card-border/80': active,
|
||||
})}
|
||||
>
|
||||
<CardContent
|
||||
{!isCheckboxField && (
|
||||
<Card
|
||||
className={cn('bg-field-card/80 h-full w-full backdrop-blur-[1px]', {
|
||||
'border-field-card-border': !disabled,
|
||||
'border-field-card-border/80': active,
|
||||
})}
|
||||
>
|
||||
<CardContent
|
||||
className={cn(
|
||||
'text-field-card-foreground flex h-full w-full flex-col items-center justify-center p-2',
|
||||
{
|
||||
'text-field-card-foreground/50': disabled,
|
||||
},
|
||||
)}
|
||||
>
|
||||
{FRIENDLY_FIELD_TYPE[field.type]}
|
||||
|
||||
<p className="w-full truncate text-center text-xs">{field.signerEmail}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{isCheckboxField && (
|
||||
<Checkbox
|
||||
className={cn(
|
||||
'text-field-card-foreground flex h-full w-full flex-col items-center justify-center p-2',
|
||||
'bg-field-card/80 h-8 w-8 border-2 backdrop-blur-[1px]',
|
||||
'shadow-[0_0_0_4px_theme(colors.gray.100/70%),0_0_0_1px_theme(colors.gray.100/70%),0_0_0_0.5px_theme(colors.primary.DEFAULT/70%)]',
|
||||
{
|
||||
'text-field-card-foreground/50': disabled,
|
||||
'border-field-card-border': !disabled,
|
||||
'border-field-card-border/80': active,
|
||||
},
|
||||
)}
|
||||
>
|
||||
{FRIENDLY_FIELD_TYPE[field.type]}
|
||||
|
||||
<p className="w-full truncate text-center text-xs">{field.signerEmail}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
/>
|
||||
)}
|
||||
</Rnd>,
|
||||
document.body,
|
||||
);
|
||||
|
||||
@ -4,9 +4,11 @@ import type { Prisma } from '@prisma/client';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
import { useFieldPageCoords } from '@documenso/lib/client-only/hooks/use-field-page-coords';
|
||||
import { FieldType } from '@documenso/prisma/client';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
import { Card, CardContent } from '../card';
|
||||
import { Checkbox } from '../checkbox';
|
||||
import { FRIENDLY_FIELD_TYPE } from './types';
|
||||
|
||||
export type ShowFieldItemProps = {
|
||||
@ -19,6 +21,7 @@ export const ShowFieldItem = ({ field, recipients }: ShowFieldItemProps) => {
|
||||
|
||||
const signerEmail =
|
||||
recipients.find((recipient) => recipient.id === field.recipientId)?.email ?? '';
|
||||
const isCheckboxField = field.type === FieldType.CHECKBOX;
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
@ -30,19 +33,30 @@ export const ShowFieldItem = ({ field, recipients }: ShowFieldItemProps) => {
|
||||
width: `${coords.width}px`,
|
||||
}}
|
||||
>
|
||||
<Card className={cn('bg-background h-full w-full')}>
|
||||
<CardContent
|
||||
className={cn(
|
||||
'text-muted-foreground/50 flex h-full w-full flex-col items-center justify-center p-2',
|
||||
)}
|
||||
>
|
||||
{FRIENDLY_FIELD_TYPE[field.type]}
|
||||
{!isCheckboxField && (
|
||||
<Card className={cn('bg-background h-full w-full')}>
|
||||
<CardContent
|
||||
className={cn(
|
||||
'text-muted-foreground/50 flex h-full w-full flex-col items-center justify-center p-2',
|
||||
)}
|
||||
>
|
||||
{FRIENDLY_FIELD_TYPE[field.type]}
|
||||
|
||||
<p className="text-muted-foreground/50 w-full truncate text-center text-xs">
|
||||
{signerEmail}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<p className="text-muted-foreground/50 w-full truncate text-center text-xs">
|
||||
{signerEmail}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{isCheckboxField && (
|
||||
<Checkbox
|
||||
className={cn(
|
||||
'h-8 w-8',
|
||||
'shadow-[0_0_0_4px_theme(colors.gray.100/70%),0_0_0_1px_theme(colors.gray.100/70%),0_0_0_0.5px_theme(colors.primary.DEFAULT/70%)]',
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>,
|
||||
document.body,
|
||||
);
|
||||
|
||||
@ -48,6 +48,7 @@ export const FRIENDLY_FIELD_TYPE: Record<FieldType, string> = {
|
||||
[FieldType.DATE]: 'Date',
|
||||
[FieldType.EMAIL]: 'Email',
|
||||
[FieldType.NAME]: 'Name',
|
||||
[FieldType.CHECKBOX]: 'Checkbox',
|
||||
};
|
||||
|
||||
export interface DocumentFlowStep {
|
||||
|
||||
Reference in New Issue
Block a user