diff --git a/packages/ui/lib/calculate-period.ts b/packages/ui/lib/calculate-period.ts new file mode 100644 index 000000000..ffa9375cc --- /dev/null +++ b/packages/ui/lib/calculate-period.ts @@ -0,0 +1,16 @@ +import { differenceInDays, differenceInMonths, differenceInWeeks } from 'date-fns'; + +export const calculatePeriod = (expiryDate: Date) => { + const now = new Date(); + const daysDiff = differenceInDays(expiryDate, now); + const weeksDiff = differenceInWeeks(expiryDate, now); + const monthsDiff = differenceInMonths(expiryDate, now); + + if (monthsDiff > 0) { + return { amount: monthsDiff, unit: 'months' as const }; + } else if (weeksDiff > 0) { + return { amount: weeksDiff, unit: 'weeks' as const }; + } else { + return { amount: daysDiff, unit: 'days' as const }; + } +}; diff --git a/packages/ui/primitives/document-flow/document-expiry-dialog.tsx b/packages/ui/primitives/document-flow/document-expiry-dialog.tsx index f797ea0ea..0a752c0b2 100644 --- a/packages/ui/primitives/document-flow/document-expiry-dialog.tsx +++ b/packages/ui/primitives/document-flow/document-expiry-dialog.tsx @@ -1,11 +1,13 @@ 'use client'; +import { useState } from 'react'; + import { useRouter } from 'next/navigation'; import { zodResolver } from '@hookform/resolvers/zod'; import { Trans, msg } from '@lingui/macro'; import { useLingui } from '@lingui/react'; -import { format } from 'date-fns'; +import { addDays, addMonths, addWeeks, format } from 'date-fns'; import { Calendar as CalendarIcon } from 'lucide-react'; import { useForm } from 'react-hook-form'; import * as z from 'zod'; @@ -32,17 +34,31 @@ import { FormMessage, } from '@documenso/ui/primitives/form/form'; import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@documenso/ui/primitives/select'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs'; +import { calculatePeriod } from '../../lib/calculate-period'; import { cn } from '../../lib/utils'; import { useToast } from '../use-toast'; import type { TAddSignerSchema as Signer } from './add-signers.types'; -const formSchema = z.object({ +const dateFormSchema = z.object({ expiry: z.date({ required_error: 'Please select an expiry date.', }), }); +const periodFormSchema = z.object({ + amount: z.number().min(1, 'Please enter a number greater than 0.'), + unit: z.enum(['days', 'weeks', 'months']), +}); + type DocumentExpiryDialogProps = { open: boolean; onOpenChange: (_open: boolean) => void; @@ -56,21 +72,53 @@ export default function DocumentExpiryDialog({ signer, documentId, }: DocumentExpiryDialogProps) { - const { toast } = useToast(); - const router = useRouter(); - const { _ } = useLingui(); + const router = useRouter(); + const { toast } = useToast(); + const [activeTab, setActiveTab] = useState<'date' | 'period'>('date'); - const form = useForm>({ - resolver: zodResolver(formSchema), + const dateForm = useForm>({ + resolver: zodResolver(dateFormSchema), defaultValues: { expiry: signer.expiry, }, }); + const periodForm = useForm>({ + resolver: zodResolver(periodFormSchema), + defaultValues: signer.expiry + ? calculatePeriod(signer.expiry) + : { + amount: undefined, + unit: undefined, + }, + }); + + const watchAmount = periodForm.watch('amount'); + const watchUnit = periodForm.watch('unit'); + const { mutateAsync: setSignerExpiry, isLoading } = trpc.recipient.setSignerExpiry.useMutation({ - onSuccess: () => { + onSuccess: ({ expired }) => { router.refresh(); + + periodForm.reset( + expired + ? calculatePeriod(expired) + : { + amount: undefined, + unit: undefined, + }, + ); + + dateForm.reset( + { + expiry: expired ?? undefined, + }, + { + keepValues: false, + }, + ); + toast({ title: _(msg`Signer Expiry Set`), description: _(msg`The expiry date for the signer has been set.`), @@ -88,7 +136,9 @@ export default function DocumentExpiryDialog({ }, }); - const onSetExpiry = async (values: z.infer) => { + const onSetExpiry = async ( + values: z.infer | z.infer, + ) => { if (!signer.nativeId) { return toast({ title: _(msg`Error`), @@ -98,11 +148,38 @@ export default function DocumentExpiryDialog({ }); } + let expiryDate: Date; + + if ('expiry' in values) { + expiryDate = values.expiry; + } else { + const now = new Date(); + switch (values.unit) { + case 'days': + expiryDate = addDays(now, values.amount); + break; + case 'weeks': + expiryDate = addWeeks(now, values.amount); + break; + case 'months': + expiryDate = addMonths(now, values.amount); + break; + } + } + + console.log('finalll expiry date', expiryDate); + await setSignerExpiry({ documentId, signerId: signer.nativeId, - expiry: new Date(values.expiry), + expiry: expiryDate, }); + + // TODO: Implement logic to update expiry when resending document + // This should be handled on the server-side when a document is resent + + // TODO: Implement logic to mark recipients as expired + // This should be a scheduled task or part of the completion process on the server }; return ( @@ -115,58 +192,143 @@ export default function DocumentExpiryDialog({ to sign the document after this date. -
- - ( - - Expiry Date - - - - - - - - date < new Date() || date < new Date('1900-01-01')} - initialFocus - /> - - - - The document will expire at 11:59 PM on the selected date. - - - - )} - /> - - - - - - - - + { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return setActiveTab(value as 'date' | 'period'); + }} + > + + Specific Date + Time Period + + +
+ + ( + + Expiry Date + + + + + + + + date < new Date() || date < new Date('1900-01-01')} + initialFocus + /> + + + + The document will expire at 11:59 PM on the selected date. + + + + )} + /> + + + + + + + + +
+ +
+ +
+ ( + + Amount + + + + + + )} + /> + ( + + Unit + + + + + + )} + /> +
+ + The document will expire after the selected time period from now. + + + + + + + +
+ +
+
);