feat: period select for expiry

This commit is contained in:
Ephraim Atta-Duncan
2024-11-17 16:06:10 +00:00
parent c422317566
commit 8491c69e8c
2 changed files with 240 additions and 62 deletions

View File

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

View File

@ -1,11 +1,13 @@
'use client'; 'use client';
import { useState } 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 { Trans, msg } from '@lingui/macro'; import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react'; 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 { Calendar as CalendarIcon } from 'lucide-react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import * as z from 'zod'; import * as z from 'zod';
@ -32,17 +34,31 @@ import {
FormMessage, FormMessage,
} from '@documenso/ui/primitives/form/form'; } from '@documenso/ui/primitives/form/form';
import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover'; 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 { cn } from '../../lib/utils';
import { useToast } from '../use-toast'; import { useToast } from '../use-toast';
import type { TAddSignerSchema as Signer } from './add-signers.types'; import type { TAddSignerSchema as Signer } from './add-signers.types';
const formSchema = z.object({ const dateFormSchema = z.object({
expiry: z.date({ expiry: z.date({
required_error: 'Please select an expiry 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 = { type DocumentExpiryDialogProps = {
open: boolean; open: boolean;
onOpenChange: (_open: boolean) => void; onOpenChange: (_open: boolean) => void;
@ -56,21 +72,53 @@ export default function DocumentExpiryDialog({
signer, signer,
documentId, documentId,
}: DocumentExpiryDialogProps) { }: DocumentExpiryDialogProps) {
const { toast } = useToast();
const router = useRouter();
const { _ } = useLingui(); const { _ } = useLingui();
const router = useRouter();
const { toast } = useToast();
const [activeTab, setActiveTab] = useState<'date' | 'period'>('date');
const form = useForm<z.infer<typeof formSchema>>({ const dateForm = useForm<z.infer<typeof dateFormSchema>>({
resolver: zodResolver(formSchema), resolver: zodResolver(dateFormSchema),
defaultValues: { defaultValues: {
expiry: signer.expiry, expiry: signer.expiry,
}, },
}); });
const periodForm = useForm<z.infer<typeof periodFormSchema>>({
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({ const { mutateAsync: setSignerExpiry, isLoading } = trpc.recipient.setSignerExpiry.useMutation({
onSuccess: () => { onSuccess: ({ expired }) => {
router.refresh(); router.refresh();
periodForm.reset(
expired
? calculatePeriod(expired)
: {
amount: undefined,
unit: undefined,
},
);
dateForm.reset(
{
expiry: expired ?? undefined,
},
{
keepValues: false,
},
);
toast({ toast({
title: _(msg`Signer Expiry Set`), title: _(msg`Signer Expiry Set`),
description: _(msg`The expiry date for the signer has been 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<typeof formSchema>) => { const onSetExpiry = async (
values: z.infer<typeof dateFormSchema> | z.infer<typeof periodFormSchema>,
) => {
if (!signer.nativeId) { if (!signer.nativeId) {
return toast({ return toast({
title: _(msg`Error`), 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({ await setSignerExpiry({
documentId, documentId,
signerId: signer.nativeId, 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 ( return (
@ -115,58 +192,143 @@ export default function DocumentExpiryDialog({
to sign the document after this date. to sign the document after this date.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<Form {...form}> <Tabs
<form onSubmit={form.handleSubmit(onSetExpiry)} className="space-y-8"> value={activeTab}
<FormField onValueChange={(value) => {
control={form.control} // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
name="expiry" return setActiveTab(value as 'date' | 'period');
render={({ field }) => ( }}
<FormItem className="flex flex-col"> >
<FormLabel>Expiry Date</FormLabel> <TabsList className="grid w-full grid-cols-2">
<Popover> <TabsTrigger value="date">Specific Date</TabsTrigger>
<PopoverTrigger asChild> <TabsTrigger value="period">Time Period</TabsTrigger>
<FormControl> </TabsList>
<Button <TabsContent value="date">
variant={'outline'} <Form {...dateForm}>
className={cn( <form onSubmit={dateForm.handleSubmit(onSetExpiry)} className="space-y-8">
'w-[240px] pl-3 text-left font-normal', <FormField
!field.value && 'text-muted-foreground', control={dateForm.control}
)} name="expiry"
> render={({ field }) => (
{field.value ? format(field.value, 'PPP') : <span>Pick a date</span>} <FormItem className="flex flex-col">
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" /> <FormLabel>Expiry Date</FormLabel>
</Button> <Popover>
</FormControl> <PopoverTrigger asChild>
</PopoverTrigger> <FormControl>
<PopoverContent className="z-[1100] w-auto p-0 " align="start"> <Button
<Calendar variant={'outline'}
mode="single" className={cn(
selected={field.value} 'w-[240px] pl-3 text-left font-normal',
onSelect={field.onChange} !field.value && 'text-muted-foreground',
disabled={(date) => date < new Date() || date < new Date('1900-01-01')} )}
initialFocus >
/> {field.value ? format(field.value, 'PPP') : <span>Pick a date</span>}
</PopoverContent> <CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
</Popover> </Button>
<FormDescription> </FormControl>
The document will expire at 11:59 PM on the selected date. </PopoverTrigger>
</FormDescription> <PopoverContent className="z-[1100] w-auto p-0" align="start">
<FormMessage /> <Calendar
</FormItem> mode="single"
)} selected={field.value}
/> onSelect={field.onChange}
<DialogFooter> disabled={(date) => date < new Date() || date < new Date('1900-01-01')}
<DialogClose asChild> initialFocus
<Button type="button" variant="secondary"> />
<Trans>Cancel</Trans> </PopoverContent>
</Button> </Popover>
</DialogClose> <FormDescription>
<Button type="submit" loading={isLoading}> The document will expire at 11:59 PM on the selected date.
<Trans>Save Changes</Trans> </FormDescription>
</Button> <FormMessage />
</DialogFooter> </FormItem>
</form> )}
</Form> />
<DialogFooter>
<DialogClose asChild>
<Button type="button" variant="secondary">
<Trans>Cancel</Trans>
</Button>
</DialogClose>
<Button type="submit" loading={isLoading}>
<Trans>Save Changes</Trans>
</Button>
</DialogFooter>
</form>
</Form>
</TabsContent>
<TabsContent value="period">
<Form {...periodForm}>
<form onSubmit={periodForm.handleSubmit(onSetExpiry)} className="space-y-8">
<div className="flex space-x-4">
<FormField
control={periodForm.control}
name="amount"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>Amount</FormLabel>
<FormControl>
<Select
onValueChange={(value) => field.onChange(parseInt(value, 10))}
value={watchAmount?.toString()}
>
<SelectTrigger>
<SelectValue placeholder="Select amount" />
</SelectTrigger>
<SelectContent>
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((num) => (
<SelectItem key={num} value={num.toString()}>
{num}
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={periodForm.control}
name="unit"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>Unit</FormLabel>
<FormControl>
<Select onValueChange={field.onChange} value={watchUnit}>
{' '}
<SelectTrigger>
<SelectValue placeholder="Select unit" />
</SelectTrigger>
<SelectContent>
<SelectItem value="days">Days</SelectItem>
<SelectItem value="weeks">Weeks</SelectItem>
<SelectItem value="months">Months</SelectItem>
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<FormDescription>
The document will expire after the selected time period from now.
</FormDescription>
<DialogFooter>
<DialogClose asChild>
<Button type="button" variant="secondary">
<Trans>Cancel</Trans>
</Button>
</DialogClose>
<Button type="submit" loading={isLoading}>
<Trans>Save Changes</Trans>
</Button>
</DialogFooter>
</form>
</Form>
</TabsContent>
</Tabs>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
); );