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' ;
13- import { cn } from '@/lib/utils' ;
12+
13+ const PopupViewMotion = motion . create ( PopupView ) ;
1414
1515export type EmbedFixedAgentClientProps = {
1616 appConfig : AppConfig ;
1717} ;
1818
19- function EmbedFixedAgentClient ( { appConfig } : EmbedFixedAgentClientProps ) {
19+ function AgentClient ( { appConfig } : EmbedFixedAgentClientProps ) {
20+ const isAnimating = useRef ( false ) ;
2021 const room = useMemo ( ( ) => new Room ( ) , [ ] ) ;
2122 const [ popupOpen , setPopupOpen ] = useState ( false ) ;
22- const [ currentError , setCurrentError ] = useState < EmbedErrorDetails | null > ( null ) ;
23+ const [ error , setError ] = useState < EmbedErrorDetails | null > ( null ) ;
2324 const { connectionDetails, refreshConnectionDetails } = useConnectionDetails ( ) ;
2425
2526 const handleTogglePopup = ( ) => {
27+ if ( isAnimating . current ) {
28+ // prevent re-opening before room has disconnected
29+ return ;
30+ }
31+
2632 setPopupOpen ( ( open ) => ! open ) ;
2733
28- if ( currentError ) {
29- handleDismissError ( ) ;
34+ if ( error ) {
35+ setError ( null ) ;
3036 }
3137 } ;
3238
3339 const handleDismissError = ( ) => {
3440 room . disconnect ( ) ;
35- setCurrentError ( null ) ;
41+ setError ( null ) ;
42+ } ;
43+
44+ const handlePanelAnimationStart = ( ) => {
45+ isAnimating . current = true ;
46+ } ;
47+
48+ const handlePanelAnimationComplete = ( ) => {
49+ isAnimating . current = false ;
50+ if ( ! popupOpen && room . state !== 'disconnected' ) {
51+ room . disconnect ( ) ;
52+ }
3653 } ;
3754
3855 useEffect ( ( ) => {
@@ -41,7 +58,7 @@ function EmbedFixedAgentClient({ appConfig }: EmbedFixedAgentClientProps) {
4158 refreshConnectionDetails ( ) ;
4259 } ;
4360 const onMediaDevicesError = ( error : Error ) => {
44- setCurrentError ( {
61+ setError ( {
4562 title : 'Encountered an error with your media devices' ,
4663 description : `${ error . name } : ${ error . message } ` ,
4764 } ) ;
@@ -74,26 +91,23 @@ function EmbedFixedAgentClient({ appConfig }: EmbedFixedAgentClientProps) {
7491 } catch ( error : unknown ) {
7592 if ( error instanceof Error ) {
7693 console . error ( 'Error connecting to agent:' , error ) ;
77- setCurrentError ( {
94+ setError ( {
7895 title : 'There was an error connecting to the agent' ,
7996 description : `${ error . name } : ${ error . message } ` ,
8097 } ) ;
8198 }
8299 }
83100 } ;
84- connect ( ) ;
85101
86- return ( ) => {
87- room . disconnect ( ) ;
88- } ;
102+ connect ( ) ;
89103 } , [ room , popupOpen , connectionDetails , appConfig . isPreConnectBufferEnabled ] ) ;
90104
91105 return (
92106 < RoomContext . Provider value = { room } >
93107 < RoomAudioRenderer />
94108 < StartAudio label = "Start Audio" />
95109
96- < Trigger error = { ! ! currentError } popupOpen = { popupOpen } onToggle = { handleTogglePopup } />
110+ < Trigger error = { error } popupOpen = { popupOpen } onToggle = { handleTogglePopup } />
97111
98112 < motion . div
99113 inert = { ! popupOpen }
@@ -107,58 +121,33 @@ function EmbedFixedAgentClient({ appConfig }: EmbedFixedAgentClientProps) {
107121 } }
108122 transition = { {
109123 type : 'spring' ,
110- duration : 1 ,
111124 bounce : 0 ,
125+ duration : popupOpen ? 1 : 0.2 ,
112126 } }
127+ onAnimationStart = { handlePanelAnimationStart }
128+ onAnimationComplete = { handlePanelAnimationComplete }
113129 className = "fixed right-0 bottom-20 z-50 w-full px-4"
114130 >
115131 < 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]" >
116132 < 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
145- 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 >
133+ < ErrorMessage error = { error } handleDismissError = { handleDismissError } />
134+ < PopupViewMotion
135+ initial = { { opacity : 1 } }
136+ animate = { { opacity : error === null ? 1 : 0 } }
137+ transition = { {
138+ type : 'linear' ,
139+ duration : 0.2 ,
140+ } }
141+ disabled = { ! popupOpen }
142+ sessionStarted = { popupOpen }
143+ onEmbedError = { setError }
144+ className = "absolute inset-0"
145+ />
157146 </ div >
158147 </ div >
159148 </ motion . div >
160149 </ RoomContext . Provider >
161150 ) ;
162151}
163152
164- export default EmbedFixedAgentClient ;
153+ export default AgentClient ;
0 commit comments