11'use client' ;
22
3- import { useEffect , useMemo , useState } from 'react' ;
3+ import { useEffect , useMemo , useRef , useState } from 'react' ;
44import { Room , RoomEvent } from 'livekit-client' ;
55import { motion } from 'motion/react' ;
66import { RoomAudioRenderer , RoomContext , StartAudio } from '@livekit/components-react' ;
7- import { XIcon } from '@phosphor-icons/react ' ;
7+ import { ErrorMessage } from '@/components/embed-popup/error-message ' ;
88import { PopupView } from '@/components/embed-popup/popup-view' ;
99import { Trigger } from '@/components/embed-popup/trigger' ;
10- import { Button } from '@/components/ui/button' ;
1110import useConnectionDetails from '@/hooks/use-connection-details' ;
1211import { type AppConfig , EmbedErrorDetails } from '@/lib/types' ;
1312import { cn } from '@/lib/utils' ;
1413
14+ const PopupViewMotion = motion . create ( PopupView ) ;
15+
1516export type EmbedFixedAgentClientProps = {
1617 appConfig : AppConfig ;
1718} ;
1819
1920function EmbedFixedAgentClient ( { appConfig } : EmbedFixedAgentClientProps ) {
21+ const isAnimating = useRef ( false ) ;
2022 const room = useMemo ( ( ) => new Room ( ) , [ ] ) ;
2123 const [ popupOpen , setPopupOpen ] = useState ( false ) ;
2224 const [ currentError , setCurrentError ] = useState < EmbedErrorDetails | null > ( null ) ;
2325 const { connectionDetails, refreshConnectionDetails } = useConnectionDetails ( ) ;
2426
2527 const handleTogglePopup = ( ) => {
28+ if ( isAnimating . current ) {
29+ // prevent re-opening before room has disconnected
30+ return ;
31+ }
32+
2633 setPopupOpen ( ( open ) => ! open ) ;
2734
2835 if ( currentError ) {
29- handleDismissError ( ) ;
36+ setCurrentError ( null ) ;
3037 }
3138 } ;
3239
@@ -35,6 +42,17 @@ function EmbedFixedAgentClient({ appConfig }: EmbedFixedAgentClientProps) {
3542 setCurrentError ( null ) ;
3643 } ;
3744
45+ const handlePanelAnimationStart = ( ) => {
46+ isAnimating . current = true ;
47+ } ;
48+
49+ const handlePanelAnimationComplete = ( ) => {
50+ isAnimating . current = false ;
51+ if ( ! popupOpen && room . state !== 'disconnected' ) {
52+ room . disconnect ( ) ;
53+ }
54+ } ;
55+
3856 useEffect ( ( ) => {
3957 const onDisconnected = ( ) => {
4058 setPopupOpen ( false ) ;
@@ -81,11 +99,8 @@ function EmbedFixedAgentClient({ appConfig }: EmbedFixedAgentClientProps) {
8199 }
82100 }
83101 } ;
84- connect ( ) ;
85102
86- return ( ) => {
87- room . disconnect ( ) ;
88- } ;
103+ connect ( ) ;
89104 } , [ room , popupOpen , connectionDetails , appConfig . isPreConnectBufferEnabled ] ) ;
90105
91106 return (
@@ -107,53 +122,29 @@ function EmbedFixedAgentClient({ appConfig }: EmbedFixedAgentClientProps) {
107122 } }
108123 transition = { {
109124 type : 'spring' ,
110- duration : 1 ,
111125 bounce : 0 ,
126+ duration : 1 ,
112127 } }
128+ onAnimationStart = { handlePanelAnimationStart }
129+ onAnimationComplete = { handlePanelAnimationComplete }
113130 className = "fixed right-0 bottom-20 z-50 w-full px-4"
114131 >
115132 < div className = "bg-bg2 dark:bg-bg1 border-separator1 ml-auto h-[480px] w-full rounded-[28px] border drop-shadow-md md:max-w-[360px]" >
116133 < div className = "relative h-full w-full" >
117- < div
118- inert = { currentError === null }
119- className = { cn (
120- 'absolute inset-0 flex h-full w-full flex-col items-center justify-center gap-5 transition-opacity' ,
121- currentError === null ? 'opacity-0' : 'opacity-100'
122- ) }
123- >
124- < div className = "pl-3" >
125- { /* eslint-disable-next-line @next/next/no-img-element */ }
126- < img src = "/lk-logo.svg" alt = "LiveKit Logo" className = "block size-6 dark:hidden" />
127- { /* eslint-disable-next-line @next/next/no-img-element */ }
128- < img
129- src = "/lk-logo-dark.svg"
130- alt = "LiveKit Logo"
131- className = "hidden size-6 dark:block"
132- />
133- </ div >
134-
135- < div className = "flex w-full flex-col justify-center gap-1 overflow-auto px-4 text-center" >
136- < span className = "text-sm font-medium" > { currentError ?. title } </ span >
137- < span className = "text-xs" > { currentError ?. description } </ span >
138- </ div >
139-
140- < Button variant = "secondary" onClick = { handleDismissError } >
141- < XIcon /> Dismiss
142- </ Button >
143- </ div >
144- < div
134+ < ErrorMessage error = { currentError } handleDismissError = { handleDismissError } />
135+ < PopupViewMotion
145136 inert = { currentError !== null }
146- className = { cn (
147- 'absolute inset-0 transition-opacity' ,
148- currentError === null ? 'opacity-100' : 'opacity-0'
149- ) }
150- >
151- < PopupView
152- disabled = { ! popupOpen }
153- sessionStarted = { popupOpen }
154- onDisplayError = { setCurrentError }
155- />
156- </ div >
137+ initial = { { opacity : 1 } }
138+ animate = { { opacity : currentError === null ? 1 : 0 } }
139+ transition = { {
140+ type : 'linear' ,
141+ duration : 200 ,
142+ } }
143+ disabled = { ! popupOpen }
144+ sessionStarted = { popupOpen }
145+ onDisplayError = { setCurrentError }
146+ className = { cn ( 'absolute inset-0' ) }
147+ / >
157148 </ div >
158149 </ div >
159150 </ motion . div >
0 commit comments