From b4726a93c4f7d0da66b17652cc7964a1f78d67ea Mon Sep 17 00:00:00 2001 From: kushagrasarathe <76868364+kushagrasarathe@users.noreply.github.com> Date: Fri, 20 Mar 2026 16:12:23 +0530 Subject: [PATCH 1/9] fix: replace vaul drawer with always-mounted panel for support chat iframe no longer unmounts on close, eliminating 4-5s reload delay. added touch drag-to-dismiss on handle bar. --- src/components/Global/SupportDrawer/index.tsx | 117 ++++++++++++++---- 1 file changed, 90 insertions(+), 27 deletions(-) diff --git a/src/components/Global/SupportDrawer/index.tsx b/src/components/Global/SupportDrawer/index.tsx index 74e2eb5a7..a6d311772 100644 --- a/src/components/Global/SupportDrawer/index.tsx +++ b/src/components/Global/SupportDrawer/index.tsx @@ -1,26 +1,52 @@ 'use client' -import { useState, useEffect } from 'react' +import { useState, useEffect, useRef, useCallback } from 'react' import { useModalsContext } from '@/context/ModalsContext' import { useCrispUserData } from '@/hooks/useCrispUserData' import { useCrispProxyUrl } from '@/hooks/useCrispProxyUrl' -import { Drawer, DrawerContent, DrawerTitle } from '../Drawer' import PeanutLoading from '../PeanutLoading' +const DISMISS_THRESHOLD = 100 + const SupportDrawer = () => { const { isSupportModalOpen, setIsSupportModalOpen, supportPrefilledMessage: prefilledMessage } = useModalsContext() const userData = useCrispUserData() - const [isLoading, setIsLoading] = useState(true) + const [isCrispReady, setIsCrispReady] = useState(false) const crispProxyUrl = useCrispProxyUrl(userData, prefilledMessage) + // drag-to-dismiss state + const panelRef = useRef(null) + const dragStartY = useRef(null) + const [dragOffset, setDragOffset] = useState(0) + const isDragging = dragStartY.current !== null + + const handleTouchStart = useCallback((e: React.TouchEvent) => { + dragStartY.current = e.touches[0].clientY + }, []) + + const handleTouchMove = useCallback((e: React.TouchEvent) => { + if (dragStartY.current === null) return + const delta = e.touches[0].clientY - dragStartY.current + // only allow dragging downward + setDragOffset(Math.max(0, delta)) + }, []) + + const handleTouchEnd = useCallback(() => { + if (dragOffset > DISMISS_THRESHOLD) { + setIsSupportModalOpen(false) + } + dragStartY.current = null + setDragOffset(0) + }, [dragOffset, setIsSupportModalOpen]) + + // listen for crisp ready once — persists across open/close cycles useEffect(() => { - // Listen for ready message from proxy iframe const handleMessage = (event: MessageEvent) => { if (event.origin !== window.location.origin) return if (event.data.type === 'CRISP_READY') { - setIsLoading(false) + setIsCrispReady(true) } } @@ -28,33 +54,70 @@ const SupportDrawer = () => { return () => window.removeEventListener('message', handleMessage) }, []) - // Reset loading state when drawer closes + // close on escape useEffect(() => { - if (!isSupportModalOpen) { - setIsLoading(true) + if (!isSupportModalOpen) return + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape') setIsSupportModalOpen(false) } - }, [isSupportModalOpen]) + window.addEventListener('keydown', handleEscape) + return () => window.removeEventListener('keydown', handleEscape) + }, [isSupportModalOpen, setIsSupportModalOpen]) return ( - - - Support -
- {isLoading && ( -
- -
- )} -