mirror of
https://github.com/documenso/documenso.git
synced 2025-11-17 02:01:33 +10:00
chore: [INCOMPLETE] checkbox ui for all fields
This commit is contained in:
@ -1,11 +1,10 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useTransition } from 'react';
|
import { useCallback, useTransition } from 'react';
|
||||||
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { Loader } from 'lucide-react';
|
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
@ -49,56 +48,41 @@ export const CheckboxField = ({ field, recipient }: CheckboxFieldProps) => {
|
|||||||
|
|
||||||
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending;
|
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending;
|
||||||
|
|
||||||
const [localText, setLocalCustomText] = useState('');
|
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof CheckBoxSchema>>({
|
const form = useForm<z.infer<typeof CheckBoxSchema>>({
|
||||||
resolver: zodResolver(CheckBoxSchema),
|
resolver: zodResolver(CheckBoxSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
check: true,
|
check: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const onPreSign = () => {
|
const onSign = useCallback(
|
||||||
if (!localText) {
|
async (authOptions?: TRecipientActionAuth) => {
|
||||||
return false;
|
try {
|
||||||
}
|
await signFieldWithToken({
|
||||||
|
token: recipient.token,
|
||||||
|
fieldId: field.id,
|
||||||
|
value: 'checked',
|
||||||
|
isBase64: true,
|
||||||
|
authOptions,
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
startTransition(() => router.refresh());
|
||||||
};
|
} catch (err) {
|
||||||
|
const error = AppError.parseError(err);
|
||||||
|
|
||||||
const onSign = async (authOptions?: TRecipientActionAuth) => {
|
if (error.code === AppErrorCode.UNAUTHORIZED) {
|
||||||
try {
|
throw error;
|
||||||
if (!localText) {
|
}
|
||||||
return;
|
|
||||||
|
toast({
|
||||||
|
title: 'Error',
|
||||||
|
description: 'An error occurred while signing the document.',
|
||||||
|
variant: 'destructive',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
await signFieldWithToken({
|
[field.id, recipient.token, router, signFieldWithToken, toast],
|
||||||
token: recipient.token,
|
);
|
||||||
fieldId: field.id,
|
|
||||||
value: localText,
|
|
||||||
isBase64: true,
|
|
||||||
authOptions,
|
|
||||||
});
|
|
||||||
|
|
||||||
setLocalCustomText('');
|
|
||||||
|
|
||||||
startTransition(() => router.refresh());
|
|
||||||
} catch (err) {
|
|
||||||
const error = AppError.parseError(err);
|
|
||||||
|
|
||||||
if (error.code === AppErrorCode.UNAUTHORIZED) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.error(err);
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: 'Error',
|
|
||||||
description: 'An error occurred while signing the document.',
|
|
||||||
variant: 'destructive',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onRemove = async () => {
|
const onRemove = async () => {
|
||||||
try {
|
try {
|
||||||
@ -119,52 +103,26 @@ export const CheckboxField = ({ field, recipient }: CheckboxFieldProps) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = (data: z.infer<typeof CheckBoxSchema>) => {
|
|
||||||
console.log(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SigningFieldContainer
|
<SigningFieldContainer
|
||||||
field={field}
|
field={field}
|
||||||
onPreSign={onPreSign}
|
|
||||||
onSign={onSign}
|
onSign={onSign}
|
||||||
onRemove={onRemove}
|
onRemove={onRemove}
|
||||||
type="Checkbox"
|
type="Checkbox"
|
||||||
raw={true}
|
raw={true}
|
||||||
>
|
>
|
||||||
{isLoading && (
|
|
||||||
<div className="bg-background absolute inset-0 flex items-center justify-center rounded-md">
|
|
||||||
<Loader className="text-primary h-5 w-5 animate-spin md:h-8 md:w-8" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!field.inserted && (
|
|
||||||
// TODO: span with a box
|
|
||||||
// <p className="group-hover:text-primary text-muted-foreground text-lg duration-200">
|
|
||||||
// Checkbox
|
|
||||||
// </p>
|
|
||||||
|
|
||||||
<Checkbox
|
|
||||||
id={`field-${field.id}`}
|
|
||||||
onClick={() => {
|
|
||||||
console.log('clicked checkbox');
|
|
||||||
}}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
setLocalCustomText(checked ? '✓' : '𐄂');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{field.inserted && <p className="text-muted-foreground duration-200">{field.customText}</p>}
|
|
||||||
|
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
<form>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="check"
|
name="check"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Checkbox checked={field.value} onCheckedChange={field.onChange} />
|
<Checkbox
|
||||||
|
checked={field.value}
|
||||||
|
className="h-8 w-8"
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -107,7 +107,7 @@ export const SigningFieldContainer = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<FieldRootContainer raw={raw} field={field}>
|
<FieldRootContainer raw={raw} field={field}>
|
||||||
{!field.inserted && !loading && (
|
{!field.inserted && !loading && !raw && (
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="absolute inset-0 z-10 h-full w-full"
|
className="absolute inset-0 z-10 h-full w-full"
|
||||||
|
|||||||
@ -92,16 +92,10 @@ export function FieldRootContainer({ field, children, raw = false }: FieldContai
|
|||||||
|
|
||||||
{raw && (
|
{raw && (
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
|
||||||
console.log('clickeddd');
|
|
||||||
}}
|
|
||||||
id={`field-${field.id}`}
|
id={`field-${field.id}`}
|
||||||
className={cn(
|
className={cn('field-card-container bg-background relative z-20 transition-all', {
|
||||||
'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,
|
||||||
{
|
})}
|
||||||
'border-orange-300 ring-1 ring-orange-300': !field.inserted && isValidating,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
ref={ref}
|
ref={ref}
|
||||||
data-inserted={field.inserted ? 'true' : 'false'}
|
data-inserted={field.inserted ? 'true' : 'false'}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import { FieldType, SendStatus } from '@documenso/prisma/client';
|
|||||||
import { cn } from '../../lib/utils';
|
import { cn } from '../../lib/utils';
|
||||||
import { Button } from '../button';
|
import { Button } from '../button';
|
||||||
import { Card, CardContent } from '../card';
|
import { Card, CardContent } from '../card';
|
||||||
|
import { Checkbox } from '../checkbox';
|
||||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from '../command';
|
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from '../command';
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from '../popover';
|
import { Popover, PopoverContent, PopoverTrigger } from '../popover';
|
||||||
import { useStep } from '../stepper';
|
import { useStep } from '../stepper';
|
||||||
@ -135,11 +136,17 @@ export const AddFieldsFormPartial = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
setCoords({
|
setCoords({
|
||||||
x: event.clientX - fieldBounds.current.width / 2,
|
x:
|
||||||
y: event.clientY - fieldBounds.current.height / 2,
|
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(
|
const onMouseClick = useCallback(
|
||||||
@ -149,6 +156,7 @@ export const AddFieldsFormPartial = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const $page = getPage(event, PDF_VIEWER_PAGE_SELECTOR);
|
const $page = getPage(event, PDF_VIEWER_PAGE_SELECTOR);
|
||||||
|
const isCheckboxField = selectedField === FieldType.CHECKBOX;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!$page ||
|
!$page ||
|
||||||
@ -172,8 +180,8 @@ export const AddFieldsFormPartial = ({
|
|||||||
let pageY = ((event.pageY - top) / height) * 100;
|
let pageY = ((event.pageY - top) / height) * 100;
|
||||||
|
|
||||||
// Get the bounds as a percentage of the page width and height
|
// Get the bounds as a percentage of the page width and height
|
||||||
const fieldPageWidth = (fieldBounds.current.width / width) * 100;
|
const fieldPageWidth = ((isCheckboxField ? 32 : fieldBounds.current.width) / width) * 100;
|
||||||
const fieldPageHeight = (fieldBounds.current.height / height) * 100;
|
const fieldPageHeight = ((isCheckboxField ? 32 : fieldBounds.current.height) / height) * 100;
|
||||||
|
|
||||||
// And center it based on the bounds
|
// And center it based on the bounds
|
||||||
pageX -= fieldPageWidth / 2;
|
pageX -= fieldPageWidth / 2;
|
||||||
@ -322,7 +330,8 @@ export const AddFieldsFormPartial = ({
|
|||||||
|
|
||||||
<DocumentFlowFormContainerContent>
|
<DocumentFlowFormContainerContent>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
{selectedField && (
|
{/* When it is not a checkbox field */}
|
||||||
|
{selectedField && selectedField !== FieldType.CHECKBOX && (
|
||||||
<Card
|
<Card
|
||||||
className={cn(
|
className={cn(
|
||||||
'bg-field-card/80 pointer-events-none fixed z-50 cursor-pointer border-2 backdrop-blur-[1px]',
|
'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>
|
</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 &&
|
{isDocumentPdfLoaded &&
|
||||||
localFields.map((field, index) => (
|
localFields.map((field, index) => (
|
||||||
<FieldItem
|
<FieldItem
|
||||||
|
|||||||
@ -10,8 +10,10 @@ import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
|||||||
|
|
||||||
import { cn } from '../../lib/utils';
|
import { cn } from '../../lib/utils';
|
||||||
import { Card, CardContent } from '../card';
|
import { Card, CardContent } from '../card';
|
||||||
|
import { Checkbox } from '../checkbox';
|
||||||
import type { TDocumentFlowFormSchema } from './types';
|
import type { TDocumentFlowFormSchema } from './types';
|
||||||
import { FRIENDLY_FIELD_TYPE } from './types';
|
import { FRIENDLY_FIELD_TYPE } from './types';
|
||||||
|
import { FieldType } from '.prisma/client';
|
||||||
|
|
||||||
type Field = TDocumentFlowFormSchema['fields'][0];
|
type Field = TDocumentFlowFormSchema['fields'][0];
|
||||||
|
|
||||||
@ -44,6 +46,8 @@ export const FieldItem = ({
|
|||||||
pageWidth: 0,
|
pageWidth: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isCheckboxField = field.type === FieldType.CHECKBOX;
|
||||||
|
|
||||||
const calculateCoords = useCallback(() => {
|
const calculateCoords = useCallback(() => {
|
||||||
const $page = document.querySelector<HTMLElement>(
|
const $page = document.querySelector<HTMLElement>(
|
||||||
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.pageNumber}"]`,
|
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.pageNumber}"]`,
|
||||||
@ -102,8 +106,8 @@ export const FieldItem = ({
|
|||||||
default={{
|
default={{
|
||||||
x: coords.pageX,
|
x: coords.pageX,
|
||||||
y: coords.pageY,
|
y: coords.pageY,
|
||||||
height: coords.pageHeight,
|
height: isCheckboxField ? 32 : coords.pageHeight,
|
||||||
width: coords.pageWidth,
|
width: isCheckboxField ? 32 : coords.pageWidth,
|
||||||
}}
|
}}
|
||||||
bounds={`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.pageNumber}"]`}
|
bounds={`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.pageNumber}"]`}
|
||||||
onDragStart={() => setActive(true)}
|
onDragStart={() => setActive(true)}
|
||||||
@ -119,7 +123,13 @@ export const FieldItem = ({
|
|||||||
>
|
>
|
||||||
{!disabled && (
|
{!disabled && (
|
||||||
<button
|
<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?.()}
|
onClick={() => onRemove?.()}
|
||||||
onTouchEnd={() => onRemove?.()}
|
onTouchEnd={() => onRemove?.()}
|
||||||
>
|
>
|
||||||
@ -127,25 +137,40 @@ export const FieldItem = ({
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Card
|
{!isCheckboxField && (
|
||||||
className={cn('bg-field-card/80 h-full w-full backdrop-blur-[1px]', {
|
<Card
|
||||||
'border-field-card-border': !disabled,
|
className={cn('bg-field-card/80 h-full w-full backdrop-blur-[1px]', {
|
||||||
'border-field-card-border/80': active,
|
'border-field-card-border': !disabled,
|
||||||
})}
|
'border-field-card-border/80': active,
|
||||||
>
|
})}
|
||||||
<CardContent
|
>
|
||||||
|
<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(
|
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>,
|
</Rnd>,
|
||||||
document.body,
|
document.body,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -4,9 +4,11 @@ import type { Prisma } from '@prisma/client';
|
|||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
import { useFieldPageCoords } from '@documenso/lib/client-only/hooks/use-field-page-coords';
|
import { useFieldPageCoords } from '@documenso/lib/client-only/hooks/use-field-page-coords';
|
||||||
|
import { FieldType } from '@documenso/prisma/client';
|
||||||
|
|
||||||
import { cn } from '../../lib/utils';
|
import { cn } from '../../lib/utils';
|
||||||
import { Card, CardContent } from '../card';
|
import { Card, CardContent } from '../card';
|
||||||
|
import { Checkbox } from '../checkbox';
|
||||||
import { FRIENDLY_FIELD_TYPE } from './types';
|
import { FRIENDLY_FIELD_TYPE } from './types';
|
||||||
|
|
||||||
export type ShowFieldItemProps = {
|
export type ShowFieldItemProps = {
|
||||||
@ -19,6 +21,7 @@ export const ShowFieldItem = ({ field, recipients }: ShowFieldItemProps) => {
|
|||||||
|
|
||||||
const signerEmail =
|
const signerEmail =
|
||||||
recipients.find((recipient) => recipient.id === field.recipientId)?.email ?? '';
|
recipients.find((recipient) => recipient.id === field.recipientId)?.email ?? '';
|
||||||
|
const isCheckboxField = field.type === FieldType.CHECKBOX;
|
||||||
|
|
||||||
return createPortal(
|
return createPortal(
|
||||||
<div
|
<div
|
||||||
@ -30,19 +33,30 @@ export const ShowFieldItem = ({ field, recipients }: ShowFieldItemProps) => {
|
|||||||
width: `${coords.width}px`,
|
width: `${coords.width}px`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Card className={cn('bg-background h-full w-full')}>
|
{!isCheckboxField && (
|
||||||
<CardContent
|
<Card className={cn('bg-background h-full w-full')}>
|
||||||
className={cn(
|
<CardContent
|
||||||
'text-muted-foreground/50 flex h-full w-full flex-col items-center justify-center p-2',
|
className={cn(
|
||||||
)}
|
'text-muted-foreground/50 flex h-full w-full flex-col items-center justify-center p-2',
|
||||||
>
|
)}
|
||||||
{FRIENDLY_FIELD_TYPE[field.type]}
|
>
|
||||||
|
{FRIENDLY_FIELD_TYPE[field.type]}
|
||||||
|
|
||||||
<p className="text-muted-foreground/50 w-full truncate text-center text-xs">
|
<p className="text-muted-foreground/50 w-full truncate text-center text-xs">
|
||||||
{signerEmail}
|
{signerEmail}
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</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>,
|
</div>,
|
||||||
document.body,
|
document.body,
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user