diff --git a/apps/remix/app/components/dialogs/document-duplicate-dialog.tsx b/apps/remix/app/components/dialogs/document-duplicate-dialog.tsx index 57146ed9f..81ec5bdf4 100644 --- a/apps/remix/app/components/dialogs/document-duplicate-dialog.tsx +++ b/apps/remix/app/components/dialogs/document-duplicate-dialog.tsx @@ -57,14 +57,14 @@ export const DocumentDuplicateDialog = ({ const { mutateAsync: duplicateDocument, isPending: isDuplicateLoading } = trpcReact.document.duplicate.useMutation({ - onSuccess: async ({ documentId }) => { + onSuccess: async ({ id }) => { toast({ title: _(msg`Document Duplicated`), description: _(msg`Your document has been successfully duplicated.`), duration: 5000, }); - await navigate(`${documentsPath}/${documentId}/edit`); + await navigate(`${documentsPath}/${id}/edit`); onOpenChange(false); }, }); diff --git a/apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx b/apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx new file mode 100644 index 000000000..a3aac4436 --- /dev/null +++ b/apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx @@ -0,0 +1,449 @@ +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 * as z from 'zod'; + +import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; +import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles'; +import type { TEnvelope } from '@documenso/lib/types/envelope'; +import { formatSigningLink } from '@documenso/lib/utils/recipients'; +import { trpc, trpc as trpcReact } from '@documenso/trpc/react'; +import { CopyTextButton } from '@documenso/ui/components/common/copy-text-button'; +import { DocumentSendEmailMessageHelper } from '@documenso/ui/components/document/document-send-email-message-helper'; +import { Alert, AlertDescription } from '@documenso/ui/primitives/alert'; +import { AvatarWithText } from '@documenso/ui/primitives/avatar'; +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: Field[]; + }; + 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 }: EnvelopeDistributeDialogProps) => { + const organisation = useCurrentOrganisation(); + + const recipients = envelope.recipients; + + const { toast } = useToast(); + const { t } = useLingui(); + + 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 everySignerHasSignature = useMemo( + () => + envelope.recipients + .filter((recipient) => recipient.role === RecipientRole.SIGNER) + .every((recipient) => + envelope.fields.some( + (field) => field.type === FieldType.SIGNATURE && field.recipientId === recipient.id, + ), + ), + [envelope.recipients, envelope.fields], + ); + + const onFormSubmit = async ({ meta }: TEnvelopeDistributeFormSchema) => { + try { + await distributeEnvelope({ envelopeId: envelope.id, meta }); + + 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 + + + {everySignerHasSignature ? ( +
+ +
+ + // 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) + + + + + + + + + + + +