mirror of
https://github.com/documenso/documenso.git
synced 2025-11-18 18:51:37 +10:00
feat: expiry dialog
This commit is contained in:
@ -177,12 +177,7 @@ export const ResendDocumentActionItem = ({
|
||||
<DialogFooter>
|
||||
<div className="flex w-full flex-1 flex-nowrap gap-4">
|
||||
<DialogClose asChild>
|
||||
<Button
|
||||
type="button"
|
||||
className="dark:bg-muted dark:hover:bg-muted/80 flex-1 bg-black/5 hover:bg-black/10"
|
||||
variant="secondary"
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
<Button type="button" className="flex-1" variant="secondary" disabled={isSubmitting}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
</DialogClose>
|
||||
|
||||
@ -29,17 +29,25 @@ function Calendar({ className, classNames, showOutsideDays = true, ...props }: C
|
||||
nav_button_next: 'absolute right-1',
|
||||
table: 'w-full border-collapse space-y-1',
|
||||
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',
|
||||
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(
|
||||
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:
|
||||
'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_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_range_middle: 'aria-selected:bg-accent aria-selected:text-accent-foreground',
|
||||
day_hidden: 'invisible',
|
||||
|
||||
@ -135,13 +135,13 @@ DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogOverlay,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogPortal,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogOverlay,
|
||||
DialogPortal,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
};
|
||||
|
||||
@ -8,7 +8,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
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 { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { prop, sortBy } from 'remeda';
|
||||
@ -41,6 +41,7 @@ import {
|
||||
DocumentFlowFormContainerStep,
|
||||
} from './document-flow-root';
|
||||
import { ShowFieldItem } from './show-field-item';
|
||||
import { SignerActionDropdown } from './signer-action-dropdown';
|
||||
import type { DocumentFlowStep } from './types';
|
||||
|
||||
export type AddSignersFormProps = {
|
||||
@ -628,24 +629,18 @@ export const AddSignersFormPartial = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
'mt-auto inline-flex h-10 w-10 items-center justify-center hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
{
|
||||
<SignerActionDropdown
|
||||
className={cn({
|
||||
'mb-6': form.formState.errors.signers?.[index],
|
||||
},
|
||||
)}
|
||||
disabled={
|
||||
})}
|
||||
onDelete={() => onRemoveSigner(index)}
|
||||
deleteDisabled={
|
||||
snapshot.isDragging ||
|
||||
isSubmitting ||
|
||||
!canRecipientBeModified(signer.nativeId) ||
|
||||
signers.length === 1
|
||||
}
|
||||
onClick={() => onRemoveSigner(index)}
|
||||
>
|
||||
<Trash className="h-4 w-4" />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</motion.fieldset>
|
||||
</div>
|
||||
|
||||
140
packages/ui/primitives/document-flow/document-expiry-dialog.tsx
Normal file
140
packages/ui/primitives/document-flow/document-expiry-dialog.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -36,7 +36,7 @@ export const ShowFieldItem = ({ field, recipients }: ShowFieldItemProps) => {
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
className={cn('pointer-events-none absolute z-10 opacity-75')}
|
||||
className={cn('pointer-events-none absolute opacity-75')}
|
||||
style={{
|
||||
top: `${coords.y}px`,
|
||||
left: `${coords.x}px`,
|
||||
|
||||
@ -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} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -92,4 +92,4 @@ const PopoverHover = ({ trigger, children, contentProps }: PopoverHoverProps) =>
|
||||
);
|
||||
};
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent, PopoverHover };
|
||||
export { Popover, PopoverContent, PopoverHover, PopoverTrigger };
|
||||
|
||||
Reference in New Issue
Block a user