import { useEffect, useMemo, useState } from 'react'; import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; import { type Recipient, RecipientRole, type Template, type TemplateDirectLink, } from '@prisma/client'; import { CircleDotIcon, CircleIcon, ClipboardCopyIcon, InfoIcon, LoaderIcon } from 'lucide-react'; import { Link, useRevalidator } from 'react-router'; import { P, match } from 'ts-pattern'; import { useLimits } from '@documenso/ee/server-only/limits/provider/client'; import { useCopyToClipboard } from '@documenso/lib/client-only/hooks/use-copy-to-clipboard'; import { DIRECT_TEMPLATE_RECIPIENT_EMAIL } from '@documenso/lib/constants/direct-templates'; import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles'; import { DIRECT_TEMPLATE_DOCUMENTATION } from '@documenso/lib/constants/template'; import { formatDirectTemplatePath } from '@documenso/lib/utils/templates'; import { trpc as trpcReact } from '@documenso/trpc/react'; import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out'; import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert'; import { Button } from '@documenso/ui/primitives/button'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@documenso/ui/primitives/dialog'; import { Input } from '@documenso/ui/primitives/input'; import { Label } from '@documenso/ui/primitives/label'; import { Switch } from '@documenso/ui/primitives/switch'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@documenso/ui/primitives/table'; import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip'; import { useToast } from '@documenso/ui/primitives/use-toast'; type TemplateDirectLinkDialogProps = { template: Template & { directLink?: Pick | null; recipients: Recipient[]; }; open: boolean; onOpenChange: (_open: boolean) => void; }; type TemplateDirectLinkStep = 'ONBOARD' | 'SELECT_RECIPIENT' | 'MANAGE' | 'CONFIRM_DELETE'; export const TemplateDirectLinkDialog = ({ template, open, onOpenChange, }: TemplateDirectLinkDialogProps) => { const { toast } = useToast(); const { quota, remaining } = useLimits(); const { _ } = useLingui(); const { revalidate } = useRevalidator(); const [, copy] = useCopyToClipboard(); const [isEnabled, setIsEnabled] = useState(template.directLink?.enabled ?? false); const [token, setToken] = useState(template.directLink?.token ?? null); const [selectedRecipientId, setSelectedRecipientId] = useState(null); const [currentStep, setCurrentStep] = useState( token ? 'MANAGE' : 'ONBOARD', ); const validDirectTemplateRecipients = useMemo( () => template.recipients.filter((recipient) => recipient.role !== RecipientRole.CC), [template.recipients], ); const { mutateAsync: createTemplateDirectLink, isPending: isCreatingTemplateDirectLink, reset: resetCreateTemplateDirectLink, } = trpcReact.template.createTemplateDirectLink.useMutation({ onSuccess: async (data) => { await revalidate(); setToken(data.token); setIsEnabled(data.enabled); setCurrentStep('MANAGE'); }, onError: () => { setSelectedRecipientId(null); toast({ title: _(msg`Something went wrong`), description: _(msg`Unable to create direct template access. Please try again later.`), variant: 'destructive', }); }, }); const { mutateAsync: toggleTemplateDirectLink, isPending: isTogglingTemplateAccess } = trpcReact.template.toggleTemplateDirectLink.useMutation({ onSuccess: async (data) => { await revalidate(); const enabledDescription = msg`Direct link signing has been enabled`; const disabledDescription = msg`Direct link signing has been disabled`; toast({ title: _(msg`Success`), description: _(data.enabled ? enabledDescription : disabledDescription), }); }, onError: (_ctx, data) => { const enabledDescription = msg`An error occurred while enabling direct link signing.`; const disabledDescription = msg`An error occurred while disabling direct link signing.`; toast({ title: _(msg`Something went wrong`), description: _(data.enabled ? enabledDescription : disabledDescription), variant: 'destructive', }); }, }); const { mutateAsync: deleteTemplateDirectLink, isPending: isDeletingTemplateDirectLink } = trpcReact.template.deleteTemplateDirectLink.useMutation({ onSuccess: async () => { await revalidate(); onOpenChange(false); setToken(null); toast({ title: _(msg`Success`), description: _(msg`Direct template link deleted`), duration: 5000, }); setToken(null); }, onError: () => { toast({ title: _(msg`Something went wrong`), description: _( msg`We encountered an error while removing the direct template link. Please try again later.`, ), variant: 'destructive', }); }, }); const onCopyClick = async (token: string) => copy(formatDirectTemplatePath(token)).then(() => { toast({ title: _(msg`Copied to clipboard`), description: _(msg`The direct link has been copied to your clipboard`), }); }); const onRecipientTableRowClick = async (recipientId: number) => { if (isLoading) { return; } setSelectedRecipientId(recipientId); await createTemplateDirectLink({ templateId: template.id, directRecipientId: recipientId, }); }; const isLoading = isCreatingTemplateDirectLink || isTogglingTemplateAccess || isDeletingTemplateDirectLink; useEffect(() => { resetCreateTemplateDirectLink(); setCurrentStep(token ? 'MANAGE' : 'ONBOARD'); setSelectedRecipientId(null); // eslint-disable-next-line react-hooks/exhaustive-deps }, [open]); return ( !isLoading && onOpenChange(value)}>
{match({ token, currentStep }) .with({ token: P.nullish, currentStep: 'ONBOARD' }, () => ( Create Direct Signing Link Here's how it works:
    {DIRECT_TEMPLATE_DOCUMENTATION.map((step, index) => (
  • {index + 1}

    {_(step.title)}

    {_(step.description)}

  • ))}
{remaining.directTemplates === 0 && ( Direct template link usage exceeded ({quota.directTemplates}/ {quota.directTemplates}) You have reached the maximum limit of {quota.directTemplates} direct templates.{' '} Upgrade your account to continue! )} {remaining.directTemplates !== 0 && ( )}
)) .with({ token: P.nullish, currentStep: 'SELECT_RECIPIENT' }, () => ( {isCreatingTemplateDirectLink && validDirectTemplateRecipients.length !== 0 && (
)} Choose Direct Link Recipient Choose an existing recipient from below to continue
Recipient Role {validDirectTemplateRecipients.length === 0 && (

No valid recipients found

)} {validDirectTemplateRecipients.map((row) => ( onRecipientTableRowClick(row.id)} >

{row.name}

{row.email}

{_(RECIPIENT_ROLES_DESCRIPTION[row.role].roleName)} {selectedRecipientId === row.id ? ( ) : ( )}
))}
{/* Prevent creating placeholder direct template recipient if the email already exists. */} {!template.recipients.some( (recipient) => recipient.email === DIRECT_TEMPLATE_RECIPIENT_EMAIL, ) && (
{validDirectTemplateRecipients.length !== 0 && (

Or

)}
)}
)) .with({ token: P.string, currentStep: 'MANAGE' }, ({ token }) => ( Direct Link Signing Manage the direct link signing for this template
setIsEnabled(value)} />
)) .with({ token: P.string, currentStep: 'CONFIRM_DELETE' }, () => ( Are you sure? Please note that proceeding will remove direct linking recipient and turn it into a placeholder. )) .otherwise(() => null)}
); };