@nyaruka/temba-components 0.45.0 → 0.46.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 (71) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/{0887303b.js → 041a136a.js} +161 -309
  3. package/dist/index.js +161 -309
  4. package/dist/sw.js +1 -1
  5. package/dist/sw.js.map +1 -1
  6. package/dist/templates/components-body.html +1 -1
  7. package/dist/templates/components-head.html +1 -1
  8. package/out-tsc/src/button/Button.js +10 -0
  9. package/out-tsc/src/button/Button.js.map +1 -1
  10. package/out-tsc/src/completion/Completion.js +3 -0
  11. package/out-tsc/src/completion/Completion.js.map +1 -1
  12. package/out-tsc/src/completion/helpers.js +3 -0
  13. package/out-tsc/src/completion/helpers.js.map +1 -1
  14. package/out-tsc/src/contacts/ContactChat.js +8 -201
  15. package/out-tsc/src/contacts/ContactChat.js.map +1 -1
  16. package/out-tsc/src/contacts/ContactHistory.js +5 -111
  17. package/out-tsc/src/contacts/ContactHistory.js.map +1 -1
  18. package/out-tsc/src/contacts/ContactTickets.js +99 -17
  19. package/out-tsc/src/contacts/ContactTickets.js.map +1 -1
  20. package/out-tsc/src/contacts/events.js +28 -46
  21. package/out-tsc/src/contacts/events.js.map +1 -1
  22. package/out-tsc/src/date/TembaDate.js +4 -1
  23. package/out-tsc/src/date/TembaDate.js.map +1 -1
  24. package/out-tsc/src/interfaces.js.map +1 -1
  25. package/out-tsc/src/list/TembaList.js +0 -1
  26. package/out-tsc/src/list/TembaList.js.map +1 -1
  27. package/out-tsc/src/options/Options.js +6 -2
  28. package/out-tsc/src/options/Options.js.map +1 -1
  29. package/out-tsc/src/store/StoreElement.js +8 -1
  30. package/out-tsc/src/store/StoreElement.js.map +1 -1
  31. package/out-tsc/src/utils/index.js +16 -6
  32. package/out-tsc/src/utils/index.js.map +1 -1
  33. package/out-tsc/src/vectoricon/index.js +1 -1
  34. package/out-tsc/src/vectoricon/index.js.map +1 -1
  35. package/out-tsc/test/temba-contact-chat.test.js +0 -105
  36. package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
  37. package/package.json +1 -1
  38. package/screenshots/truth/contacts/badges.png +0 -0
  39. package/screenshots/truth/contacts/compose-attachments-no-text-failure.png +0 -0
  40. package/screenshots/truth/contacts/compose-attachments-no-text-success.png +0 -0
  41. package/screenshots/truth/contacts/compose-text-and-attachments-failure-attachments.png +0 -0
  42. package/screenshots/truth/contacts/compose-text-and-attachments-failure-generic.png +0 -0
  43. package/screenshots/truth/contacts/compose-text-and-attachments-failure-text-and-attachments.png +0 -0
  44. package/screenshots/truth/contacts/compose-text-and-attachments-failure-text.png +0 -0
  45. package/screenshots/truth/contacts/compose-text-and-attachments-success.png +0 -0
  46. package/screenshots/truth/contacts/compose-text-no-attachments-failure.png +0 -0
  47. package/screenshots/truth/contacts/compose-text-no-attachments-success.png +0 -0
  48. package/screenshots/truth/contacts/contact-active-default.png +0 -0
  49. package/screenshots/truth/contacts/contact-active-show-chatbox.png +0 -0
  50. package/screenshots/truth/contacts/contact-archived-hide-chatbox.png +0 -0
  51. package/screenshots/truth/contacts/contact-blocked-hide-chatbox.png +0 -0
  52. package/screenshots/truth/contacts/contact-stopped-hide-chatbox.png +0 -0
  53. package/screenshots/truth/contacts/history.png +0 -0
  54. package/src/button/Button.ts +10 -0
  55. package/src/completion/Completion.ts +3 -0
  56. package/src/completion/helpers.ts +4 -0
  57. package/src/contacts/ContactChat.ts +8 -214
  58. package/src/contacts/ContactHistory.ts +7 -129
  59. package/src/contacts/ContactTickets.ts +99 -19
  60. package/src/contacts/events.ts +28 -47
  61. package/src/date/TembaDate.ts +7 -1
  62. package/src/interfaces.ts +1 -1
  63. package/src/list/TembaList.ts +0 -2
  64. package/src/options/Options.ts +8 -2
  65. package/src/store/StoreElement.ts +7 -1
  66. package/src/utils/index.ts +14 -6
  67. package/src/vectoricon/index.ts +1 -1
  68. package/test/temba-contact-chat.test.ts +0 -141
  69. package/screenshots/truth/contacts/contact-active-ticket-closed-show-reopen-button.png +0 -0
  70. package/screenshots/truth/contacts/contact-active-ticket-open-show-chatbox.png +0 -0
  71. package/screenshots/truth/contacts/contact-archived-ticket-closed-hide-chatbox.png +0 -0
@@ -6,7 +6,6 @@ import {
6
6
  oxfordFn,
7
7
  oxfordNamed,
8
8
  renderAvatar,
9
- timeSince,
10
9
  } from '../utils';
11
10
  import { Icon } from '../vectoricon';
12
11
  import { getDisplayName } from './helpers';
@@ -195,7 +194,7 @@ export const getEventStyles = () => {
195
194
  .event.msg_created,
196
195
  .event.broadcast_created,
197
196
  .event.ivr_created,
198
- .tickets .event.ticket_note_added {
197
+ .event.ticket_note_added {
199
198
  align-self: flex-end;
200
199
  }
201
200
 
@@ -255,11 +254,11 @@ export const getEventStyles = () => {
255
254
  background: rgba(10, 10, 10, 0.02);
256
255
  }
257
256
 
258
- .tickets .ticket_note_added {
257
+ .ticket_note_added {
259
258
  max-width: 300px;
260
259
  }
261
260
 
262
- .tickets .note-summary {
261
+ .note-summary {
263
262
  display: flex;
264
263
  flex-direction: row;
265
264
  font-size: 85%;
@@ -268,11 +267,13 @@ export const getEventStyles = () => {
268
267
  padding: 8px 3px;
269
268
  }
270
269
 
271
- .tickets .ticket_note_added .description {
270
+ .ticket_note_added .description {
272
271
  border: 2px solid rgba(100, 100, 100, 0.1);
273
272
  background: rgb(255, 249, 194);
274
273
  padding: var(--event-padding);
275
- border-radius: 8px;
274
+ font-weight: 400;
275
+ color: rgba(0, 0, 0, 0.6);
276
+ border-radius: calc(var(--curvature) * 2.5);
276
277
  }
277
278
 
278
279
  .channel_event {
@@ -294,14 +295,6 @@ export const getEventStyles = () => {
294
295
  padding: 0em 1em;
295
296
  }
296
297
 
297
- .ticket_opened temba-icon.clickable[name='check'] {
298
- --icon-color: rgba(100, 100, 100, 1);
299
- }
300
-
301
- .ticket_opened .active {
302
- color: var(--color-text);
303
- }
304
-
305
298
  .ticket_closed .inactive .subtext {
306
299
  display: none;
307
300
  }
@@ -476,8 +469,6 @@ export const getEventStyles = () => {
476
469
  `;
477
470
  };
478
471
 
479
- const FLOW_USER_ID = 'flow';
480
-
481
472
  export interface EventGroup {
482
473
  type: string;
483
474
  events: ContactEvent[];
@@ -641,9 +632,9 @@ export const getEventGroupType = (event: ContactEvent, ticket: string) => {
641
632
  if (!event) {
642
633
  return 'messages';
643
634
  }
635
+
644
636
  switch (event.type) {
645
637
  case Events.TICKET_ASSIGNED:
646
- case Events.TICKET_NOTE_ADDED:
647
638
  case Events.TICKET_OPENED:
648
639
  case Events.TICKET_CLOSED:
649
640
  case Events.TICKET_REOPENED:
@@ -663,6 +654,8 @@ export const getEventGroupType = (event: ContactEvent, ticket: string) => {
663
654
  case Events.MESSAGE_CREATED:
664
655
  case Events.MESSAGE_RECEIVED:
665
656
  case Events.IVR_CREATED:
657
+ case Events.TICKET_NOTE_ADDED:
658
+ case Events.NOTE_CREATED:
666
659
  return 'messages';
667
660
  }
668
661
  return 'verbose';
@@ -826,11 +819,9 @@ export const renderMsgEvent = (
826
819
  </div>
827
820
  </div>
828
821
 
829
- ${!isInbound
822
+ ${!isInbound && event.msg.created_by
830
823
  ? html`<div style="margin-left:0.8em;margin-top:0.3em;font-size:0.9em">
831
- ${event.msg.created_by
832
- ? renderUserAvatar(event.msg.created_by)
833
- : renderUserAvatar(null)}
824
+ ${renderUserAvatar(event.msg.created_by)}
834
825
  </div>`
835
826
  : null}
836
827
  </div>`;
@@ -953,7 +944,7 @@ export const renderLabelsAdded = (event: LabelsAddedEvent): TemplateResult => {
953
944
  };
954
945
 
955
946
  export const renderNoteCreated = (event: TicketEvent): TemplateResult => {
956
- return html`<div style="display:flex;align-items:flex-start">
947
+ return html` <div style="display:flex;align-items:flex-start">
957
948
  <div style="display:flex;flex-direction:column">
958
949
  <div class="description">${event.note}</div>
959
950
  <div class="note-summary">
@@ -996,7 +987,7 @@ export const renderTicketAction = (
996
987
  <span
997
988
  onclick="goto(event)"
998
989
  class="linked"
999
- href="/ticket/all/open/${event.ticket.uuid}"
990
+ href="/ticket/all/open/${event.ticket.uuid}/"
1000
991
  >
1001
992
  ticket
1002
993
  </span>
@@ -1007,12 +998,14 @@ export const renderTicketAction = (
1007
998
  return html`
1008
999
  <div class="assigned active">
1009
1000
  <div style="text-align:center">
1010
- ${getDisplayName(event.created_by)} ${action} this ticket
1001
+ ${event.created_by
1002
+ ? html` ${getDisplayName(event.created_by)} ${action} this ticket `
1003
+ : html` This ticket was ${action} `}
1011
1004
  </div>
1012
1005
  <div class="subtext" style="justify-content:center">
1013
1006
  <temba-date
1014
1007
  class="time"
1015
- value="${reopened}"
1008
+ value="${event.created_on}"
1016
1009
  display="duration"
1017
1010
  ></temba-date>
1018
1011
  </div>
@@ -1065,30 +1058,18 @@ export const renderTicketOpened = (
1065
1058
  </div>`;
1066
1059
  } else {
1067
1060
  return html`
1068
- <temba-icon size="1.5" name="${icon}"></temba-icon>
1069
-
1070
- <div class="active" style="flex-grow:1;">
1071
- Opened
1072
- <div class="attn">
1073
- ${event.ticket.topic ? event.ticket.topic.name : 'General'}
1061
+ <div>
1062
+ <div style="text-align:center">
1063
+ ${getDisplayName(event.created_by)} opened this ticket
1064
+ </div>
1065
+ <div class="subtext" style="justify-content:center">
1066
+ <temba-date
1067
+ class="time"
1068
+ value="${event.created_on}"
1069
+ display="duration"
1070
+ ></temba-date>
1074
1071
  </div>
1075
- <div class="subtext">${timeSince(new Date(event.created_on))}</div>
1076
1072
  </div>
1077
- ${handleClose
1078
- ? html`
1079
- <temba-tip text="Resolve" position="left" style="width:1.5em">
1080
- <temba-icon
1081
- class="clickable"
1082
- size="1.5"
1083
- name="${Icon.check}"
1084
- @click=${() => {
1085
- handleClose(event.ticket.uuid);
1086
- }}
1087
- ?clickable=${open}
1088
- />
1089
- </temba-tip>
1090
- `
1091
- : null}
1092
1073
  `;
1093
1074
  }
1094
1075
  };
@@ -41,9 +41,15 @@ export class TembaDate extends RapidElement {
41
41
  changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>
42
42
  ): void {
43
43
  super.firstUpdated(changedProperties);
44
+ this.store = document.querySelector('temba-store');
45
+ }
46
+
47
+ protected updated(
48
+ changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>
49
+ ): void {
50
+ super.updated(changedProperties);
44
51
  if (changedProperties.has('value')) {
45
52
  this.datetime = DateTime.fromISO(this.value);
46
- this.store = document.querySelector('temba-store');
47
53
  }
48
54
  }
49
55
 
package/src/interfaces.ts CHANGED
@@ -53,7 +53,7 @@ export interface Ticket {
53
53
  contact: ObjectReference;
54
54
  ticketer: ObjectReference;
55
55
  topic: ObjectReference;
56
- assignee?: User;
56
+ assignee?: { email: string; name: string };
57
57
  }
58
58
 
59
59
  export interface FlowResult {
@@ -341,8 +341,6 @@ export class TembaList extends RapidElement {
341
341
  } catch (error) {
342
342
  // aborted
343
343
  this.reset();
344
-
345
- console.log('error, resetting');
346
344
  console.log(error);
347
345
  return;
348
346
  }
@@ -598,12 +598,18 @@ export class Options extends RapidElement {
598
598
  }
599
599
 
600
600
  const containerStyle = {
601
- top: this.top ? `${this.top}px` : '0px',
602
- left: this.left ? `${this.left}px` : '0px',
603
601
  'margin-left': `${this.marginHorizontal}px`,
604
602
  'margin-top': `${vertical}px`,
605
603
  };
606
604
 
605
+ if (this.top) {
606
+ containerStyle['top'] = `${this.top}px`;
607
+ }
608
+
609
+ if (this.left) {
610
+ containerStyle['left'] = `${this.left}px`;
611
+ }
612
+
607
613
  const optionsStyle = {};
608
614
  if (this.width) {
609
615
  optionsStyle['width'] = `${this.width}px`;
@@ -34,8 +34,12 @@ export class StoreElement extends RapidElement {
34
34
  private handleStoreUpdated(event: CustomEvent) {
35
35
  this.store.initialHttpComplete.then(() => {
36
36
  if (event.detail.url === this.url) {
37
+ const previous = this.data;
37
38
  this.data = event.detail.data;
38
- this.fireCustomEvent(CustomEventType.Refreshed, { data: this.data });
39
+ this.fireCustomEvent(CustomEventType.Refreshed, {
40
+ data: event.detail.data,
41
+ previous,
42
+ });
39
43
  }
40
44
  });
41
45
  }
@@ -47,6 +51,8 @@ export class StoreElement extends RapidElement {
47
51
  if (properties.has('url')) {
48
52
  if (this.url) {
49
53
  this.store.makeRequest(this.url, { prepareData: this.prepareData });
54
+ } else {
55
+ this.data = null;
50
56
  }
51
57
  }
52
58
  }
@@ -152,10 +152,14 @@ export const getAssetPage = (url: string): Promise<AssetPage> => {
152
152
  return new Promise<AssetPage>((resolve, reject) => {
153
153
  getUrl(url)
154
154
  .then((response: WebResponse) => {
155
- resolve({
156
- assets: response.json.results,
157
- next: response.json.next,
158
- });
155
+ if (response.status >= 200 && response.status < 300) {
156
+ resolve({
157
+ assets: response.json.results,
158
+ next: response.json.next,
159
+ });
160
+ } else {
161
+ reject(response);
162
+ }
159
163
  })
160
164
  .catch(error => reject(error));
161
165
  });
@@ -170,8 +174,12 @@ export const getAssets = async (url: string): Promise<Asset[]> => {
170
174
  let pageUrl = url;
171
175
  while (pageUrl) {
172
176
  const assetPage = await getAssetPage(pageUrl);
173
- assets = assets.concat(assetPage.assets);
174
- pageUrl = assetPage.next;
177
+ if (assetPage.assets) {
178
+ assets = assets.concat(assetPage.assets);
179
+ pageUrl = assetPage.next;
180
+ } else {
181
+ pageUrl = null;
182
+ }
175
183
  }
176
184
  return assets;
177
185
  };
@@ -47,7 +47,7 @@ export enum Icon {
47
47
  group = 'users-01',
48
48
  group_smart = 'atom-01',
49
49
  help = 'help-circle',
50
- home = 'home-01',
50
+ home = 'settings-02',
51
51
  inbox = 'inbox-01',
52
52
  label = 'tag-01',
53
53
  language = 'globe-01',
@@ -2,8 +2,6 @@ import { useFakeTimers } from 'sinon';
2
2
  import { Button } from '../src/button/Button';
3
3
  import { Compose } from '../src/compose/Compose';
4
4
  import { ContactChat } from '../src/contacts/ContactChat';
5
- import { CustomEventType } from '../src/interfaces';
6
- import { TicketList } from '../src/list/TicketList';
7
5
  import {
8
6
  assertScreenshot,
9
7
  clearMockPosts,
@@ -42,25 +40,6 @@ const getContactChat = async (attrs: any = {}) => {
42
40
  return chat;
43
41
  };
44
42
 
45
- const list_TAG = 'temba-list';
46
- const getTicketList = async (attrs: any = {}) => {
47
- const list = (await getComponent(list_TAG, attrs)) as TicketList;
48
-
49
- if (!list.endpoint) {
50
- return list;
51
- }
52
-
53
- return new Promise<TicketList>(resolve => {
54
- list.addEventListener(
55
- CustomEventType.FetchComplete,
56
- async () => {
57
- resolve(list);
58
- },
59
- { once: true }
60
- );
61
- });
62
- };
63
-
64
43
  describe('temba-contact-chat - contact tests', () => {
65
44
  // map requests for contact history to our static files
66
45
  // we'll just us the same historylist for everybody for now
@@ -468,123 +447,3 @@ describe('temba-contact-chat - contact tests - handle send tests - text and atta
468
447
  );
469
448
  });
470
449
  });
471
-
472
- describe('temba-contact-chat - ticket tests', () => {
473
- // map requests for contact history to our static files
474
- // we'll just us the same history for everybody for now
475
- beforeEach(() => {
476
- mockGET(
477
- /\/contact\/history\/contact-.*/,
478
- '/test-assets/contacts/history.json'
479
- );
480
-
481
- mockGET(/\/api\/v2\/tickets\.json.*/, '/test-assets/tickets/empty.json');
482
- clock = useFakeTimers();
483
- });
484
-
485
- afterEach(function () {
486
- clock.restore();
487
- });
488
-
489
- it('show history and show chatbox if contact is active and ticket is open', async () => {
490
- // we are a StoreElement, so load a store first
491
- await loadStore();
492
- const chat: ContactChat = await getContactChat({
493
- contact: 'contact-carter-active',
494
- });
495
-
496
- const tickets: TicketList = await getTicketList({
497
- endpoint: '/test-assets/tickets/ticket-carter-open.json',
498
- });
499
-
500
- chat.currentTicket = tickets.items[0];
501
- chat.refresh();
502
- await chat.httpComplete;
503
-
504
- // we want to wait until our scroll is finished before taking our screenshot
505
- // once we have two scrollTops that are the same, we'll assume we're ready
506
- let lastScroll = 0;
507
- await assertScreenshot(
508
- 'contacts/contact-active-ticket-open-show-chatbox',
509
- getClip(chat),
510
- {
511
- clock: clock,
512
- predicate: () => {
513
- const currentScroll = chat
514
- .getContactHistory()
515
- .getEventsPane().scrollTop;
516
- if (currentScroll !== 0 && currentScroll === lastScroll) {
517
- return true;
518
- }
519
- lastScroll = currentScroll;
520
- },
521
- }
522
- );
523
- });
524
-
525
- it('show history and show reopen button if contact is active and ticket is closed', async () => {
526
- // we are a StoreElement, so load a store first
527
- await loadStore();
528
- const chat: ContactChat = await getContactChat({
529
- contact: 'contact-carter-active',
530
- });
531
-
532
- const tickets: TicketList = await getTicketList({
533
- endpoint: '/test-assets/tickets/ticket-carter-closed.json',
534
- });
535
- chat.currentTicket = tickets.items[0];
536
- chat.refresh();
537
- await chat.httpComplete;
538
-
539
- let lastScroll = 0;
540
- await assertScreenshot(
541
- 'contacts/contact-active-ticket-closed-show-reopen-button',
542
- getClip(chat),
543
- {
544
- clock: clock,
545
- predicate: () => {
546
- const currentScroll = chat
547
- .getContactHistory()
548
- .getEventsPane().scrollTop;
549
- if (currentScroll !== 0 && currentScroll === lastScroll) {
550
- return true;
551
- }
552
- lastScroll = currentScroll;
553
- },
554
- }
555
- );
556
- });
557
-
558
- it('show history and hide chatbox if contact is archived and ticket is closed', async () => {
559
- // we are a StoreElement, so load a store first
560
- await loadStore();
561
-
562
- const chat: ContactChat = await getContactChat({
563
- contact: 'contact-barack-archived',
564
- });
565
-
566
- const tickets: TicketList = await getTicketList({
567
- endpoint: '/test-assets/tickets/ticket-barack-closed.json',
568
- });
569
- chat.currentTicket = tickets.items[0];
570
- chat.refresh();
571
- await chat.httpComplete;
572
- let lastScroll = 0;
573
- await assertScreenshot(
574
- 'contacts/contact-archived-ticket-closed-hide-chatbox',
575
- getClip(chat),
576
- {
577
- clock: clock,
578
- predicate: () => {
579
- const currentScroll = chat
580
- .getContactHistory()
581
- .getEventsPane().scrollTop;
582
- if (currentScroll !== 0 && currentScroll === lastScroll) {
583
- return true;
584
- }
585
- lastScroll = currentScroll;
586
- },
587
- }
588
- );
589
- });
590
- });