1
1
'use client' ;
2
2
3
- import { useEffect , useMemo , useState } from 'react' ;
3
+ import { useEffect , useMemo , useRef , useState } from 'react' ;
4
4
import { Room , RoomEvent } from 'livekit-client' ;
5
5
import { motion } from 'motion/react' ;
6
6
import { RoomAudioRenderer , RoomContext , StartAudio } from '@livekit/components-react' ;
7
- import { XIcon } from '@phosphor-icons/react ' ;
7
+ import { ErrorMessage } from '@/components/embed-popup/error-message ' ;
8
8
import { PopupView } from '@/components/embed-popup/popup-view' ;
9
9
import { Trigger } from '@/components/embed-popup/trigger' ;
10
- import { Button } from '@/components/ui/button' ;
11
10
import useConnectionDetails from '@/hooks/use-connection-details' ;
12
11
import { type AppConfig , EmbedErrorDetails } from '@/lib/types' ;
13
- import { cn } from '@/lib/utils' ;
12
+
13
+ const PopupViewMotion = motion . create ( PopupView ) ;
14
14
15
15
export type EmbedFixedAgentClientProps = {
16
16
appConfig : AppConfig ;
17
17
} ;
18
18
19
- function EmbedFixedAgentClient ( { appConfig } : EmbedFixedAgentClientProps ) {
19
+ function AgentClient ( { appConfig } : EmbedFixedAgentClientProps ) {
20
+ const isAnimating = useRef ( false ) ;
20
21
const room = useMemo ( ( ) => new Room ( ) , [ ] ) ;
21
22
const [ popupOpen , setPopupOpen ] = useState ( false ) ;
22
- const [ currentError , setCurrentError ] = useState < EmbedErrorDetails | null > ( null ) ;
23
+ const [ error , setError ] = useState < EmbedErrorDetails | null > ( null ) ;
23
24
const { connectionDetails, refreshConnectionDetails } = useConnectionDetails ( ) ;
24
25
25
26
const handleTogglePopup = ( ) => {
27
+ if ( isAnimating . current ) {
28
+ // prevent re-opening before room has disconnected
29
+ return ;
30
+ }
31
+
26
32
setPopupOpen ( ( open ) => ! open ) ;
27
33
28
- if ( currentError ) {
29
- handleDismissError ( ) ;
34
+ if ( error ) {
35
+ setError ( null ) ;
30
36
}
31
37
} ;
32
38
33
39
const handleDismissError = ( ) => {
34
40
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
+ }
36
53
} ;
37
54
38
55
useEffect ( ( ) => {
@@ -41,7 +58,7 @@ function EmbedFixedAgentClient({ appConfig }: EmbedFixedAgentClientProps) {
41
58
refreshConnectionDetails ( ) ;
42
59
} ;
43
60
const onMediaDevicesError = ( error : Error ) => {
44
- setCurrentError ( {
61
+ setError ( {
45
62
title : 'Encountered an error with your media devices' ,
46
63
description : `${ error . name } : ${ error . message } ` ,
47
64
} ) ;
@@ -74,26 +91,23 @@ function EmbedFixedAgentClient({ appConfig }: EmbedFixedAgentClientProps) {
74
91
} catch ( error : unknown ) {
75
92
if ( error instanceof Error ) {
76
93
console . error ( 'Error connecting to agent:' , error ) ;
77
- setCurrentError ( {
94
+ setError ( {
78
95
title : 'There was an error connecting to the agent' ,
79
96
description : `${ error . name } : ${ error . message } ` ,
80
97
} ) ;
81
98
}
82
99
}
83
100
} ;
84
- connect ( ) ;
85
101
86
- return ( ) => {
87
- room . disconnect ( ) ;
88
- } ;
102
+ connect ( ) ;
89
103
} , [ room , popupOpen , connectionDetails , appConfig . isPreConnectBufferEnabled ] ) ;
90
104
91
105
return (
92
106
< RoomContext . Provider value = { room } >
93
107
< RoomAudioRenderer />
94
108
< StartAudio label = "Start Audio" />
95
109
96
- < Trigger error = { ! ! currentError } popupOpen = { popupOpen } onToggle = { handleTogglePopup } />
110
+ < Trigger error = { error } popupOpen = { popupOpen } onToggle = { handleTogglePopup } />
97
111
98
112
< motion . div
99
113
inert = { ! popupOpen }
@@ -107,58 +121,33 @@ function EmbedFixedAgentClient({ appConfig }: EmbedFixedAgentClientProps) {
107
121
} }
108
122
transition = { {
109
123
type : 'spring' ,
110
- duration : 1 ,
111
124
bounce : 0 ,
125
+ duration : popupOpen ? 1 : 0.2 ,
112
126
} }
127
+ onAnimationStart = { handlePanelAnimationStart }
128
+ onAnimationComplete = { handlePanelAnimationComplete }
113
129
className = "fixed right-0 bottom-20 z-50 w-full px-4"
114
130
>
115
131
< 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]" >
116
132
< 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
+ />
157
146
</ div >
158
147
</ div >
159
148
</ motion . div >
160
149
</ RoomContext . Provider >
161
150
) ;
162
151
}
163
152
164
- export default EmbedFixedAgentClient ;
153
+ export default AgentClient ;
0 commit comments