1
- // import { getCurrentHub } from '@sentry/core';
1
+ import { getCurrentHub } from '@sentry/core' ;
2
2
import type { Integration } from '@sentry/types' ;
3
3
import { isNodeEnv } from '@sentry/utils' ;
4
4
5
- import type { FeedbackConfigurationWithDefaults } from './types' ;
5
+ import { sendFeedback } from './sendFeedback' ;
6
+ import type { FeedbackConfigurationWithDefaults , FeedbackFormData } from './types' ;
6
7
import { sendFeedbackRequest } from './util/sendFeedbackRequest' ;
7
8
import { Actor } from './widget/Actor' ;
8
9
import { createActorStyles } from './widget/Actor.css' ;
9
10
import { Dialog } from './widget/Dialog' ;
10
11
import { createDialogStyles } from './widget/Dialog.css' ;
12
+ import { SuccessMessage } from './widget/SuccessMessage' ;
11
13
12
14
export { sendFeedbackRequest } ;
13
15
@@ -29,10 +31,14 @@ const THEME = {
29
31
light : {
30
32
background : '#ffffff' ,
31
33
foreground : '#2B2233' ,
34
+ success : '#268d75' ,
35
+ error : '#df3338' ,
32
36
} ,
33
37
dark : {
34
38
background : '#29232f' ,
35
39
foreground : '#EBE6EF' ,
40
+ success : '#2da98c' ,
41
+ error : '#f55459' ,
36
42
} ,
37
43
} ;
38
44
@@ -76,6 +82,11 @@ export class Feedback implements Integration {
76
82
*/
77
83
private _isDialogOpen : boolean ;
78
84
85
+ /**
86
+ * Tracks if dialog has ever been opened at least one time
87
+ */
88
+ private _hasDialogOpened : boolean ;
89
+
79
90
public constructor ( {
80
91
showEmail = true ,
81
92
showName = true ,
@@ -97,6 +108,7 @@ export class Feedback implements Integration {
97
108
messageLabel = 'Description' ,
98
109
namePlaceholder = 'Your Name' ,
99
110
nameLabel = 'Name' ,
111
+ successMessageText = 'Thank you for your report!' ,
100
112
} : Partial < FeedbackConfigurationWithDefaults > = { } ) {
101
113
// Initializations
102
114
this . name = Feedback . id ;
@@ -105,6 +117,7 @@ export class Feedback implements Integration {
105
117
this . _host = null ;
106
118
this . _shadow = null ;
107
119
this . _isDialogOpen = false ;
120
+ this . _hasDialogOpened = false ;
108
121
109
122
this . options = {
110
123
isAnonymous,
@@ -124,6 +137,7 @@ export class Feedback implements Integration {
124
137
messagePlaceholder,
125
138
nameLabel,
126
139
namePlaceholder,
140
+ successMessageText,
127
141
} ;
128
142
129
143
// TOOD: temp for testing;
@@ -164,21 +178,60 @@ export class Feedback implements Integration {
164
178
this . _shadow = this . _createShadowHost ( ) ;
165
179
}
166
180
167
- this . _shadow . appendChild ( createDialogStyles ( document , THEME ) ) ;
168
- this . _dialog = Dialog ( { onCancel : this . closeDialog , options : this . options } ) ;
181
+ // Lazy-load until dialog is opened and only inject styles once
182
+ if ( ! this . _hasDialogOpened ) {
183
+ this . _shadow . appendChild ( createDialogStyles ( document , THEME ) ) ;
184
+ }
185
+
186
+ const userKey = this . options . useSentryUser ;
187
+ const scope = getCurrentHub ( ) . getScope ( ) ;
188
+ const user = scope && scope . getUser ( ) ;
189
+
190
+ this . _dialog = Dialog ( {
191
+ defaultName : ( userKey && user && user [ userKey . name ] ) || '' ,
192
+ defaultEmail : ( userKey && user && user [ userKey . email ] ) || '' ,
193
+ onClose : ( ) => {
194
+ this . showActor ( ) ;
195
+ } ,
196
+ onCancel : ( ) => {
197
+ this . hideDialog ( ) ;
198
+ this . showActor ( ) ;
199
+ } ,
200
+ onSubmit : this . _handleFeedbackSubmit ,
201
+ options : this . options ,
202
+ } ) ;
169
203
this . _shadow . appendChild ( this . _dialog . $el ) ;
204
+
205
+ // Hides the default actor whenever dialog is opened
170
206
this . _actor && this . _actor . hide ( ) ;
207
+
208
+ this . _hasDialogOpened = true ;
171
209
}
172
210
173
211
/**
174
- * Closes the dialog
212
+ * Hides the dialog
175
213
*/
176
- public closeDialog = ( ) : void => {
214
+ public hideDialog = ( ) : void => {
177
215
if ( this . _dialog ) {
178
216
this . _dialog . close ( ) ;
179
217
}
218
+ } ;
219
+
220
+ /**
221
+ * Removes the dialog element from DOM
222
+ */
223
+ public removeDialog = ( ) : void => {
224
+ if ( this . _dialog ) {
225
+ this . _dialog . $el . remove ( ) ;
226
+ this . _dialog = null ;
227
+ }
228
+ } ;
180
229
181
- // TODO: if has default actor, show the button
230
+ /**
231
+ * Displays the default actor
232
+ */
233
+ public showActor = ( ) : void => {
234
+ // TODO: Only show default actor
182
235
if ( this . _actor ) {
183
236
this . _actor . show ( ) ;
184
237
}
@@ -218,7 +271,12 @@ export class Feedback implements Integration {
218
271
this . _host . id = 'sentry-feedback' ;
219
272
220
273
// Create the shadow root
221
- return this . _host . attachShadow ( { mode : 'open' } ) ;
274
+ const shadow = this . _host . attachShadow ( { mode : 'open' } ) ;
275
+
276
+ // Insert styles for actor
277
+ shadow . appendChild ( createActorStyles ( document , THEME ) ) ;
278
+
279
+ return shadow ;
222
280
}
223
281
224
282
/**
@@ -230,7 +288,6 @@ export class Feedback implements Integration {
230
288
return ;
231
289
}
232
290
233
- // Insert styles for actor
234
291
this . _shadow . appendChild ( createActorStyles ( document , THEME ) ) ;
235
292
236
293
// Create Actor component
@@ -239,6 +296,34 @@ export class Feedback implements Integration {
239
296
this . _shadow . appendChild ( this . _actor . $el ) ;
240
297
}
241
298
299
+ /**
300
+ * Show the success message for 5 seconds
301
+ */
302
+ protected _showSuccessMessage ( ) : void {
303
+ if ( ! this . _shadow ) {
304
+ return ;
305
+ }
306
+
307
+ const success = SuccessMessage ( {
308
+ message : this . options . successMessageText ,
309
+ onRemove : ( ) => {
310
+ if ( timeoutId ) {
311
+ clearTimeout ( timeoutId ) ;
312
+ }
313
+ this . showActor ( ) ;
314
+ } ,
315
+ theme : THEME ,
316
+ } ) ;
317
+
318
+ this . _shadow . appendChild ( success . $el ) ;
319
+
320
+ const timeoutId = setTimeout ( ( ) => {
321
+ if ( success ) {
322
+ success . remove ( ) ;
323
+ }
324
+ } , 5000 ) ;
325
+ }
326
+
242
327
/**
243
328
* Handles when the actor is clicked, opens the dialog modal and calls any
244
329
* callbacks.
@@ -254,4 +339,37 @@ export class Feedback implements Integration {
254
339
this . _actor . hide ( ) ;
255
340
}
256
341
} ;
342
+
343
+ /**
344
+ * Handler for when the feedback form is completed by the user. This will
345
+ * create and send the feedback message as an event.
346
+ */
347
+ protected _handleFeedbackSubmit = async ( feedback : FeedbackFormData ) : Promise < void > => {
348
+ console . log ( 'ahndle feedback submit' ) ;
349
+ if ( ! this . _dialog ) {
350
+ // Not sure when this would happen
351
+ return ;
352
+ }
353
+
354
+ try {
355
+ this . _dialog . hideError ( ) ;
356
+ this . _dialog . setSubmitDisabled ( ) ;
357
+ const resp = await sendFeedback ( feedback ) ;
358
+ console . log ( { resp } ) ;
359
+ if ( resp ) {
360
+ // Success!
361
+ this . removeDialog ( ) ;
362
+ this . _showSuccessMessage ( ) ;
363
+ return ;
364
+ }
365
+
366
+ // Errored... re-enable submit button
367
+ this . _dialog . setSubmitEnabled ( ) ;
368
+ this . _dialog . showError ( 'There was a problem submitting feedback, please wait and try again.' ) ;
369
+ } catch {
370
+ // Errored... re-enable submit button
371
+ this . _dialog . setSubmitEnabled ( ) ;
372
+ this . _dialog . showError ( 'There was a problem submitting feedback, please wait and try again.' ) ;
373
+ }
374
+ } ;
257
375
}
0 commit comments