import { useMemo, useState } from 'react'; import { zodResolver } from '@hookform/resolvers/zod'; import { useLingui } from '@lingui/react/macro'; import { Trans } from '@lingui/react/macro'; import { DocumentDistributionMethod, DocumentStatus, EnvelopeType, type Field, FieldType, type Recipient, RecipientRole, } from '@prisma/client'; import { AnimatePresence, motion } from 'framer-motion'; import { InfoIcon } from 'lucide-react'; import { useForm } from 'react-hook-form'; import { useNavigate } from 'react-router'; import { match } from 'ts-pattern'; import * as z from 'zod'; import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; import type { TEnvelope } from '@documenso/lib/types/envelope'; import { trpc, trpc as trpcReact } from '@documenso/trpc/react'; import { DocumentSendEmailMessageHelper } from '@documenso/ui/components/document/document-send-email-message-helper'; import { cn } from '@documenso/ui/lib/utils'; import { Alert, AlertDescription } from '@documenso/ui/primitives/alert'; import { Button } from '@documenso/ui/primitives/button'; import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from '@documenso/ui/primitives/dialog'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from '@documenso/ui/primitives/form/form'; import { Input } from '@documenso/ui/primitives/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@documenso/ui/primitives/select'; import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs'; import { Textarea } from '@documenso/ui/primitives/textarea'; import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip'; import { useToast } from '@documenso/ui/primitives/use-toast'; export type EnvelopeDistributeDialogProps = { envelope: Pick & { recipients: Recipient[]; fields: Pick[]; }; onDistribute?: () => Promise; documentRootPath: string; trigger?: React.ReactNode; }; export const ZEnvelopeDistributeFormSchema = z.object({ meta: z.object({ emailId: z.string().nullable(), emailReplyTo: z.preprocess( (val) => (val === '' ? undefined : val), z.string().email().optional(), ), subject: z.string(), message: z.string(), distributionMethod: z .nativeEnum(DocumentDistributionMethod) .optional() .default(DocumentDistributionMethod.EMAIL), }), }); export type TEnvelopeDistributeFormSchema = z.infer; export const EnvelopeDistributeDialog = ({ envelope, trigger, documentRootPath, onDistribute, }: EnvelopeDistributeDialogProps) => { const organisation = useCurrentOrganisation(); const recipients = envelope.recipients; const { toast } = useToast(); const { t } = useLingui(); const navigate = useNavigate(); const [isOpen, setIsOpen] = useState(false); const { mutateAsync: distributeEnvelope } = trpcReact.envelope.distribute.useMutation(); const form = useForm({ defaultValues: { meta: { emailId: envelope.documentMeta?.emailId ?? null, emailReplyTo: envelope.documentMeta?.emailReplyTo || undefined, subject: envelope.documentMeta?.subject ?? '', message: envelope.documentMeta?.message ?? '', distributionMethod: envelope.documentMeta?.distributionMethod || DocumentDistributionMethod.EMAIL, }, }, resolver: zodResolver(ZEnvelopeDistributeFormSchema), }); const { handleSubmit, setValue, watch, formState: { isSubmitting }, } = form; const { data: emailData, isLoading: isLoadingEmails } = trpc.enterprise.organisation.email.find.useQuery({ organisationId: organisation.id, perPage: 100, }); const emails = emailData?.data || []; const distributionMethod = watch('meta.distributionMethod'); const recipientsMissingSignatureFields = useMemo( () => envelope.recipients.filter( (recipient) => recipient.role === RecipientRole.SIGNER && !envelope.fields.some( (field) => field.type === FieldType.SIGNATURE && field.recipientId === recipient.id, ), ), [envelope.recipients, envelope.fields], ); const invalidEnvelopeCode = useMemo(() => { if (recipientsMissingSignatureFields.length > 0) { return 'MISSING_SIGNATURES'; } if (envelope.recipients.length === 0) { return 'MISSING_RECIPIENTS'; } return null; }, [envelope.recipients, envelope.fields, recipientsMissingSignatureFields]); const onFormSubmit = async ({ meta }: TEnvelopeDistributeFormSchema) => { try { await distributeEnvelope({ envelopeId: envelope.id, meta }); await onDistribute?.(); let redirectPath = `${documentRootPath}/${envelope.id}`; if (meta.distributionMethod === DocumentDistributionMethod.NONE) { redirectPath += '?action=copy-links'; } await navigate(redirectPath); toast({ title: t`Envelope distributed`, description: t`Your envelope has been distributed successfully.`, duration: 5000, }); setIsOpen(false); } catch (err) { toast({ title: t`Something went wrong`, description: t`This envelope could not be distributed at this time. Please try again.`, variant: 'destructive', duration: 7500, }); } }; if (envelope.status !== DocumentStatus.DRAFT || envelope.type !== EnvelopeType.DOCUMENT) { return null; } return ( {trigger} Send Document Recipients will be able to sign the document once sent {!invalidEnvelopeCode ? (
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions setValue('meta.distributionMethod', value as DocumentDistributionMethod) } value={distributionMethod} className="mb-2" > Email None
{distributionMethod === DocumentDistributionMethod.EMAIL && (
{organisation.organisationClaim.flags.emailDomains && ( ( Email Sender )} /> )} ( Reply To Email{' '} (Optional) )} /> ( Subject{' '} (Optional) )} /> ( Message{' '} (Optional)