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
+
32
+ setError ( null ) ;
26
33
setPopupOpen ( ( open ) => ! open ) ;
34
+ } ;
27
35
28
- if ( currentError ) {
29
- handleDismissError ( ) ;
30
- }
36
+ const handlePanelAnimationStart = ( ) => {
37
+ isAnimating . current = true ;
31
38
} ;
32
39
33
- const handleDismissError = ( ) => {
34
- room . disconnect ( ) ;
35
- setCurrentError ( null ) ;
40
+ const handlePanelAnimationComplete = ( ) => {
41
+ isAnimating . current = false ;
42
+ if ( ! popupOpen && room . state !== 'disconnected' ) {
43
+ room . disconnect ( ) ;
44
+ }
36
45
} ;
37
46
38
47
useEffect ( ( ) => {
@@ -41,7 +50,7 @@ function EmbedFixedAgentClient({ appConfig }: EmbedFixedAgentClientProps) {
41
50
refreshConnectionDetails ( ) ;
42
51
} ;
43
52
const onMediaDevicesError = ( error : Error ) => {
44
- setCurrentError ( {
53
+ setError ( {
45
54
title : 'Encountered an error with your media devices' ,
46
55
description : `${ error . name } : ${ error . message } ` ,
47
56
} ) ;
@@ -58,10 +67,14 @@ function EmbedFixedAgentClient({ appConfig }: EmbedFixedAgentClientProps) {
58
67
if ( ! popupOpen ) {
59
68
return ;
60
69
}
61
- if ( room . state !== 'disconnected' ) {
70
+ if ( ! connectionDetails ) {
71
+ setError ( {
72
+ title : 'Error fetching connection details' ,
73
+ description : 'Please try again later' ,
74
+ } ) ;
62
75
return ;
63
76
}
64
- if ( ! connectionDetails ) {
77
+ if ( room . state !== 'disconnected' ) {
65
78
return ;
66
79
}
67
80
@@ -74,26 +87,23 @@ function EmbedFixedAgentClient({ appConfig }: EmbedFixedAgentClientProps) {
74
87
} catch ( error : unknown ) {
75
88
if ( error instanceof Error ) {
76
89
console . error ( 'Error connecting to agent:' , error ) ;
77
- setCurrentError ( {
90
+ setError ( {
78
91
title : 'There was an error connecting to the agent' ,
79
92
description : `${ error . name } : ${ error . message } ` ,
80
93
} ) ;
81
94
}
82
95
}
83
96
} ;
84
- connect ( ) ;
85
97
86
- return ( ) => {
87
- room . disconnect ( ) ;
88
- } ;
98
+ connect ( ) ;
89
99
} , [ room , popupOpen , connectionDetails , appConfig . isPreConnectBufferEnabled ] ) ;
90
100
91
101
return (
92
102
< RoomContext . Provider value = { room } >
93
103
< RoomAudioRenderer />
94
104
< StartAudio label = "Start Audio" />
95
105
96
- < Trigger error = { ! ! currentError } popupOpen = { popupOpen } onToggle = { handleTogglePopup } />
106
+ < Trigger error = { error } popupOpen = { popupOpen } onToggle = { handleTogglePopup } />
97
107
98
108
< motion . div
99
109
inert = { ! popupOpen }
@@ -107,58 +117,35 @@ function EmbedFixedAgentClient({ appConfig }: EmbedFixedAgentClientProps) {
107
117
} }
108
118
transition = { {
109
119
type : 'spring' ,
110
- duration : 1 ,
111
120
bounce : 0 ,
121
+ duration : popupOpen ? 1 : 0.2 ,
112
122
} }
123
+ onAnimationStart = { handlePanelAnimationStart }
124
+ onAnimationComplete = { handlePanelAnimationComplete }
113
125
className = "fixed right-0 bottom-20 z-50 w-full px-4"
114
126
>
115
127
< 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
128
< 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
129
+ < ErrorMessage error = { error } />
130
+ { ! error && (
131
+ < PopupViewMotion
132
+ initial = { { opacity : 1 } }
133
+ animate = { { opacity : error === null ? 1 : 0 } }
134
+ transition = { {
135
+ type : 'linear' ,
136
+ duration : 0.2 ,
137
+ } }
152
138
disabled = { ! popupOpen }
153
139
sessionStarted = { popupOpen }
154
- onDisplayError = { setCurrentError }
140
+ onEmbedError = { setError }
141
+ className = "absolute inset-0"
155
142
/>
156
- </ div >
143
+ ) }
157
144
</ div >
158
145
</ div >
159
146
</ motion . div >
160
147
</ RoomContext . Provider >
161
148
) ;
162
149
}
163
150
164
- export default EmbedFixedAgentClient ;
151
+ export default AgentClient ;
0 commit comments