chore: [INCOMPLETE] checkbox ui for all fields

This commit is contained in:
Ephraim Atta-Duncan
2024-04-13 17:53:39 +00:00
parent aa951c1608
commit 507e8482dc
6 changed files with 142 additions and 121 deletions

View File

@ -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>
)} )}
/> />

View File

@ -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"

View File

@ -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'}
> >

View File

@ -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

View File

@ -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,
); );

View File

@ -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,
); );