From 5c7768c2538eff1493bed651469c515a089dc238 Mon Sep 17 00:00:00 2001 From: Mythie Date: Thu, 27 Feb 2025 11:43:42 +1100 Subject: [PATCH] fix: improve embed mobile experience --- .../app/embed/direct/[[...url]]/client.tsx | 85 +++++++++++++++--- .../src/app/embed/sign/[[...url]]/client.tsx | 86 ++++++++++++++++--- 2 files changed, 143 insertions(+), 28 deletions(-) diff --git a/apps/web/src/app/embed/direct/[[...url]]/client.tsx b/apps/web/src/app/embed/direct/[[...url]]/client.tsx index 5ff905e39..7788ab012 100644 --- a/apps/web/src/app/embed/direct/[[...url]]/client.tsx +++ b/apps/web/src/app/embed/direct/[[...url]]/client.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useEffect, useLayoutEffect, useState } from 'react'; +import { useEffect, useLayoutEffect, useRef, useState } from 'react'; import { useSearchParams } from 'next/navigation'; @@ -83,12 +83,15 @@ export const EmbedDirectTemplateClientPage = ({ const [hasCompletedDocument, setHasCompletedDocument] = useState(false); const [isExpanded, setIsExpanded] = useState(false); + const [hasAutoExpanded, setHasAutoExpanded] = useState(false); const [isEmailLocked, setIsEmailLocked] = useState(false); const [isNameLocked, setIsNameLocked] = useState(false); const [showPendingFieldTooltip, setShowPendingFieldTooltip] = useState(false); + const documentEndRef = useRef(null); + const [throttledOnCompleteClick, isThrottled] = useThrottleFn(() => void onCompleteClick(), 500); const [localFields, setLocalFields] = useState(() => fields); @@ -317,6 +320,43 @@ export const EmbedDirectTemplateClientPage = ({ } }, [hasFinishedInit, hasDocumentLoaded]); + // Set up intersection observer to auto-expand widget when user reaches bottom of document + useEffect(() => { + if (!hasDocumentLoaded || !hasFinishedInit || hasAutoExpanded || isExpanded) { + return; + } + + // Add a delay to ensure document has fully rendered and stabilized + const timeoutId = setTimeout(() => { + // Get the number of pages in the document + const pageCount = document.querySelectorAll(PDF_VIEWER_PAGE_SELECTOR).length; + + // Only set up the observer if there's more than one page + if (pageCount <= 1) return; + + const observer = new IntersectionObserver( + (entries) => { + const [entry] = entries; + + if (entry.isIntersecting) { + setIsExpanded(true); + setHasAutoExpanded(true); + observer.disconnect(); + } + }, + { threshold: 1.0 }, + ); + + if (documentEndRef.current) { + observer.observe(documentEndRef.current); + } + }, 1500); // 1.5 second delay + + return () => { + clearTimeout(timeoutId); + }; + }, [hasDocumentLoaded, hasFinishedInit, hasAutoExpanded, isExpanded]); + if (hasCompletedDocument) { return ( setHasDocumentLoaded(true)} /> + {/* Observer target at the bottom of the document */} +
{/* Widget */} @@ -360,19 +402,34 @@ export const EmbedDirectTemplateClientPage = ({ Sign document - + {isExpanded ? ( + + ) : pendingFields.length > 0 ? ( + + ) : ( + + )} diff --git a/apps/web/src/app/embed/sign/[[...url]]/client.tsx b/apps/web/src/app/embed/sign/[[...url]]/client.tsx index ea024bde0..34289187d 100644 --- a/apps/web/src/app/embed/sign/[[...url]]/client.tsx +++ b/apps/web/src/app/embed/sign/[[...url]]/client.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useEffect, useId, useLayoutEffect, useState } from 'react'; +import { useEffect, useId, useLayoutEffect, useRef, useState } from 'react'; import { Trans, msg } from '@lingui/macro'; import { useLingui } from '@lingui/react'; @@ -91,11 +91,14 @@ export const EmbedSignDocumentClientPage = ({ ); const [isExpanded, setIsExpanded] = useState(false); + const [hasAutoExpanded, setHasAutoExpanded] = useState(false); const [isNameLocked, setIsNameLocked] = useState(false); const [showPendingFieldTooltip, setShowPendingFieldTooltip] = useState(false); const [allowDocumentRejection, setAllowDocumentRejection] = useState(false); + const documentEndRef = useRef(null); + const selectedSigner = allRecipients.find((r) => r.id === selectedSignerId); const isAssistantMode = recipient.role === RecipientRole.ASSISTANT; @@ -241,6 +244,42 @@ export const EmbedSignDocumentClientPage = ({ } }, [hasFinishedInit, hasDocumentLoaded]); + // Set up intersection observer to auto-expand widget when user reaches bottom of document + useEffect(() => { + if (!hasDocumentLoaded || !hasFinishedInit || hasAutoExpanded || isExpanded) { + return; + } + + // Add a delay to ensure document has fully rendered and stabilized + const timeoutId = setTimeout(() => { + const pageCount = document.querySelectorAll(PDF_VIEWER_PAGE_SELECTOR).length; + + // Only set up the observer if there's more than one page + if (pageCount <= 1) return; + + const observer = new IntersectionObserver( + (entries) => { + const [entry] = entries; + + if (entry.isIntersecting) { + setIsExpanded(true); + setHasAutoExpanded(true); + observer.disconnect(); + } + }, + { threshold: 1.0 }, + ); + + if (documentEndRef.current) { + observer.observe(documentEndRef.current); + } + }, 1500); // 1.5 second delay + + return () => { + clearTimeout(timeoutId); + }; + }, [hasDocumentLoaded, hasFinishedInit, hasAutoExpanded, isExpanded]); + if (hasRejectedDocument) { return ; } @@ -283,6 +322,8 @@ export const EmbedSignDocumentClientPage = ({ documentData={documentData} onDocumentLoad={() => setHasDocumentLoaded(true)} /> + {/* Observer target at the bottom of the document */} +
{/* Widget */} @@ -303,19 +344,36 @@ export const EmbedSignDocumentClientPage = ({ )} - + {isExpanded ? ( + + ) : pendingFields.length > 0 ? ( + + ) : ( + + )}