@nyaruka/temba-components 0.91.6 → 0.92.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/demo/index.html +1 -1
  3. package/dist/temba-components.js +760 -1189
  4. package/dist/temba-components.js.map +1 -1
  5. package/out-tsc/src/chat/Chat.js +714 -0
  6. package/out-tsc/src/chat/Chat.js.map +1 -0
  7. package/out-tsc/src/completion/helpers.js +1 -29
  8. package/out-tsc/src/completion/helpers.js.map +1 -1
  9. package/out-tsc/src/compose/Compose.js +6 -2
  10. package/out-tsc/src/compose/Compose.js.map +1 -1
  11. package/out-tsc/src/contacts/ContactChat.js +518 -54
  12. package/out-tsc/src/contacts/ContactChat.js.map +1 -1
  13. package/out-tsc/src/contacts/events.js +1 -998
  14. package/out-tsc/src/contacts/events.js.map +1 -1
  15. package/out-tsc/src/lightbox/Lightbox.js +4 -0
  16. package/out-tsc/src/lightbox/Lightbox.js.map +1 -1
  17. package/out-tsc/src/list/TembaMenu.js +0 -1
  18. package/out-tsc/src/list/TembaMenu.js.map +1 -1
  19. package/out-tsc/src/markdown.js +33 -0
  20. package/out-tsc/src/markdown.js.map +1 -0
  21. package/out-tsc/src/select/Select.js +6 -1
  22. package/out-tsc/src/select/Select.js.map +1 -1
  23. package/out-tsc/src/textinput/TextInput.js +1 -1
  24. package/out-tsc/src/textinput/TextInput.js.map +1 -1
  25. package/out-tsc/src/thumbnail/Thumbnail.js +128 -81
  26. package/out-tsc/src/thumbnail/Thumbnail.js.map +1 -1
  27. package/out-tsc/src/utils/index.js +9 -11
  28. package/out-tsc/src/utils/index.js.map +1 -1
  29. package/out-tsc/src/webchat/WebChat.js +109 -358
  30. package/out-tsc/src/webchat/WebChat.js.map +1 -1
  31. package/out-tsc/src/webchat/index.js +17 -0
  32. package/out-tsc/src/webchat/index.js.map +1 -1
  33. package/out-tsc/temba-modules.js +2 -2
  34. package/out-tsc/temba-modules.js.map +1 -1
  35. package/out-tsc/temba-webchat.js +2 -0
  36. package/out-tsc/temba-webchat.js.map +1 -1
  37. package/out-tsc/test/temba-contact-chat.test.js +1 -0
  38. package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
  39. package/out-tsc/test/temba-lightbox.test.js +4 -4
  40. package/out-tsc/test/temba-lightbox.test.js.map +1 -1
  41. package/package.json +1 -1
  42. package/screenshots/truth/contacts/compose-attachments-no-text-failure.png +0 -0
  43. package/screenshots/truth/contacts/compose-attachments-no-text-success.png +0 -0
  44. package/screenshots/truth/contacts/compose-text-and-attachments-failure-attachments.png +0 -0
  45. package/screenshots/truth/contacts/compose-text-and-attachments-failure-generic.png +0 -0
  46. package/screenshots/truth/contacts/compose-text-and-attachments-failure-text-and-attachments.png +0 -0
  47. package/screenshots/truth/contacts/compose-text-and-attachments-failure-text.png +0 -0
  48. package/screenshots/truth/contacts/compose-text-and-attachments-success.png +0 -0
  49. package/screenshots/truth/contacts/compose-text-no-attachments-failure.png +0 -0
  50. package/screenshots/truth/contacts/compose-text-no-attachments-success.png +0 -0
  51. package/screenshots/truth/contacts/contact-active-default.png +0 -0
  52. package/screenshots/truth/contacts/contact-active-show-chatbox.png +0 -0
  53. package/screenshots/truth/contacts/contact-archived-hide-chatbox.png +0 -0
  54. package/screenshots/truth/contacts/contact-blocked-hide-chatbox.png +0 -0
  55. package/screenshots/truth/contacts/contact-stopped-hide-chatbox.png +0 -0
  56. package/screenshots/truth/lightbox/img-zoomed.png +0 -0
  57. package/screenshots/truth/lightbox/img.png +0 -0
  58. package/src/chat/Chat.ts +791 -0
  59. package/src/completion/helpers.ts +2 -40
  60. package/src/compose/Compose.ts +6 -2
  61. package/src/contacts/ContactChat.ts +609 -59
  62. package/src/contacts/events.ts +1 -1068
  63. package/src/lightbox/Lightbox.ts +5 -0
  64. package/src/list/TembaMenu.ts +0 -1
  65. package/src/markdown.ts +41 -0
  66. package/src/select/Select.ts +5 -1
  67. package/src/textinput/TextInput.ts +1 -1
  68. package/src/thumbnail/Thumbnail.ts +130 -81
  69. package/src/utils/index.ts +12 -13
  70. package/src/webchat/WebChat.ts +196 -413
  71. package/src/webchat/index.ts +23 -1
  72. package/static/css/temba-components.css +2 -0
  73. package/temba-modules.ts +2 -2
  74. package/temba-webchat.ts +2 -0
  75. package/test/temba-contact-chat.test.ts +1 -0
  76. package/test/temba-lightbox.test.ts +4 -4
  77. package/test-assets/contacts/history.json +1 -56
  78. package/out-tsc/src/contacts/ContactHistory.js +0 -691
  79. package/out-tsc/src/contacts/ContactHistory.js.map +0 -1
  80. package/out-tsc/test/temba-contact-history.test.js +0 -69
  81. package/out-tsc/test/temba-contact-history.test.js.map +0 -1
  82. package/src/contacts/ContactHistory.ts +0 -875
  83. package/test/temba-contact-history.test.ts +0 -107
@@ -1,485 +1,6 @@
1
- import { css, html, TemplateResult } from 'lit';
1
+ import { html, TemplateResult } from 'lit';
2
2
  import { Msg, ObjectReference, User } from '../interfaces';
3
- import { getClasses, oxford, oxfordFn, oxfordNamed } from '../utils';
4
3
  import { Icon } from '../vectoricon';
5
- import { getDisplayName } from './helpers';
6
-
7
- export const getEventStyles = () => {
8
- return css`
9
- .grouping {
10
- margin-top: 1em;
11
- }
12
-
13
- .grouping.verbose {
14
- background: #f9f9f9;
15
- color: var(--color-dark);
16
- --color-link-primary: rgba(38, 166, 230, 1);
17
- pointer-events: none;
18
- background: #fefefe;
19
- box-shadow: -8px 0px 8px 1px rgba(0, 0, 0, 0.05) inset;
20
- margin-right: -16px;
21
- padding-right: 16px;
22
- margin-bottom: 1.3em;
23
- max-width: 100%;
24
- }
25
-
26
- .grouping .items {
27
- display: block;
28
- }
29
-
30
- .grouping.verbose .items {
31
- opacity: 0;
32
- max-height: 0;
33
- display: flex;
34
- flex-direction: column;
35
- user-select: auto;
36
- }
37
-
38
- .grouping.flows .items {
39
- padding: 0;
40
- }
41
-
42
- .grouping.messages .items {
43
- display: flex;
44
- flex-direction: column;
45
- margin: 0em 0.75em;
46
- }
47
-
48
- .grouping.verbose.expanded .items {
49
- transition: max-height var(--transition-speed) ease-in-out,
50
- opacity var(--transition-speed) ease-in-out;
51
- opacity: 1;
52
- max-height: 1000px;
53
- padding: 1em 1em;
54
- }
55
-
56
- .grouping.verbose.expanded {
57
- border-top: 1px solid #f3f3f3;
58
- border-bottom: 1px solid #f3f3f3;
59
- }
60
-
61
- .grouping.verbose.expanded,
62
- .grouping.verbose .event-count {
63
- pointer-events: auto;
64
- }
65
-
66
- .grouping.verbose temba-icon {
67
- }
68
-
69
- .grouping.verbose > .event,
70
- .grouping.verbose > pre {
71
- max-height: 0px;
72
- padding-top: 0;
73
- padding-bottom: 0;
74
- margin-top: 0;
75
- margin-bottom: 0;
76
- opacity: 0;
77
- }
78
-
79
- .grouping.verbose .attn {
80
- color: #666;
81
- }
82
-
83
- .event-count {
84
- position: relative;
85
- font-size: 0.8em;
86
- text-align: center;
87
- margin: 0 auto;
88
- display: table;
89
- padding: 3px 10px;
90
- font-weight: 400;
91
- color: #999;
92
- cursor: pointer;
93
- width: 100%;
94
- opacity: 1;
95
- }
96
-
97
- .event-count temba-icon {
98
- display: inline-block;
99
- position: absolute;
100
- right: 5px;
101
- top: 5px;
102
- }
103
-
104
- .event-count:hover {
105
- color: var(--color-link-primary-hover);
106
- }
107
-
108
- .expanded .event-count {
109
- padding: 0;
110
- pointer-events: none;
111
- }
112
-
113
- .grouping.flows {
114
- margin-left: 1em;
115
- margin-right: 1em;
116
- margin-bottom: 1.5em;
117
-
118
- border: 1px solid #f2f2f2;
119
- border-radius: var(--curvature);
120
- padding: 0.5em 1em;
121
- }
122
-
123
- .grouping.flows .event {
124
- margin: 0;
125
- padding: 0;
126
- }
127
-
128
- .grouping.tickets {
129
- margin-bottom: 2em;
130
- }
131
-
132
- pre {
133
- white-space: pre-wrap;
134
- word-wrap: break-word;
135
- }
136
-
137
- .grouping.verbose.expanded .event,
138
- .grouping.verbose.expanded pre {
139
- max-height: 500px;
140
- opacity: 1;
141
- }
142
-
143
- .grouping-close-button {
144
- position: relative;
145
- display: inline-block;
146
- opacity: 0;
147
- float: right;
148
- --icon-color: #666;
149
- }
150
-
151
- .grouping.verbose.expanded:hover .grouping-close-button {
152
- opacity: 1;
153
- }
154
-
155
- .grouping.messages,
156
- .grouping.tickets {
157
- display: flex;
158
- flex-direction: column;
159
- }
160
-
161
- .event {
162
- margin: 0.25em 0.5em;
163
- border-radius: var(--curvature);
164
- flex-grow: 1;
165
- }
166
-
167
- .msg {
168
- border-radius: calc(var(--curvature) * 2.5);
169
- border: 2px solid rgba(100, 100, 100, 0.1);
170
- max-width: 300px;
171
- word-break: break-word;
172
- overflow: hidden;
173
- }
174
-
175
- .msg.attachments-1.no-message {
176
- border: 2px solid transparent;
177
- background-color: transparent !important;
178
- }
179
-
180
- .msg .text {
181
- padding: var(--event-padding);
182
- }
183
-
184
- .event.msg_received .msg {
185
- background: rgba(200, 200, 200, 0.1);
186
- }
187
-
188
- .event.msg_created,
189
- .event.broadcast_created,
190
- .event.ivr_created,
191
- .event.ticket_note_added {
192
- align-self: flex-end;
193
- }
194
-
195
- .event.msg_created .msg,
196
- .event.broadcast_created .msg,
197
- .event.ivr_created .msg {
198
- background: var(--color-primary-dark);
199
- color: white;
200
- font-weight: 400;
201
- }
202
-
203
- .msg.automated {
204
- background: var(--color-automated) !important;
205
- }
206
-
207
- .queued {
208
- opacity: 0.3;
209
- }
210
-
211
- .optin_requested {
212
- --icon-color: var(--color-primary-dark);
213
- }
214
-
215
- .webhook_called {
216
- --icon-color: #e68628;
217
- word-break: break-all;
218
- }
219
-
220
- .webhook_called .failed {
221
- --icon-color: var(--color-error);
222
- color: var(--color-error);
223
- }
224
-
225
- .input_labels_added,
226
- .contact_name_changed,
227
- .contact_field_changed,
228
- .contact_urns_changed,
229
- .contact_language_changed,
230
- .run_result_changed {
231
- --icon-color: rgba(1, 193, 175, 1);
232
- }
233
-
234
- .email_sent {
235
- --icon-color: #8e5ea7;
236
- }
237
-
238
- .contact_groups_changed .added {
239
- --icon-color: #309c42;
240
- }
241
- .contact_groups_changed .removed {
242
- --icon-color: var(--color-error);
243
- }
244
-
245
- .event.error .description,
246
- .event.failure .description {
247
- color: var(--color-error);
248
- }
249
-
250
- .description.error {
251
- color: var(--color-error);
252
- }
253
-
254
- .info {
255
- border: 1px solid rgba(100, 100, 100, 0.2);
256
- background: rgba(10, 10, 10, 0.02);
257
- }
258
-
259
- .ticket_note_added {
260
- max-width: 300px;
261
- }
262
-
263
- .note-summary {
264
- display: flex;
265
- flex-direction: row;
266
- font-size: 85%;
267
- margin-top: -0.5em;
268
- color: rgba(0, 0, 0, 0.6);
269
- padding: 8px 3px;
270
- }
271
-
272
- .ticket_note_added .description {
273
- border: 2px solid rgba(100, 100, 100, 0.1);
274
- background: rgb(255, 249, 194);
275
- padding: var(--event-padding);
276
- font-weight: 400;
277
- color: rgba(0, 0, 0, 0.6);
278
- border-radius: calc(var(--curvature) * 2.5);
279
- }
280
-
281
- .channel_event {
282
- --icon-color: rgb(200, 200, 200);
283
- }
284
-
285
- .airtime_transferred,
286
- .flow_exited,
287
- .flow_entered,
288
- .ticket_opened,
289
- .ticket_reopened,
290
- .ticket_closed,
291
- .call_started,
292
- .campaign_fired {
293
- --icon-color: rgba(223, 65, 159, 1);
294
- }
295
-
296
- .active-ticket.ticket_opened {
297
- padding: 0em 1em;
298
- }
299
-
300
- .ticket_closed .inactive .subtext {
301
- display: none;
302
- }
303
-
304
- .attn {
305
- color: var(--color-text);
306
- }
307
-
308
- .flow_exited,
309
- .flow_entered {
310
- align-self: center;
311
- max-width: 80%;
312
- display: flex;
313
- flex-direction: row;
314
- }
315
-
316
- .flow_exited temba-icon,
317
- .flow_entered temba-icon {
318
- }
319
-
320
- .event {
321
- display: flex;
322
- align-items: center;
323
- }
324
-
325
- .event .description {
326
- flex-grow: 1;
327
- }
328
-
329
- .msg-summary {
330
- display: flex;
331
- font-size: 85%;
332
- color: rgba(0, 0, 0, 0.6);
333
- padding: 6px 3px;
334
- margin-bottom: 0.5em;
335
- margin-top: -0.5em;
336
- }
337
-
338
- .msg-summary temba-icon.log {
339
- --icon-color: rgba(0, 0, 0, 0.2);
340
- }
341
-
342
- .msg-summary temba-icon.log:hover {
343
- --icon-color: var(--color-link-primary-hover);
344
- cursor: pointer;
345
- }
346
-
347
- .msg-summary temba-icon.error {
348
- --icon-color: rgba(var(--error-rgb), 0.75);
349
- }
350
-
351
- .msg-summary temba-icon.error:hover {
352
- --icon-color: var(--color-error);
353
- cursor: pointer;
354
- }
355
-
356
- .msg-summary temba-icon.broadcast {
357
- --icon-color: rgba(90, 90, 90, 0.5);
358
- }
359
-
360
- .msg-summary * {
361
- display: flex;
362
- margin-right: 1px;
363
- margin-left: 1px;
364
- }
365
-
366
- .unsupported {
367
- border: 1px solid #f2f2f2;
368
- color: #999;
369
- padding: 0.5em 1em;
370
- border-radius: var(--curvature);
371
- }
372
-
373
- .optin {
374
- align-items: center;
375
- padding: 0 0.2em;
376
- }
377
-
378
- .time {
379
- padding: 0.3em 1px;
380
- }
381
-
382
- .subtext .time {
383
- padding: 0em;
384
- }
385
-
386
- .status {
387
- padding: 0.3em 3px;
388
- }
389
-
390
- .separator {
391
- padding: 0.3em 0px;
392
- }
393
-
394
- .recipients {
395
- padding: 0.3em 3px;
396
- }
397
-
398
- .verbose temba-icon,
399
- .flows temba-icon,
400
- .tickets temba-icon {
401
- margin-right: 0.75em;
402
- }
403
-
404
- .attn {
405
- display: inline-block;
406
- font-weight: 500;
407
- margin: 0px 2px;
408
- word-break: break-all;
409
- white-space: break-spaces;
410
- }
411
-
412
- .subtext {
413
- font-size: 80%;
414
- }
415
-
416
- .body-pre {
417
- white-space: pre-wrap;
418
- word-wrap: break-word;
419
- font-size: 90%;
420
- }
421
-
422
- a,
423
- .linked {
424
- color: var(--color-link-primary);
425
- cursor: pointer;
426
- }
427
-
428
- a:hover,
429
- .linked:hover {
430
- text-decoration: underline;
431
- color: var(--color-link-primary-hover);
432
- }
433
-
434
- temba-icon.error {
435
- --icon-color: var(--color-error);
436
- }
437
-
438
- .delivery-error {
439
- --icon-color: var(--color-error);
440
- margin-right: 0.25em;
441
- }
442
-
443
- .flow {
444
- --icon-color: #ddd;
445
- background: #fff;
446
- width: 18px;
447
- height: 18px;
448
- padding-top: 4px;
449
- padding-left: 9px;
450
- border: 0px solid #f3f3f3;
451
- }
452
-
453
- .assigned {
454
- color: #777;
455
- max-width: 300px;
456
- margin-left: auto;
457
- margin-right: auto;
458
- display: flex;
459
- flex-direction: column;
460
- align-items: center;
461
- margin-bottom: 10px;
462
- }
463
-
464
- .assigned .attn {
465
- color: #777;
466
- }
467
-
468
- .attachments {
469
- display: flex;
470
- flex-wrap: wrap;
471
- margin: -0.2em;
472
- }
473
-
474
- .attachment {
475
- flex: 1 0 45%;
476
- border-top: 0.05em solid transparent;
477
- border-left: 0.05em solid transparent;
478
- margin-top: 0.05em;
479
- margin-left: 0.05em;
480
- }
481
- `;
482
- };
483
4
 
484
5
  export interface EventGroup {
485
6
  type: string;
@@ -487,37 +8,6 @@ export interface EventGroup {
487
8
  open: boolean;
488
9
  }
489
10
 
490
- export enum Events {
491
- MESSAGE_CREATED = 'msg_created',
492
- MESSAGE_RECEIVED = 'msg_received',
493
- BROADCAST_CREATED = 'broadcast_created',
494
- IVR_CREATED = 'ivr_created',
495
- FLOW_ENTERED = 'flow_entered',
496
- FLOW_EXITED = 'flow_exited',
497
- RUN_RESULT_CHANGED = 'run_result_changed',
498
- CONTACT_FIELD_CHANGED = 'contact_field_changed',
499
- CONTACT_GROUPS_CHANGED = 'contact_groups_changed',
500
- CONTACT_NAME_CHANGED = 'contact_name_changed',
501
- CONTACT_URNS_CHANGED = 'contact_urns_changed',
502
- CAMPAIGN_FIRED = 'campaign_fired',
503
- CHANNEL_EVENT = 'channel_event',
504
- CONTACT_LANGUAGE_CHANGED = 'contact_language_changed',
505
- WEBHOOK_CALLED = 'webhook_called',
506
- AIRTIME_TRANSFERRED = 'airtime_transferred',
507
- CALL_STARTED = 'call_started',
508
- EMAIL_SENT = 'email_sent',
509
- INPUT_LABELS_ADDED = 'input_labels_added',
510
- NOTE_CREATED = 'note_created',
511
- TICKET_ASSIGNED = 'ticket_assigned',
512
- TICKET_NOTE_ADDED = 'ticket_note_added',
513
- TICKET_CLOSED = 'ticket_closed',
514
- TICKET_OPENED = 'ticket_opened',
515
- TICKET_REOPENED = 'ticket_reopened',
516
- OPTIN_REQUESTED = 'optin_requested',
517
- ERROR = 'error',
518
- FAILURE = 'failure'
519
- }
520
-
521
11
  export interface ContactEvent {
522
12
  type: string;
523
13
  created_on: string;
@@ -657,39 +147,6 @@ export interface ContactHistoryPage {
657
147
  events: ContactEvent[];
658
148
  }
659
149
 
660
- export const getEventGroupType = (event: ContactEvent, ticket: string) => {
661
- if (!event) {
662
- return 'messages';
663
- }
664
-
665
- switch (event.type) {
666
- case Events.TICKET_ASSIGNED:
667
- case Events.TICKET_OPENED:
668
- case Events.TICKET_CLOSED:
669
- case Events.TICKET_REOPENED:
670
- if (!ticket) {
671
- return 'verbose';
672
- }
673
-
674
- if ((event as TicketEvent).ticket.uuid === ticket) {
675
- return 'tickets';
676
- }
677
-
678
- break;
679
- case Events.FLOW_ENTERED:
680
- case Events.FLOW_EXITED:
681
- return 'flows';
682
- case Events.BROADCAST_CREATED:
683
- case Events.MESSAGE_CREATED:
684
- case Events.MESSAGE_RECEIVED:
685
- case Events.IVR_CREATED:
686
- case Events.TICKET_NOTE_ADDED:
687
- case Events.NOTE_CREATED:
688
- return 'messages';
689
- }
690
- return 'verbose';
691
- };
692
-
693
150
  export const renderAttachment = (attachment: string): TemplateResult => {
694
151
  const idx = attachment.indexOf(':');
695
152
  const attType = attachment.substr(0, idx);
@@ -750,527 +207,3 @@ export const renderAttachment = (attachment: string): TemplateResult => {
750
207
 
751
208
  return html`<div style="">${inner}</div>`;
752
209
  };
753
-
754
- export const renderMsgEvent = (event: MsgEvent): TemplateResult => {
755
- const isInbound = event.type === Events.MESSAGE_RECEIVED;
756
- const isError = event.status === 'E';
757
- const isFailure = event.status === 'F';
758
-
759
- // summary items which appear under the message bubble
760
- const summary: TemplateResult[] = [];
761
-
762
- if (event.logs_url) {
763
- summary.push(html` <div class="icon-link">
764
- <temba-icon
765
- onclick="goto(event)"
766
- href="${event.logs_url}"
767
- name="${Icon.log}"
768
- class="log ${isError || isFailure ? 'error' : ''}"
769
- ></temba-icon>
770
- </div>`);
771
- } else if (isError) {
772
- summary.push(
773
- html`<temba-icon
774
- title="Message delivery error"
775
- name="${Icon.error}"
776
- class="delivery-error"
777
- ></temba-icon>`
778
- );
779
- } else if (isFailure) {
780
- summary.push(
781
- html`<temba-icon
782
- title="Message delivery failure: ${event.failed_reason_display}"
783
- name="${Icon.error}"
784
- class="delivery-error"
785
- ></temba-icon>`
786
- );
787
- }
788
-
789
- if (event.type == 'broadcast_created') {
790
- summary.push(html`<temba-icon
791
- size="1"
792
- class="broadcast"
793
- name="${Icon.broadcast}"
794
- ></temba-icon>`);
795
-
796
- if (event.recipient_count > 1) {
797
- summary.push(
798
- html`<div class="recipients">${event.recipient_count} contacts</div>`
799
- );
800
- }
801
- summary.push(html`<div class="separator">•</div>`);
802
- }
803
-
804
- if (event.optin) {
805
- summary.push(
806
- html`<div class="optin">${event.optin.name}</div>
807
- <div class="separator">•</div>`
808
- );
809
- }
810
-
811
- if (event.status === 'Q') {
812
- summary.push(html`<div>Queued</div>`);
813
- }
814
-
815
- summary.push(
816
- html`<temba-date
817
- class="time"
818
- value="${event.created_on}"
819
- display="duration"
820
- ></temba-date>`
821
- );
822
-
823
- return html`<div
824
- style="display:flex;align-items:flex-start"
825
- class=${getClasses({ queued: event.status === 'Q' })}
826
- >
827
- <div style="display:flex;flex-direction:column">
828
- <div
829
- class="${event.msg.text ? '' : 'no-message'} attachments-${(
830
- event.msg.attachments || []
831
- ).length} ${getClasses({
832
- msg: true,
833
- automated: !isInbound && !event.created_by
834
- })}"
835
- >
836
- ${event.msg.text
837
- ? html` <div class="text">${event.msg.text}</div> `
838
- : null}
839
- ${event.msg.attachments
840
- ? html`<div class="attachments">
841
- ${event.msg.attachments.map(
842
- (attachment) =>
843
- html` <div class="attachment">
844
- ${renderAttachment(attachment)}
845
- </div>`
846
- )}
847
- </div> `
848
- : null}
849
- </div>
850
-
851
- ${!event.msg.text && !event.msg.attachments && !event.optin
852
- ? html`<div class="unsupported">Unsupported Message</div>`
853
- : null}
854
-
855
- <div
856
- class="msg-summary"
857
- style="align-items:center;flex-direction:row${isInbound
858
- ? '-reverse'
859
- : ''}"
860
- >
861
- <div style="flex-grow:1"></div>
862
- ${summary}
863
- </div>
864
- </div>
865
-
866
- ${!isInbound && event.created_by
867
- ? html`<temba-user
868
- style="margin-left:0.5em"
869
- email=${event.created_by.email}
870
- ></temba-user>`
871
- : null}
872
- </div>`;
873
- };
874
-
875
- export const renderFlowEvent = (event: FlowEvent): TemplateResult => {
876
- let verb = 'Interrupted';
877
- let icon = Icon.flow_interrupted;
878
-
879
- if (event.status !== 'I') {
880
- if (event.type === Events.FLOW_ENTERED) {
881
- verb = 'Started';
882
- icon = Icon.flow;
883
- } else {
884
- verb = 'Completed';
885
- icon = Icon.flow;
886
- }
887
- }
888
-
889
- return html`
890
- <temba-icon name="${icon}"></temba-icon>
891
- <div class="description">
892
- ${verb}
893
- <span
894
- class="linked"
895
- href="/flow/editor/${event.flow.uuid}/"
896
- onclick="goto(event)"
897
- >
898
- ${event.flow.name}
899
- </span>
900
- </div>
901
- `;
902
- };
903
-
904
- export const renderResultEvent = (event: UpdateResultEvent): TemplateResult => {
905
- if (event.name.startsWith('_')) {
906
- return null;
907
- }
908
- return html`
909
- <temba-icon name="${Icon.updated}"></temba-icon>
910
- <div class="description">
911
- Updated
912
- <div class="attn">${event.name}</div>
913
- to
914
- <div class="attn">${event.value}</div>
915
- ${event.category
916
- ? html`with category
917
- <div class="attn">${event.category}</div>`
918
- : null}
919
- </div>
920
- `;
921
- };
922
-
923
- export const renderUpdateEvent = (event: UpdateFieldEvent): TemplateResult => {
924
- return html`
925
- <temba-icon name="${Icon.contact_updated}"></temba-icon>
926
- <div class="description">
927
- ${event.value
928
- ? html`Updated
929
- <div class="attn">${event.field.name}</div>
930
- to
931
- <div class="attn">${event.value.text}</div>`
932
- : html`Cleared
933
- <div class="attn">${event.field.name}</div>`}
934
- </div>
935
- `;
936
- };
937
-
938
- export const renderNameChanged = (event: NameChangedEvent): TemplateResult => {
939
- return html`
940
- <temba-icon name="${Icon.contact_updated}"></temba-icon>
941
- <div class="description">
942
- Updated
943
- <div class="attn">Name</div>
944
- to
945
- <div class="attn">${event.name}</div>
946
- </div>
947
- `;
948
- };
949
-
950
- export const renderContactURNsChanged = (
951
- event: URNsChangedEvent
952
- ): TemplateResult => {
953
- return html`
954
- <temba-icon name="${Icon.contact_updated}"></temba-icon>
955
- <div class="description">
956
- Updated
957
- <div class="attn">URNs</div>
958
- to
959
- ${oxfordFn(
960
- event.urns,
961
- (urn: string) =>
962
- html`<div class="attn">${urn.split(':')[1].split('?')[0]}</div>`
963
- )}
964
- </div>
965
- </div>
966
- `;
967
- };
968
-
969
- export const renderEmailSent = (event: EmailSentEvent): TemplateResult => {
970
- return html`
971
- <temba-icon name="${Icon.email}"></temba-icon>
972
- <div class="description">
973
- Email sent to
974
- <div class="attn">${oxford(event.to, 'and')}</div>
975
- with subject
976
- <div class="attn">${event.subject}</div>
977
- </div>
978
- `;
979
- };
980
-
981
- export const renderLabelsAdded = (event: LabelsAddedEvent): TemplateResult => {
982
- return html`
983
- <temba-icon name="${Icon.label}"></temba-icon>
984
- <div class="description">
985
- Message labeled with
986
- <div class="attn">${oxfordNamed(event.labels, 'and')}</div>
987
- </div>
988
- `;
989
- };
990
-
991
- export const renderNoteCreated = (event: TicketEvent): TemplateResult => {
992
- return html` <div style="display:flex;align-items:flex-start">
993
- <div style="display:flex;flex-direction:column">
994
- <div class="description">${event.note}</div>
995
- <div class="note-summary">
996
- <div style="flex-grow:1"></div>
997
- <temba-date
998
- class="time"
999
- value="${event.created_on}"
1000
- display="duration"
1001
- ></temba-date>
1002
- </div>
1003
- </div>
1004
- <temba-user email=${event.created_by.email}></temba-user>
1005
- </div>`;
1006
- };
1007
-
1008
- export const renderTicketAction = (
1009
- event: TicketEvent,
1010
- action: string,
1011
- grouped: boolean
1012
- ): TemplateResult => {
1013
- if (grouped) {
1014
- return html`<div class="" style="display: flex">
1015
- <temba-icon name="${Icon.inbox}"></temba-icon>
1016
- <div class="description">
1017
- ${getDisplayName(event.created_by)} ${action} a
1018
- <span
1019
- onclick="goto(event)"
1020
- class="linked"
1021
- href="/ticket/all/open/${event.ticket.uuid}/"
1022
- >
1023
- ticket
1024
- </span>
1025
- </div>
1026
- </div>`;
1027
- }
1028
-
1029
- return html`
1030
- <div class="assigned active">
1031
- <div style="text-align:center">
1032
- ${event.created_by
1033
- ? html` ${getDisplayName(event.created_by)} ${action} this ticket `
1034
- : html` This ticket was ${action} `}
1035
- </div>
1036
- <div class="subtext" style="justify-content:center">
1037
- <temba-date
1038
- class="time"
1039
- value="${event.created_on}"
1040
- display="duration"
1041
- ></temba-date>
1042
- </div>
1043
- </div>
1044
- `;
1045
- };
1046
-
1047
- export const renderTicketAssigned = (event: TicketEvent): TemplateResult => {
1048
- return html`
1049
- <div class="assigned active">
1050
- <div style="text-align:center">
1051
- ${event.assignee
1052
- ? event.assignee.id === event.created_by.id
1053
- ? html`${getDisplayName(event.created_by)} took this ticket`
1054
- : html`${getDisplayName(event.created_by)} assigned this ticket to
1055
- <div class="attn">${getDisplayName(event.assignee)}</div>`
1056
- : html`${getDisplayName(event.created_by)} unassigned this ticket`}
1057
- </div>
1058
- <div class="subtext" style="justify-content:center">
1059
- <temba-date
1060
- class="time"
1061
- value="${event.created_on}"
1062
- display="duration"
1063
- ></temba-date>
1064
- </div>
1065
- </div>
1066
- `;
1067
- };
1068
-
1069
- export const renderTicketOpened = (
1070
- event: TicketEvent,
1071
- handleClose: (uuid: string) => void,
1072
- grouped: boolean
1073
- ): TemplateResult => {
1074
- if (grouped) {
1075
- return html`<div class="" style="display: flex">
1076
- <temba-icon name="${Icon.inbox}"></temba-icon>
1077
- <div class="description">
1078
- ${event.ticket.topic.name}
1079
- <span
1080
- class="linked"
1081
- onclick="goto(event)"
1082
- href="/ticket/all/open/${event.ticket.uuid}"
1083
- >ticket</span
1084
- >
1085
- was opened
1086
- </div>
1087
- </div>`;
1088
- } else {
1089
- return html`
1090
- <div>
1091
- <div style="text-align:center">
1092
- ${getDisplayName(event.created_by)} opened this ticket
1093
- </div>
1094
- <div class="subtext" style="justify-content:center">
1095
- <temba-date
1096
- class="time"
1097
- value="${event.created_on}"
1098
- display="duration"
1099
- ></temba-date>
1100
- </div>
1101
- </div>
1102
- `;
1103
- }
1104
- };
1105
-
1106
- export const renderErrorMessage = (
1107
- event: ErrorMessageEvent
1108
- ): TemplateResult => {
1109
- return html`
1110
- <temba-icon
1111
- name="${Icon.error}"
1112
- style="--icon-color:var(--color-error)"
1113
- ></temba-icon>
1114
- <div class="description">
1115
- ${event.text}
1116
- ${event.type === Events.FAILURE
1117
- ? html`<div>Run ended prematurely, check the flow design.</div>`
1118
- : null}
1119
- </div>
1120
- `;
1121
- };
1122
-
1123
- export const renderWebhookEvent = (event: WebhookEvent): TemplateResult => {
1124
- return html`
1125
- <div
1126
- class="${event.status === 'success' ? '' : 'failed'}"
1127
- style="display: flex"
1128
- >
1129
- <temba-icon name="${Icon.webhook}"></temba-icon>
1130
- <div class="description">
1131
- ${event.status === 'success'
1132
- ? html`Successfully called ${event.url}`
1133
- : html`Failed to call ${event.url}`}
1134
- </div>
1135
- </div>
1136
- `;
1137
- };
1138
-
1139
- export const renderAirtimeTransferredEvent = (
1140
- event: AirtimeTransferredEvent
1141
- ): TemplateResult => {
1142
- if (parseFloat(event.actual_amount) === 0) {
1143
- return html`<temba-icon
1144
- name="${Icon.error}"
1145
- style="--icon-color: var(--color-error)"
1146
- ></temba-icon>
1147
- <div class="description error">Airtime transfer failed</div>`;
1148
- }
1149
-
1150
- return html`<temba-icon name="${Icon.airtime}"></temba-icon>
1151
- <div class="description">
1152
- Transferred
1153
- <div class="attn">${event.actual_amount} ${event.currency}</div>
1154
- of airtime
1155
- </div>`;
1156
- };
1157
-
1158
- export const renderCallStartedEvent = (): TemplateResult => {
1159
- return html`<temba-icon name="${Icon.call}"></temba-icon>
1160
- <div class="description">Call Started</div>`;
1161
- };
1162
-
1163
- export const renderContactLanguageChangedEvent = (
1164
- event: ContactLanguageChangedEvent
1165
- ): TemplateResult => {
1166
- return html`<temba-icon name="${Icon.contact_updated}"></temba-icon>
1167
- <div class="description">
1168
- Language updated to <span class="attn">${event.language}</span>
1169
- </div>`;
1170
- };
1171
-
1172
- export const renderOptinRequested = (
1173
- event: OptinRequestedEvent
1174
- ): TemplateResult => {
1175
- return html`<temba-icon name="${Icon.optin_requested}"></temba-icon>
1176
- <div class="description">
1177
- Requested opt-in for <span class="attn">${event.optin.name}</span>
1178
- </div>`;
1179
- };
1180
-
1181
- export const renderChannelEvent = (event: ChannelEvent): TemplateResult => {
1182
- let eventMessage: string | TemplateResult = '';
1183
- let icon = Icon.call;
1184
-
1185
- if (event.event.type === 'mt_miss') {
1186
- eventMessage = 'Missed outgoing call';
1187
- icon = Icon.call_missed;
1188
- } else if (event.event.type === 'mo_miss') {
1189
- eventMessage = 'Missed incoming call';
1190
- icon = Icon.call_missed;
1191
- } else if (event.event.type === 'new_conversation') {
1192
- eventMessage = 'Started Conversation';
1193
- icon = Icon.event;
1194
- } else if (event.channel_event_type === 'welcome_message') {
1195
- eventMessage = 'Welcome Message Sent';
1196
- icon = Icon.event;
1197
- } else if (event.event.type === 'referral') {
1198
- eventMessage = 'Referred';
1199
- icon = Icon.event;
1200
- } else if (event.event.type === 'follow') {
1201
- eventMessage = 'Followed';
1202
- icon = Icon.event;
1203
- } else if (event.event.type === 'stop_contact') {
1204
- eventMessage = 'Stopped';
1205
- icon = Icon.contact_stopped;
1206
- } else if (event.event.type === 'mt_call') {
1207
- eventMessage = 'Outgoing Phone Call';
1208
- } else if (event.event.type == 'mo_call') {
1209
- eventMessage = 'Incoming Phone call';
1210
- } else if (event.event.type == 'optin') {
1211
- eventMessage = html`Opted in to
1212
- <span class="attn">${event.event.optin?.name}</span>`;
1213
- icon = Icon.optin;
1214
- } else if (event.event.type == 'optout') {
1215
- eventMessage = html`Opted out of
1216
- <span class="attn">${event.event.optin?.name}</span>`;
1217
- icon = Icon.optout;
1218
- }
1219
-
1220
- return html`<temba-icon name="${icon}"></temba-icon>
1221
- <div class="description">${eventMessage}</div>`;
1222
- };
1223
-
1224
- export const renderCampaignFiredEvent = (
1225
- event: CampaignFiredEvent
1226
- ): TemplateResult => {
1227
- return html`<temba-icon name="${Icon.campaign}"></temba-icon>
1228
- <div class="description">
1229
- Campaign
1230
- <span
1231
- class="linked"
1232
- onclick="goto(event, this)"
1233
- href="/campaign/read/${event.campaign.uuid}/"
1234
- >${event.campaign.name}</span
1235
- >
1236
- ${event.fired_result === 'S' ? 'skipped' : 'triggered'}
1237
- <span
1238
- class="linked"
1239
- onclick="goto(event, this)"
1240
- href="/campaignevent/read/${event.campaign.uuid}/${event.campaign_event
1241
- .id}/"
1242
- >
1243
- ${event.campaign_event.offset_display}
1244
- ${event.campaign_event.relative_to.name}</span
1245
- >
1246
- </div>`;
1247
- };
1248
-
1249
- export const renderContactGroupsEvent = (
1250
- event: ContactGroupsEvent
1251
- ): TemplateResult => {
1252
- const groups = event.groups_added || event.groups_removed;
1253
- const added = !!event.groups_added;
1254
- return html`
1255
- <temba-icon
1256
- name="${Icon.users}"
1257
- class="${getClasses({ added: added, removed: !added })}"
1258
- ></temba-icon>
1259
- <div class="description">
1260
- ${added ? 'Added to' : 'Removed from'}
1261
- ${oxfordFn(
1262
- groups,
1263
- (group: ObjectReference) =>
1264
- html`<span
1265
- class="linked"
1266
- onclick="goto(event)"
1267
- href="/contact/filter/${group.uuid}"
1268
- >${group.name}</span
1269
- >`
1270
- )}
1271
- ${event.type === Events.FAILURE
1272
- ? html`<div>Run ended prematurely, check the flow design.</div>`
1273
- : null}
1274
- </div>
1275
- `;
1276
- };