import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect'; import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer'; import { Trans, useLingui } from '@lingui/react/macro'; import type { Field, Recipient } from '@prisma/client'; import { FieldType, SigningStatus } from '@prisma/client'; import { CalendarDaysIcon, CheckSquareIcon, ChevronDownIcon, ContactIcon, DiscIcon, EyeOffIcon, HashIcon, LockIcon, MailIcon, TypeIcon, UserIcon, } from 'lucide-react'; import { type ElementType, useCallback, useEffect, useState } from 'react'; import { isTemplateRecipientEmailPlaceholder } from '../../../lib/constants/template'; import { extractInitials } from '../../../lib/utils/recipient-formatter'; import { SignatureIcon } from '../../icons/signature'; import { cn } from '../../lib/utils'; import { Avatar, AvatarFallback } from '../../primitives/avatar'; import { FRIENDLY_FIELD_TYPE } from '../../primitives/document-flow/types'; import { PopoverHover } from '../../primitives/popover'; interface EnvelopeRecipientFieldTooltipProps { field: Pick< Field, 'id' | 'inserted' | 'positionX' | 'positionY' | 'width' | 'height' | 'page' | 'type' | 'fieldMeta' > & { recipient: Pick; }; showFieldStatus?: boolean; showRecipientTooltip?: boolean; showRecipientColors?: boolean; } const FIELD_TYPE_ICONS: Record = { [FieldType.SIGNATURE]: SignatureIcon, [FieldType.FREE_SIGNATURE]: SignatureIcon, [FieldType.INITIALS]: ContactIcon, [FieldType.TEXT]: TypeIcon, [FieldType.DATE]: CalendarDaysIcon, [FieldType.EMAIL]: MailIcon, [FieldType.NAME]: UserIcon, [FieldType.NUMBER]: HashIcon, [FieldType.RADIO]: DiscIcon, [FieldType.CHECKBOX]: CheckSquareIcon, [FieldType.DROPDOWN]: ChevronDownIcon, }; /** * Renders a tooltip for a given field. */ export function EnvelopeRecipientFieldTooltip({ field, showFieldStatus = false, showRecipientTooltip = false, showRecipientColors = false, }: EnvelopeRecipientFieldTooltipProps) { const { t } = useLingui(); const FieldIcon = FIELD_TYPE_ICONS[field.type]; const [hideField, setHideField] = useState(!showRecipientTooltip); const [coords, setCoords] = useState({ x: 0, y: 0, height: 0, width: 0, }); const calculateCoords = useCallback(() => { const $page = document.querySelector(`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`); if (!$page) { return; } const { height, width } = getBoundingClientRect($page); const fieldHeight = (Number(field.height) / 100) * height; const fieldWidth = (Number(field.width) / 100) * width; const fieldX = (Number(field.positionX) / 100) * width + Number(fieldWidth); const fieldY = (Number(field.positionY) / 100) * height; setCoords({ x: fieldX, y: fieldY, height: fieldHeight, width: fieldWidth, }); }, [field.height, field.page, field.positionX, field.positionY, field.width]); useEffect(() => { calculateCoords(); }, [calculateCoords]); useEffect(() => { const onResize = () => { calculateCoords(); }; window.addEventListener('resize', onResize); return () => { window.removeEventListener('resize', onResize); }; }, [calculateCoords]); useEffect(() => { const $page = document.querySelector(`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`); if (!$page) { return; } const observer = new ResizeObserver(() => { calculateCoords(); }); observer.observe($page); return () => { observer.disconnect(); }; }, [calculateCoords, field.page]); if (hideField) { return null; } return (
{extractInitials(field.recipient.name || field.recipient.email)} } contentProps={{ className: 'flex w-64 flex-col overflow-hidden p-0 text-sm', sideOffset: 20, }} >

{t(FRIENDLY_FIELD_TYPE[field.type])} field

{showFieldStatus && (
{field?.fieldMeta?.readOnly ? ( <> Read Only ) : field.recipient.signingStatus === SigningStatus.SIGNED ? ( <> Signed ) : ( <> Pending )}
)}

{field.recipient.name || field.recipient.email}

{!isTemplateRecipientEmailPlaceholder(field.recipient.email) && field.recipient.name && (

{field.recipient.email}

)}
); }