feat: expiry dialog

This commit is contained in:
Ephraim Atta-Duncan
2024-11-17 09:46:41 +00:00
parent 63830fb257
commit ca2b6bea95
8 changed files with 223 additions and 34 deletions

View File

@ -177,12 +177,7 @@ export const ResendDocumentActionItem = ({
<DialogFooter> <DialogFooter>
<div className="flex w-full flex-1 flex-nowrap gap-4"> <div className="flex w-full flex-1 flex-nowrap gap-4">
<DialogClose asChild> <DialogClose asChild>
<Button <Button type="button" className="flex-1" variant="secondary" disabled={isSubmitting}>
type="button"
className="dark:bg-muted dark:hover:bg-muted/80 flex-1 bg-black/5 hover:bg-black/10"
variant="secondary"
disabled={isSubmitting}
>
<Trans>Cancel</Trans> <Trans>Cancel</Trans>
</Button> </Button>
</DialogClose> </DialogClose>

View File

@ -29,17 +29,25 @@ function Calendar({ className, classNames, showOutsideDays = true, ...props }: C
nav_button_next: 'absolute right-1', nav_button_next: 'absolute right-1',
table: 'w-full border-collapse space-y-1', table: 'w-full border-collapse space-y-1',
head_row: 'flex', head_row: 'flex',
head_cell: 'text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]', head_cell: 'text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]',
row: 'flex w-full mt-2', row: 'flex w-full mt-2',
cell: 'text-center text-sm p-0 relative [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20', cell: cn(
'relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected].day-range-end)]:rounded-r-md',
props.mode === 'range'
? '[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md'
: '[&:has([aria-selected])]:rounded-md',
),
day: cn( day: cn(
buttonVariants({ variant: 'ghost' }), buttonVariants({ variant: 'ghost' }),
'h-9 w-9 p-0 font-normal aria-selected:opacity-100', 'h-8 w-8 p-0 font-normal aria-selected:opacity-100',
), ),
day_range_start: 'day-range-start',
day_range_end: 'day-range-end',
day_selected: day_selected:
'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground', 'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground',
day_today: 'bg-accent text-accent-foreground', day_today: 'bg-accent text-accent-foreground',
day_outside: 'text-muted-foreground opacity-50', day_outside:
'day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground',
day_disabled: 'text-muted-foreground opacity-50', day_disabled: 'text-muted-foreground opacity-50',
day_range_middle: 'aria-selected:bg-accent aria-selected:text-accent-foreground', day_range_middle: 'aria-selected:bg-accent aria-selected:text-accent-foreground',
day_hidden: 'invisible', day_hidden: 'invisible',

View File

@ -135,13 +135,13 @@ DialogDescription.displayName = DialogPrimitive.Description.displayName;
export { export {
Dialog, Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogOverlay,
DialogTitle,
DialogDescription,
DialogPortal,
DialogClose, DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
}; };

View File

@ -8,7 +8,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, msg } from '@lingui/macro'; import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react'; import { useLingui } from '@lingui/react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { GripVerticalIcon, Plus, Trash } from 'lucide-react'; import { GripVerticalIcon, Plus } from 'lucide-react';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import { useFieldArray, useForm } from 'react-hook-form'; import { useFieldArray, useForm } from 'react-hook-form';
import { prop, sortBy } from 'remeda'; import { prop, sortBy } from 'remeda';
@ -41,6 +41,7 @@ import {
DocumentFlowFormContainerStep, DocumentFlowFormContainerStep,
} from './document-flow-root'; } from './document-flow-root';
import { ShowFieldItem } from './show-field-item'; import { ShowFieldItem } from './show-field-item';
import { SignerActionDropdown } from './signer-action-dropdown';
import type { DocumentFlowStep } from './types'; import type { DocumentFlowStep } from './types';
export type AddSignersFormProps = { export type AddSignersFormProps = {
@ -628,24 +629,18 @@ export const AddSignersFormPartial = ({
)} )}
/> />
<button <SignerActionDropdown
type="button" className={cn({
className={cn( 'mb-6': form.formState.errors.signers?.[index],
'mt-auto inline-flex h-10 w-10 items-center justify-center hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-50', })}
{ onDelete={() => onRemoveSigner(index)}
'mb-6': form.formState.errors.signers?.[index], deleteDisabled={
},
)}
disabled={
snapshot.isDragging || snapshot.isDragging ||
isSubmitting || isSubmitting ||
!canRecipientBeModified(signer.nativeId) || !canRecipientBeModified(signer.nativeId) ||
signers.length === 1 signers.length === 1
} }
onClick={() => onRemoveSigner(index)} />
>
<Trash className="h-4 w-4" />
</button>
</div> </div>
</motion.fieldset> </motion.fieldset>
</div> </div>

View File

@ -0,0 +1,140 @@
'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans } from '@lingui/macro';
import { format } from 'date-fns';
import { Calendar as CalendarIcon } from 'lucide-react';
import { useForm } from 'react-hook-form';
import * as z from 'zod';
import { Button } from '@documenso/ui/primitives/button';
import { Calendar } from '@documenso/ui/primitives/calendar';
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover';
import { cn } from '../../lib/utils';
const formSchema = z.object({
expiryDate: z.date({
required_error: 'Please select an expiry date.',
}),
});
type DocumentExpiryDialogProps = {
open: boolean;
onOpenChange: (_open: boolean) => void;
};
export default function DocumentExpiryDialog({ open, onOpenChange }: DocumentExpiryDialogProps) {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
});
// const { mutateAsync: moveDocument, isLoading } = trpc.document.moveDocumentToTeam.useMutation({
// onSuccess: () => {
// router.refresh();
// toast({
// title: _(msg`Document moved`),
// description: _(msg`The document has been successfully moved to the selected team.`),
// duration: 5000,
// });
// onOpenChange(false);
// },
// onError: (error) => {
// toast({
// title: _(msg`Error`),
// description: error.message || _(msg`An error occurred while moving the document.`),
// variant: 'destructive',
// duration: 7500,
// });
// },
// });
function onSubmit(values: z.infer<typeof formSchema>) {
console.log(values);
onOpenChange(false);
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[450px]">
<DialogHeader>
<DialogTitle>Set Document Expiry</DialogTitle>
<DialogDescription>
Set the expiry date for the document signing recipient. The recipient will not be able
to sign the document after this date.
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="expiryDate"
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel>Expiry Date</FormLabel>
<Popover>
<PopoverTrigger asChild>
<FormControl>
<Button
variant={'outline'}
className={cn(
'w-[240px] pl-3 text-left font-normal',
!field.value && 'text-muted-foreground',
)}
>
{field.value ? format(field.value, 'PPP') : <span>Pick a date</span>}
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="z-[1100] w-auto p-0 " align="start">
<Calendar
mode="single"
selected={field.value}
onSelect={field.onChange}
disabled={(date) => date < new Date() || date < new Date('1900-01-01')}
initialFocus
/>
</PopoverContent>
</Popover>
<FormDescription>
The document will expire at 11:59 PM on the selected date.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<DialogFooter>
<DialogClose asChild>
<Button type="button" variant="secondary">
<Trans>Cancel</Trans>
</Button>
</DialogClose>
<Button type="submit">
<Trans>Save Changes</Trans>
</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);
}

View File

@ -36,7 +36,7 @@ export const ShowFieldItem = ({ field, recipients }: ShowFieldItemProps) => {
return createPortal( return createPortal(
<div <div
className={cn('pointer-events-none absolute z-10 opacity-75')} className={cn('pointer-events-none absolute opacity-75')}
style={{ style={{
top: `${coords.y}px`, top: `${coords.y}px`,
left: `${coords.x}px`, left: `${coords.x}px`,

View File

@ -0,0 +1,51 @@
'use client';
import { useState } from 'react';
import { MoreHorizontal, Timer, Trash } from 'lucide-react';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@documenso/ui/primitives/dropdown-menu';
import { cn } from '../../lib/utils';
import DocumentExpiryDialog from './document-expiry-dialog';
type SignerActionDropdownProps = {
onDelete: () => void;
deleteDisabled?: boolean;
className?: string;
};
export function SignerActionDropdown({ deleteDisabled, className }: SignerActionDropdownProps) {
const [isExpiryDialogOpen, setExpiryDialogOpen] = useState(false);
return (
<>
<div className={cn('flex items-center justify-center', className)}>
<DropdownMenu>
<DropdownMenuTrigger>
<MoreHorizontal className="text-muted-foreground h-5 w-5" />
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuGroup>
<DropdownMenuItem className="gap-x-2" onClick={() => setExpiryDialogOpen(true)}>
<Timer className="h-4 w-4" />
Expiry
</DropdownMenuItem>
<DropdownMenuItem disabled={deleteDisabled} className="gap-x-2">
<Trash className="h-4 w-4" />
Delete
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
<DocumentExpiryDialog open={isExpiryDialogOpen} onOpenChange={setExpiryDialogOpen} />
</>
);
}

View File

@ -92,4 +92,4 @@ const PopoverHover = ({ trigger, children, contentProps }: PopoverHoverProps) =>
); );
}; };
export { Popover, PopoverTrigger, PopoverContent, PopoverHover }; export { Popover, PopoverContent, PopoverHover, PopoverTrigger };