@chat21/chat21-ionic 3.4.26 → 3.4.27-ar2

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 (25) hide show
  1. package/CHANGELOG.md +35 -2
  2. package/package.json +1 -1
  3. package/src/app/app.component.ts +67 -4
  4. package/src/app/components/canned-response/canned-response.component.html +26 -23
  5. package/src/app/components/canned-response/canned-response.component.ts +3 -1
  6. package/src/app/components/conversation-detail/message-text-area/message-text-area.component.html +1 -1
  7. package/src/app/components/conversation-detail/message-text-area/message-text-area.component.ts +15 -7
  8. package/src/app/components/project-item/project-item.component.ts +11 -11
  9. package/src/app/components/sidebar-user-details/sidebar-user-details.component.html +2 -2
  10. package/src/app/components/sidebar-user-details/sidebar-user-details.component.ts +9 -7
  11. package/src/app/modals/create-ticket/create-ticket.page.ts +4 -2
  12. package/src/app/pages/conversation-detail/conversation-detail.page.html +4 -2
  13. package/src/app/pages/conversation-detail/conversation-detail.page.ts +45 -3
  14. package/src/app/services/global-settings/global-settings.service.ts +4 -2
  15. package/src/app/services/project_users/project-users.service.spec.ts +16 -0
  16. package/src/app/services/project_users/project-users.service.ts +63 -0
  17. package/src/app/services/tiledesk/tiledesk.service.ts +0 -16
  18. package/src/app/shared/shared.module.ts +11 -0
  19. package/src/app/utils/permissions.constants.ts +135 -0
  20. package/src/app/utils/project-utils.ts +2 -2
  21. package/src/app/utils/utils.ts +18 -1
  22. package/src/chat21-core/models/projectUsers.ts +19 -0
  23. package/src/chat21-core/providers/firebase/firebase-conversation-handler.ts +1 -1
  24. package/src/chat21-core/providers/mqtt/mqtt-conversation-handler.ts +1 -1
  25. package/src/chat21-core/utils/utils.ts +10 -2
package/CHANGELOG.md CHANGED
@@ -8,6 +8,20 @@
8
8
  ### **Copyrigth**:
9
9
  *Tiledesk SRL*
10
10
 
11
+ # 3.4.27-rc4
12
+ - **bug-fixed**: extractUrls function is not able to detect url start with www or without https/http
13
+ - **bug-fixed**: if message is sent with keydown, error on domain check is not showed
14
+
15
+ # 3.4.27-rc3
16
+ - **bug-fixed**: cannot set user availability if supportMode is enabled and tiledesk_projectID url params is set
17
+
18
+ # 3.4.27-rc2
19
+ - **bug-fixed**: cannede responses role
20
+
21
+ # 3.4.27-rc1
22
+ - **added**: managed canned responses with roles
23
+ - **changed**: name in info mesage
24
+
11
25
  # 3.4.26 in PROD
12
26
 
13
27
  # 3.4.26-rc2
@@ -16,6 +30,7 @@
16
30
  # 3.4.26-rc1
17
31
  - **added**: tiledesk_projectID query param to manage user status
18
32
  - **added**: token to managane ticket feature
33
+ - **added**: getOsCode login into utils.ts
19
34
 
20
35
  # 3.4.25 in PROD
21
36
  - **changed**: pipe marked to support malicious text input
@@ -38,6 +53,24 @@
38
53
  # 3.4.22 in PROD
39
54
  - **added**: managed allowed_upload_extentions from project settings
40
55
 
56
+ # 3.4.21-rc6
57
+ - **added**: managed allowed_upload_extentions from project settings
58
+
59
+ # 3.4.21-rc5
60
+ - **added**: setConversation as read when agent click on it
61
+
62
+ # 3.4.21-rc4
63
+ - **added**: ability to init and decrement new conversation count badge
64
+
65
+ # 3.4.21-rc3
66
+ - **changed**: badge notification for agentDesktop
67
+
68
+ # 3.4.21-rc2
69
+ - **added**: count in newConversation handler event
70
+
71
+ # 3.4.21-rc1
72
+ - **added**: implement badge notification for agentDesktop sw when new conversation is assigned to logged agent
73
+
41
74
  # 3.4.21 in PROD
42
75
 
43
76
  # 3.4.20 in PROD
@@ -51,8 +84,8 @@
51
84
  - **bug-fixed**: minor fix on ion-texarea element with allowed url domain
52
85
 
53
86
  # 3.4.19-rc1
54
- - added: ability to check for if emoji is allowd to be sent in message textarea
55
- - added: ability to check for if url domain is allowd to be sent in message textarea
87
+ - **added**: ability to check for if emoji is allowd to be sent in message textarea
88
+ - **added**: ability to check for if url domain is allowd to be sent in message textarea
56
89
 
57
90
  # 3.4.18 in PROD
58
91
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@chat21/chat21-ionic",
3
3
  "author": "Tiledesk SRL",
4
- "version": "3.4.26",
4
+ "version": "3.4.27-ar2",
5
5
  "license": "MIT License",
6
6
  "homepage": "https://tiledesk.com/",
7
7
  "repository": {
@@ -44,6 +44,8 @@ import { conversationToMessage } from 'src/chat21-core/utils/utils-message';
44
44
  import { ProjectService } from './services/projects/project.service';
45
45
  import { ContactsService } from './services/contacts/contacts.service';
46
46
  import { TiledeskService } from './services/tiledesk/tiledesk.service';
47
+ import { Project } from 'src/chat21-core/models/projects';
48
+ import { ProjectUsersService } from './services/project_users/project-users.service';
47
49
 
48
50
  @Component({
49
51
  selector: 'app-root',
@@ -142,6 +144,7 @@ export class AppComponent implements OnInit {
142
144
  /**TILEDESK SERVICES */
143
145
  private tiledeskService: TiledeskService,
144
146
  private projectService: ProjectService,
147
+ private projectUsersService: ProjectUsersService,
145
148
  private contactsService: ContactsService
146
149
  ) {
147
150
 
@@ -168,6 +171,7 @@ export class AppComponent implements OnInit {
168
171
  }, { capture: true });
169
172
  }
170
173
 
174
+
171
175
  listenChatAlreadyOpenWithoutParamsInMobileMode() {
172
176
  this.events.subscribe('noparams:mobile', (isAlreadyOpenInMobileMode) => {
173
177
  // console.log('[APP-COMP] Chat is Already Open In Mobile Mode ', isAlreadyOpenInMobileMode)
@@ -296,6 +300,7 @@ export class AppComponent implements OnInit {
296
300
  this.zone = new NgZone({}); // a cosa serve?
297
301
 
298
302
  this.SUPPORT_MODE = this.g.supportMode
303
+ console.log('[APP-COMP] this.SUPPORT_MODE', this.SUPPORT_MODE)
299
304
  }
300
305
 
301
306
  });
@@ -332,7 +337,7 @@ export class AppComponent implements OnInit {
332
337
 
333
338
  listenToPostMsgs() {
334
339
  window.addEventListener("message", (event) => {
335
- this.logger.log("[APP-COMP] message event ", event);
340
+ // this.logger.log("[APP-COMP] message event ", event);
336
341
 
337
342
  if (event && event.data && event.data.action && event.data.parameter) {
338
343
  if (event.data.action === 'openJoinConversationModal') {
@@ -864,7 +869,8 @@ export class AppComponent implements OnInit {
864
869
  // console.log('[APP-COMP] PLATFORM', PLATFORM_MOBILE, 'route.snapshot', this.route.snapshot);
865
870
  if (!IDConv) {
866
871
  this.logger.log('[APP-COMP] navigateByUrl -- conversations-list');
867
- this.router.navigateByUrl('conversations-list')
872
+ const queryString = window.location.search; // restituisce ad es. "?jwt=...&tiledesk_supportMode=false"
873
+ this.router.navigateByUrl('conversations-list' + queryString);
868
874
  }
869
875
  // this.router.navigateByUrl(pageUrl);
870
876
  // this.navService.setRoot(ConversationListPage, {});
@@ -888,6 +894,11 @@ export class AppComponent implements OnInit {
888
894
  if (IDConv && FullNameConv) {
889
895
  pageUrl += IDConv + '/' + FullNameConv + '/' + Convtype
890
896
  }
897
+
898
+ const queryParams = this.route.snapshot.queryParams;
899
+ const queryString = new URLSearchParams(queryParams).toString();
900
+ pageUrl += queryString ? `?${queryString}` : '';
901
+
891
902
  // replace(/\(/g, '%28').replace(/\)/g, '%29') -> used for the encoder of any round brackets
892
903
  this.router.navigateByUrl(pageUrl.replace(/\(/g, '%28').replace(/\)/g, '%29').replace( /#/g, "%23" ));
893
904
 
@@ -1110,6 +1121,7 @@ export class AppComponent implements OnInit {
1110
1121
  if (conversation && conversation.is_new === true && this.isInitialized) {
1111
1122
  this.manageTabNotification('conv_added', conversation.sound)
1112
1123
  this.manageEventNewConversation(conversation)
1124
+ this.setNotification();
1113
1125
  }
1114
1126
  if(conversation) this.updateConversationsOnStorage()
1115
1127
  });
@@ -1174,14 +1186,18 @@ export class AppComponent implements OnInit {
1174
1186
 
1175
1187
  this.tiledeskService.initialize(serverBaseURL)
1176
1188
  this.projectService.initialize(serverBaseURL)
1189
+ this.projectUsersService.initialize(serverBaseURL)
1177
1190
  this.contactsService.initialize(serverBaseURL)
1178
1191
  // this.chatManager.startApp();
1179
1192
 
1193
+
1194
+ //INIT WEBSOCKET
1195
+ this.connetWebsocket(tiledeskToken)
1196
+
1180
1197
  // ----------------------------------------------
1181
1198
  // PUSH NOTIFICATIONS
1182
1199
  // ----------------------------------------------
1183
1200
  const pushEngine = this.appConfigProvider.getConfig().pushEngine
1184
-
1185
1201
  if (currentUser) {
1186
1202
  if (pushEngine && pushEngine !== 'none') {
1187
1203
  this.notificationsService.getNotificationPermissionAndSaveToken(currentUser.uid);
@@ -1203,6 +1219,24 @@ export class AppComponent implements OnInit {
1203
1219
  } catch (err) {
1204
1220
  this.logger.error('[APP-COMP] -> error:', err);
1205
1221
  }
1222
+
1223
+ // ----------------------------------------------
1224
+ // LAST PROJECT FROM URL
1225
+ // ----------------------------------------------
1226
+ if(this.g.projectID){
1227
+ this.projectService.getProjects().subscribe({ next: (projects: Project[]) => {
1228
+ const project = projects.find(prjct => prjct.id_project._id === this.g.projectID)
1229
+ if(project){
1230
+ this.logger.log('[APP-COMP] - GET PROJECT - project found with this.projectID', project);
1231
+ localStorage.setItem('last_project', JSON.stringify(project))
1232
+ this.events.publish('storage:last_project', project)
1233
+ }
1234
+ }, error: (error) => {
1235
+ this.logger.log('[APP-COMP] - GET PROJECT - project NOT found with this.projectID', this.g.projectID, error);
1236
+ }, complete: () => {
1237
+
1238
+ }});
1239
+ }
1206
1240
  }
1207
1241
 
1208
1242
 
@@ -1248,6 +1282,21 @@ export class AppComponent implements OnInit {
1248
1282
  myWindow.focus();
1249
1283
  }
1250
1284
 
1285
+ connetWebsocket(tiledeskToken) {
1286
+
1287
+ this.logger.log('[WEBSOCKET-JS] connetWebsocket called in [PROJECT-ITEM] tiledeskToken ', tiledeskToken)
1288
+ const appconfig = this.appConfigProvider.getConfig();
1289
+ this.logger.log('[WEBSOCKET-JS] connetWebsocket called in [PROJECT-ITEM] wsUrl ', appconfig.wsUrl)
1290
+ const WS_URL = appconfig.wsUrl + '?token=' + tiledeskToken
1291
+ this.logger.log('[WEBSOCKET-JS] connetWebsocket called in [PROJECT-ITEM] wsUrl ', WS_URL)
1292
+ this.webSocketJs.init(
1293
+ WS_URL,
1294
+ undefined,
1295
+ undefined,
1296
+ undefined
1297
+ );
1298
+ }
1299
+
1251
1300
 
1252
1301
  webSocketClose() {
1253
1302
  this.logger.log('[APP-COMP] - GO-OFFLINE - webSocketClose');
@@ -1344,7 +1393,10 @@ export class AppComponent implements OnInit {
1344
1393
 
1345
1394
  subscribeConversationSelected= (conversation: ConversationModel) => {
1346
1395
  if(conversation && conversation.is_new){
1347
- this.audio_NewConv.pause()
1396
+ this.audio_NewConv.pause();
1397
+ this.conversationsHandlerService.setConversationRead(conversation.uid)
1398
+ //UPDATE NOTIFICATION FOR NEW CONVERSATION COUNT
1399
+ this.setNotification();
1348
1400
  }
1349
1401
  }
1350
1402
 
@@ -1420,6 +1472,9 @@ export class AppComponent implements OnInit {
1420
1472
  this.logger.debug('[APP-COMP]-CONVS - INIT CONV CONVS 2', conversations)
1421
1473
  this.events.publish('appcompSubscribeToConvs:loadingIsActive', false);
1422
1474
  }
1475
+
1476
+ //INIT NOTIFICATION FOR NEW CONVERSATION COUNT
1477
+ this.setNotification();
1423
1478
  });
1424
1479
 
1425
1480
  }
@@ -1636,6 +1691,14 @@ export class AppComponent implements OnInit {
1636
1691
  this.triggerEvents.triggerOnNewConversationInit(conversation)
1637
1692
  }
1638
1693
 
1694
+ private setNotification() {
1695
+ this.logger.log('[APP-COMP] setNotification for NEW CONVERSATION');
1696
+ if(window['AGENTDESKTOP']){
1697
+ this.logger.log('[APP-COMP] manageNotification AGENTDESKTOP exist', window['AGENTDESKTOP']);
1698
+ window['AGENTDESKTOP']['TAB'].Badge(this.conversationsHandlerService.countIsNew().toString())
1699
+ }
1700
+ }
1701
+
1639
1702
 
1640
1703
  @HostListener('document:visibilitychange', [])
1641
1704
  visibilitychange() {
@@ -1,20 +1,30 @@
1
1
  <div>
2
- <div class="canned-list" *ngIf="tagsCannedFilter.length > 0">
3
- <ion-item button="true" [ngClass]="{'is_active_item': i == arrowkeyLocation}" lines="none"
4
- class="canned-item no-ripple border" id="{{'canned-item_'+ i }}"
5
- *ngFor="let canned of tagsCannedFilter; let i = index;"
6
- (click)="onClickCannedFN(canned, $event)">
7
- <div class="cannedContent">
8
- <ion-input [class.readonly]="canned?.disabled" [readonly]="canned?.disabled" type="text" [(ngModel)]="canned.title" class="title" id="{{'titleCanned_'+canned._id}}" #title></ion-input>
9
- <ion-input [class.readonly]="canned?.disabled" [readonly]="canned?.disabled" type="text" [(ngModel)]="canned.text" class="text truncate"></ion-input>
2
+ <div class="canned-list" *ngIf="!showLoading">
3
+ <span *ngIf="tagsCannedFilter.length > 0">
4
+ <ion-item button="true" [ngClass]="{'is_active_item': i == arrowkeyLocation}" lines="none"
5
+ class="canned-item no-ripple border" id="{{'canned-item_'+ i }}"
6
+ *ngFor="let canned of tagsCannedFilter; let i = index;"
7
+ (click)="onClickCannedFN(canned, $event)">
8
+ <div class="cannedContent">
9
+ <ion-input [class.readonly]="canned?.disabled" [readonly]="canned?.disabled" type="text" [(ngModel)]="canned.title" class="title" id="{{'titleCanned_'+canned._id}}" #title></ion-input>
10
+ <ion-input [class.readonly]="canned?.disabled" [readonly]="canned?.disabled" type="text" [(ngModel)]="canned.text" class="text truncate"></ion-input>
11
+ </div>
12
+ <!-- <ion-icon class="canned-item-icon" name="pin" src="assets/images/pin.svg" slot=end *ngIf="canned.pinned" (click)="onPinCanned(canned, $event)"></ion-icon>
13
+ <ion-icon class="canned-item-icon" name="pin" src="assets/images/pinned.svg" slot=end (click)="onUnPinCanned(canned, $event)"></ion-icon> -->
14
+ <ion-icon class="canned-item-icon" name="checkmark-sharp" slot=end *ngIf="(canned.createdBy === loggedUser.uid && !canned.disabled) && roles[PERMISSIONS.CANNED_RESPONSES_UPDATE]" (click)="onConfirmEditCanned(canned, $event)"></ion-icon>
15
+ <ion-icon class="canned-item-icon" name="pencil-sharp" slot=end *ngIf="(canned.createdBy === loggedUser.uid && canned.disabled) && roles[PERMISSIONS.CANNED_RESPONSES_UPDATE]" (click)="onEditCanned(canned, $event)"></ion-icon>
16
+ <ion-icon class="canned-item-icon" name="trash-bin-outline" slot=end *ngIf="(canned.createdBy === loggedUser.uid) && roles[PERMISSIONS.CANNED_RESPONSES_DELETE]" (click)="onDeleteCanned(canned, $event)"></ion-icon>
17
+ </ion-item>
18
+ </span>
19
+ <div class="no-data" *ngIf="tagsCannedFilter.length === 0">
20
+ <div class="container">
21
+ <ion-item button="false" lines="none" class="canned-item no-ripple border">
22
+ <ion-icon name="cloud-offline" slot="start"></ion-icon>
23
+ <ion-label>{{translationMap.get('THERE_ARE_NO_CANNED_RESPONSES_AVAILABLE')}}</ion-label>
24
+ </ion-item>
10
25
  </div>
11
- <!-- <ion-icon class="canned-item-icon" name="pin" src="assets/images/pin.svg" slot=end *ngIf="canned.pinned" (click)="onPinCanned(canned, $event)"></ion-icon>
12
- <ion-icon class="canned-item-icon" name="pin" src="assets/images/pinned.svg" slot=end (click)="onUnPinCanned(canned, $event)"></ion-icon> -->
13
- <ion-icon class="canned-item-icon" name="checkmark-sharp" slot=end *ngIf="canned.createdBy === loggedUser.uid && !canned.disabled" (click)="onConfirmEditCanned(canned, $event)"></ion-icon>
14
- <ion-icon class="canned-item-icon" name="pencil-sharp" slot=end *ngIf="canned.createdBy === loggedUser.uid && canned.disabled" (click)="onEditCanned(canned, $event)"></ion-icon>
15
- <ion-icon class="canned-item-icon" name="trash-bin-outline" slot=end *ngIf="canned.createdBy === loggedUser.uid" (click)="onDeleteCanned(canned, $event)"></ion-icon>
16
- </ion-item>
17
- <ion-item class="canned-item add-canned-response-wpr" button="true" lines="none" (click)="onClickAddCannedResponseFN()">
26
+ </div>
27
+ <ion-item *ngIf="roles[PERMISSIONS.CANNED_RESPONSES_CREATE]" class="canned-item add-canned-response-wpr" button="true" lines="none" (click)="onClickAddCannedResponseFN()">
18
28
  <ion-icon class="add-canned-response-icon" name="flash-outline"></ion-icon>
19
29
  <span class="add-canned-response-add-icon">+</span>
20
30
  <label class="add-canned-response-label" >{{translationMap?.get('AddNewCannedResponse')}}</label>
@@ -33,12 +43,5 @@
33
43
  <div class="label">{{translationMap.get('LABEL_LOADING')}}</div>
34
44
  </div>
35
45
  </div>
36
- <div class="no-data" *ngIf="tagsCannedFilter.length === 0 && !showLoading">
37
- <div class="container">
38
- <ion-item button="false" lines="none" class="canned-item no-ripple border">
39
- <ion-icon name="cloud-offline" slot="start"></ion-icon>
40
- <ion-label>{{translationMap.get('THERE_ARE_NO_CANNED_RESPONSES_AVAILABLE')}}</ion-label>
41
- </ion-item>
42
- </div>
43
- </div>
46
+
44
47
  </div>
@@ -9,6 +9,7 @@ import { TiledeskAuthService } from 'src/chat21-core/providers/tiledesk/tiledesk
9
9
  import { compareValues, htmlEntities } from 'src/chat21-core/utils/utils';
10
10
  import { getProjectIdSelectedConversation } from 'src/chat21-core/utils/utils-message';
11
11
  import { PLAN_NAME } from 'src/chat21-core/utils/constants';
12
+ import { PERMISSIONS } from 'src/app/utils/permissions.constants';
12
13
 
13
14
  @Component({
14
15
  selector: 'app-canned-response',
@@ -21,7 +22,7 @@ export class CannedResponseComponent implements OnInit {
21
22
  @Input() conversationWith: string;
22
23
  @Input() conversationWithFullname: string;
23
24
  @Input() currentString: string;
24
- @Input() canShowCanned: boolean = true;
25
+ @Input() roles: Array<string>;
25
26
  @Input() stylesMap: Map<string, string>;
26
27
  @Input() translationMap: Map<string, string>;
27
28
  @Output() onLoadedCannedResponses = new EventEmitter<[any]>();
@@ -36,6 +37,7 @@ export class CannedResponseComponent implements OnInit {
36
37
 
37
38
  public arrowkeyLocation = -1
38
39
 
40
+ PERMISSIONS = PERMISSIONS
39
41
 
40
42
  private logger: LoggerService = LoggerInstance.getInstance();
41
43
  constructor(
@@ -57,7 +57,7 @@
57
57
  <div class="buttons-left">
58
58
 
59
59
  <!-- CANNED RESPONSES -->
60
- <ng-container *ngIf="areVisibleCAR && supportMode">
60
+ <ng-container *ngIf="areVisibleCAR && supportMode && cannedSection">
61
61
  <div class="canned-responses-btn-wpr" tooltip="{{translationMap?.get('CANNED_RESPONSES')}}" placement="top">
62
62
  <ion-button ion-button fill="clear" class="canned-responses-btn" (click)="openCannedResponses()"
63
63
  [disabled]="!conversationWith?.startsWith(CHANNEL_TYPE.SUPPORT_GROUP) || disableTextarea">
@@ -26,6 +26,7 @@ import { CopilotService } from 'src/app/services/copilot/copilot.service';
26
26
  import { BRAND_BASE_INFO } from 'src/app/utils/utils-resources';
27
27
  import { ProjectService } from 'src/app/services/projects/project.service';
28
28
  import { Project } from 'src/chat21-core/models/projects';
29
+ import { ProjectUser } from 'src/chat21-core/models/projectUsers';
29
30
 
30
31
 
31
32
  @Component({
@@ -49,6 +50,7 @@ export class MessageTextAreaComponent implements OnInit, AfterViewInit, OnChange
49
50
  @ViewChild('fileInput', { static: false }) fileInput: any;
50
51
 
51
52
  @Input() loggedUser: UserModel;
53
+ @Input() projectUser: ProjectUser;
52
54
  @Input() conversationWith: string;
53
55
  @Input() channelType: string;
54
56
  @Input() channel: string;
@@ -62,6 +64,7 @@ export class MessageTextAreaComponent implements OnInit, AfterViewInit, OnChange
62
64
  @Input() offlineMsgEmail: boolean;
63
65
  @Input() whatsappTemplatesSection: boolean;
64
66
  @Input() isOpenInfoConversation: boolean;
67
+ @Input() cannedSection: boolean;
65
68
  @Input() stylesMap: Map<string, string>;
66
69
  @Input() translationMap: Map<string, string>;
67
70
  @Input() dropEvent: any;
@@ -563,8 +566,10 @@ export class MessageTextAreaComponent implements OnInit, AfterViewInit, OnChange
563
566
  if (!text.includes("/")) {
564
567
  this.logger.log('[CONVS-DETAIL][MSG-TEXT-AREA] onKeydown - SEND MESSAGE 1 message: ', message);
565
568
  this.logger.log("[CONVS-DETAIL] replaceTagInMessage onKeydown in msg-texarea SEND MESSAGE 1 message: ", message);
566
- this.messageString = '';
569
+
567
570
  this.sendMessage(text);
571
+ // this.messageString = '';
572
+
568
573
  this.countClicks = 0
569
574
  } else if (text.includes("/") && pos === 0 && this.countClicks > 1 && this.tagsCannedFilter.length > 0) {
570
575
  this.logger.log('[CONVS-DETAIL][MSG-TEXT-AREA] onKeydown - tagsCannedFilter.length 2: ', this.tagsCannedFilter.length);
@@ -576,9 +581,10 @@ export class MessageTextAreaComponent implements OnInit, AfterViewInit, OnChange
576
581
  this.logger.log("[CONVS-DETAIL] replaceTagInMessage onKeydown in msg-texarea SEND MESSAGE 2 this.countClicks: ", this.countClicks);
577
582
  this.logger.log("[CONVS-DETAIL][MSG-TEXT-AREA] onKeydown in msg-texarea SEND MESSAGE 2 this.countClicks: ", this.countClicks);
578
583
  this.logger.log('[CONVS-DETAIL][MSG-TEXT-AREA] onKeydown - SEND MESSAGE 2 message: ', message);
579
- this.messageString = '';
580
-
584
+
581
585
  this.sendMessage(text);
586
+ // this.messageString = '';
587
+
582
588
  this.countClicks = 0
583
589
  } else if (text.includes("/") && pos > 0 && this.countClicks > 1 && this.tagsCannedFilter.length > 0 && text.substr(-1) !== '/') {
584
590
  this.logger.log('[CONVS-DETAIL][MSG-TEXT-AREA] onKeydown - tagsCannedFilter.length 3: ', this.tagsCannedFilter.length);
@@ -590,17 +596,19 @@ export class MessageTextAreaComponent implements OnInit, AfterViewInit, OnChange
590
596
  this.logger.log("[CONVS-DETAIL] replaceTagInMessage onKeydown in msg-texarea SEND MESSAGE 2 this.countClicks: ", this.countClicks);
591
597
  this.logger.log("[CONVS-DETAIL][MSG-TEXT-AREA] onKeydown in msg-texarea SEND MESSAGE 2 this.countClicks: ", this.countClicks);
592
598
  this.logger.log('[CONVS-DETAIL][MSG-TEXT-AREA] onKeydown - SEND MESSAGE 2 message: ', message);
593
- this.messageString = '';
594
-
599
+
595
600
  this.sendMessage(text);
601
+ // this.messageString = '';
602
+
596
603
  this.countClicks = 0
597
604
  } else if (text.includes("/") && this.tagsCannedFilter.length === 0) {
598
605
  this.logger.log('[CONVS-DETAIL][MSG-TEXT-AREA] onKeydown - tagsCannedFilter.length 3: ', this.tagsCannedFilter.length);
599
606
  this.logger.log('[CONVS-DETAIL][MSG-TEXT-AREA] onKeydown - SEND MESSAGE 3 message: ', message);
600
607
  this.logger.log("[CONVS-DETAIL] replaceTagInMessage onKeydown in msg-texarea SEND MESSAGE 3 message: ", message);
601
- this.messageString = '';
602
-
608
+
603
609
  this.sendMessage(text);
610
+ // this.messageString = '';
611
+
604
612
  this.countClicks = 0
605
613
 
606
614
  }
@@ -85,17 +85,17 @@ export class ProjectItemComponent implements OnInit {
85
85
 
86
86
  connetWebsocket(tiledeskToken) {
87
87
 
88
- this.logger.log('[WEBSOCKET-JS] connetWebsocket called in [PROJECT-ITEM] tiledeskToken ', tiledeskToken)
89
- const appconfig = this.appConfigProvider.getConfig();
90
- this.logger.log('[WEBSOCKET-JS] connetWebsocket called in [PROJECT-ITEM] wsUrl ', appconfig.wsUrl)
91
- const WS_URL = appconfig.wsUrl + '?token=' + tiledeskToken
92
- this.logger.log('[WEBSOCKET-JS] connetWebsocket called in [PROJECT-ITEM] wsUrl ', WS_URL)
93
- this.webSocketJs.init(
94
- WS_URL,
95
- undefined,
96
- undefined,
97
- undefined
98
- );
88
+ // this.logger.log('[WEBSOCKET-JS] connetWebsocket called in [PROJECT-ITEM] tiledeskToken ', tiledeskToken)
89
+ // const appconfig = this.appConfigProvider.getConfig();
90
+ // this.logger.log('[WEBSOCKET-JS] connetWebsocket called in [PROJECT-ITEM] wsUrl ', appconfig.wsUrl)
91
+ // const WS_URL = appconfig.wsUrl + '?token=' + tiledeskToken
92
+ // this.logger.log('[WEBSOCKET-JS] connetWebsocket called in [PROJECT-ITEM] wsUrl ', WS_URL)
93
+ // this.webSocketJs.init(
94
+ // WS_URL,
95
+ // undefined,
96
+ // undefined,
97
+ // undefined
98
+ // );
99
99
 
100
100
  this.getLastProjectStoredAndSubscToWSAvailabilityAndConversations();
101
101
  }
@@ -81,11 +81,11 @@
81
81
  [searchable]="false">
82
82
  <ng-template ng-label-tmp let-item="item">
83
83
  <img style="width: 15px;height: 15px;position: relative; top: 1px;" height="15" width="15" [src]="item?.avatar" />
84
- <span id="sidebaravatar_{{item.name}}" style="text-transform: capitalize; margin-left:8px"> {{item.label}} </span>
84
+ <span id="sidebaravatar_{{item.name}}" style="text-transform: capitalize; margin-left:8px"> {{item.label | translate}} </span>
85
85
  </ng-template>
86
86
  <ng-template ng-option-tmp let-item="item" let-index="index">
87
87
  <img style="width: 15px;height: 15px;position: relative; top: 1px;" height="15" width="15" [src]="item?.avatar" />
88
- <span id="sidebaravatar_{{item.name}}" style="text-transform: capitalize; margin-left:8px"> {{item.label}} </span>
88
+ <span id="sidebaravatar_{{item.name}}" style="text-transform: capitalize; margin-left:8px"> {{item.label | translate}} </span>
89
89
  </ng-template>
90
90
  </ng-select>
91
91
  </section>
@@ -242,9 +242,9 @@ export class SidebarUserDetailsComponent implements OnInit, OnChanges {
242
242
  .set('SubscriptionPaymentProblem', text['SubscriptionPaymentProblem'])
243
243
  .set('ThePlanHasExpired', text['ThePlanHasExpired'])
244
244
 
245
- this.teammateStatus.forEach(element => {
246
- element.label = this.translationsMap.get(element.label)
247
- });
245
+ // this.teammateStatus.forEach(element => {
246
+ // element.label = this.translationsMap.get(element.label)
247
+ // });
248
248
 
249
249
  });
250
250
  }
@@ -261,7 +261,7 @@ export class SidebarUserDetailsComponent implements OnInit, OnChanges {
261
261
  listenToCurrentStoredProject() {
262
262
  this.events.subscribe('storage:last_project', projectObjct => {
263
263
  if (projectObjct && projectObjct !== 'undefined') {
264
- // this.logger.log('[SIDEBAR-USER-DETAILS] - GET STORED PROJECT ', projectObjct)
264
+ this.logger.log('[SIDEBAR-USER-DETAILS] - GET STORED PROJECT ', projectObjct)
265
265
 
266
266
  //TODO: recuperare info da root e non da id_project
267
267
  this.project = {
@@ -284,6 +284,8 @@ export class SidebarUserDetailsComponent implements OnInit, OnChanges {
284
284
  } else if (this.project.profile.type === 'payment' && this.project.profile.name === 'enterprise') {
285
285
  this.getEnterprisePlanTranslation();
286
286
  }
287
+
288
+ this.wsService.subscriptionToWsCurrentProjectUserAvailability(this.project._id, projectObjct._id);
287
289
  }
288
290
  })
289
291
 
@@ -329,14 +331,14 @@ export class SidebarUserDetailsComponent implements OnInit, OnChanges {
329
331
  // this.logger.log('teammateStatus ', this.teammateStatus)
330
332
  this.selectedStatus = this.teammateStatus[2].id;
331
333
  this.logger.debug('[SIDEBAR-USER-DETAILS] - PROFILE_STATUS selected option', this.teammateStatus[2].name);
332
- this.teammateStatus = this.teammateStatus.slice(0)
334
+ // this.teammateStatus = this.teammateStatus.slice(0)
333
335
  } else if (projectUser['user_available'] === false && (projectUser['profileStatus'] === '' || !projectUser['profileStatus'])) {
334
336
  this.selectedStatus = this.teammateStatus[1].id;
335
337
  this.logger.debug('[SIDEBAR-USER-DETAILS] - PROFILE_STATUS selected option', this.teammateStatus[1].name);
336
- this.teammateStatus = this.teammateStatus.slice(0)
338
+ // this.teammateStatus = this.teammateStatus.slice(0)
337
339
  } else if (projectUser['user_available'] === true && (projectUser['profileStatus'] === '' || !projectUser['profileStatus'])) {
338
340
  this.selectedStatus = this.teammateStatus[0].id
339
- this.teammateStatus = this.teammateStatus.slice(0)
341
+ // this.teammateStatus = this.teammateStatus.slice(0)
340
342
  this.logger.debug('[SIDEBAR-USER-DETAILS] - PROFILE_STATUS selected option', this.teammateStatus[0].name);
341
343
  }
342
344
  this.IS_BUSY = projectUser['isBusy']
@@ -9,6 +9,7 @@ import * as uuid from 'uuid';
9
9
  import { EventsService } from 'src/app/services/events-service'
10
10
  import { LoggerService } from 'src/chat21-core/providers/abstract/logger.service';
11
11
  import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance';
12
+ import { ProjectUsersService } from 'src/app/services/project_users/project-users.service'
12
13
 
13
14
  @Component({
14
15
  selector: 'app-create-ticket',
@@ -64,6 +65,7 @@ export class CreateTicketPage implements OnInit {
64
65
  logger: LoggerService = LoggerInstance.getInstance();
65
66
  constructor(
66
67
  public modalController: ModalController,
68
+ public projectUsersService: ProjectUsersService,
67
69
  public tiledeskService: TiledeskService,
68
70
  public appConfigProvider: AppConfigProvider,
69
71
  public events: EventsService
@@ -104,7 +106,7 @@ export class CreateTicketPage implements OnInit {
104
106
  // Create the array of the project-users and contacts displayed in the combo box "Requester"
105
107
  // -------------------------------------------------------------------------------------------
106
108
  getProjectUsersAndContacts(projctid: string) {
107
- const projectUsers = this.tiledeskService.getProjectUsersByProjectId(projctid)
109
+ const projectUsers = this.projectUsersService.getProjectUsersByProjectId(projctid)
108
110
  const leads = this.tiledeskService.getAllLeadsActiveWithLimit(projctid,10000)
109
111
 
110
112
  zip(projectUsers, leads).subscribe(
@@ -243,7 +245,7 @@ export class CreateTicketPage implements OnInit {
243
245
  // -------------------------------------------------------------------------------------------------------------------
244
246
  getProjectUserBotsAndDepts(projctid: string) {
245
247
  // this.loadingAssignee = true;
246
- const projectUsers = this.tiledeskService.getProjectUsersByProjectId( projctid)
248
+ const projectUsers = this.projectUsersService.getProjectUsersByProjectId( projctid)
247
249
  const bots = this.tiledeskService.getAllBotByProjectId(projctid)
248
250
  const depts = this.tiledeskService.getDeptsByProjectId(projctid)
249
251
 
@@ -173,7 +173,7 @@
173
173
  <!-- ----------------------------------------------------------- -->
174
174
  <app-canned-response *ngIf="SHOW_CANNED_RESPONSES"
175
175
  id="canned"
176
- [canShowCanned]="canShowCanned"
176
+ [roles]="rolesCanned"
177
177
  [conversationWith]="conversationWith"
178
178
  [conversationWithFullname]="conversationWithFullname"
179
179
  [currentString]="messageStr"
@@ -201,7 +201,8 @@
201
201
  <!-- [tagsCannedFilter]="tagsCannedFilter" -->
202
202
  <!-- openInfoConversation {{openInfoConversation}} - isMobile {{isMobile}} -->
203
203
  <app-message-text-area *ngIf="(openInfoConversation === false && isMobile === true) || (openInfoConversation === true && isMobile === false) || (openInfoConversation === false && isMobile === false)"
204
- [loggedUser]="loggedUser"
204
+ [loggedUser]="loggedUser"
205
+ [projectUser]="projectUser"
205
206
  [conversationWith]="conversationWith"
206
207
  [channelType]="channelType"
207
208
  [channel]="conversation?.attributes?.request_channel"
@@ -213,6 +214,7 @@
213
214
  [fileUploadAccept]="fileUploadAccept"
214
215
  [emailSection]="isEmailEnabled"
215
216
  [offlineMsgEmail]="offlineMsgEmail"
217
+ [cannedSection]="canShowCanned"
216
218
  [whatsappTemplatesSection]="isWhatsappTemplatesEnabled"
217
219
  [isOpenInfoConversation]="openInfoConversation"
218
220
  [stylesMap]="styleMap"
@@ -83,7 +83,10 @@ import { WebsocketService } from 'src/app/services/websocket/websocket.service';
83
83
  import { Project } from 'src/chat21-core/models/projects';
84
84
  import { Globals } from 'src/app/utils/globals';
85
85
  import { ProjectService } from 'src/app/services/projects/project.service';
86
- import { getOSCode } from 'src/app/utils/utils';
86
+ import { ProjectUsersService } from 'src/app/services/project_users/project-users.service';
87
+ import { ProjectUser } from 'src/chat21-core/models/projectUsers';
88
+ import { getOSCode, hasRole } from 'src/app/utils/utils';
89
+ import { PERMISSIONS } from 'src/app/utils/permissions.constants';
87
90
 
88
91
  @Component({
89
92
  selector: 'app-conversation-detail',
@@ -108,6 +111,7 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
108
111
  private subscriptions: Array<any>
109
112
  public tenant: string;
110
113
  public loggedUser: UserModel
114
+ public projectUser: ProjectUser;
111
115
  public conversationWith: string
112
116
  public conversationWithFullname: string
113
117
  public messages: Array<MessageModel> = []
@@ -137,6 +141,7 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
137
141
  public tagsCannedFilter: Array<any> = [];
138
142
  public SHOW_CANNED_RESPONSES: boolean = false
139
143
  public canShowCanned: boolean = true
144
+ public rolesCanned: { [key: string]: boolean }
140
145
 
141
146
  public SHOW_COPILOT_SUGGESTIONS: boolean = false;
142
147
 
@@ -240,6 +245,7 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
240
245
  public toastController: ToastController,
241
246
  public tiledeskService: TiledeskService,
242
247
  public projectService: ProjectService,
248
+ public projectUsersService: ProjectUsersService,
243
249
  private networkService: NetworkService,
244
250
  private events: EventsService,
245
251
  private webSocketService: WebsocketService,
@@ -534,7 +540,6 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
534
540
  this.logger.log('[CONVS-DETAIL] - GET PROJECTID BY CONV RECIPIENT * COMPLETE *',)
535
541
  })
536
542
  }else {
537
- this.canShowCanned = false;
538
543
  this.offlineMsgEmail = false;
539
544
  }
540
545
 
@@ -545,10 +550,13 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
545
550
  this.logger.log('[CONVS-DETAIL] - GET PROJECTID BY CONV RECIPIENT RES', project)
546
551
  if (project) {
547
552
  const projectId = project.id_project
548
- this.canShowCanned = this.projectPlanUtils.checkPlanIsExpired(project)
553
+ this.projectUser = await this.projectUsersService.getProjectUserByProjectId(project._id)
549
554
  this.offlineMsgEmail = this.checkOfflineMsgEmailIsEnabled(project)
550
555
  this.isCopilotEnabled = this.projectPlanUtils.checkProjectProfileFeature(project, 'copilot');
551
556
  this.fileUploadAccept = this.checkAcceptedUploadFile(project)
557
+ this.rolesCanned = this.checkCannedResponsesRoles(project)
558
+ this.canShowCanned = this.checkCannedResponses(project)
559
+ this.logger.log('[CONVS-DETAIL] this.rolesCanned ', this.canShowCanned)
552
560
  }
553
561
  }, (error) => {
554
562
  this.logger.error('[CONVS-DETAIL] - GET PROJECTID BY CONV RECIPIENT - ERROR ', error)
@@ -586,6 +594,40 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
586
594
  return this.appConfigProvider.getConfig().fileUploadAccept
587
595
  }
588
596
 
597
+ checkCannedResponses(project: Project): boolean {
598
+ let expires = this.projectPlanUtils.checkPlanIsExpired(project)
599
+ this.logger.log('[CONVS-DETAIL] checkCannedResponses expires ', expires)
600
+ if(expires){
601
+ return false
602
+ }
603
+
604
+ let hasRoleToShowCanned = this.rolesCanned[PERMISSIONS.CANNED_RESPONSES_READ]
605
+ this.logger.log('[CONVS-DETAIL] checkCannedResponses hasRoleToShowCanned ', hasRoleToShowCanned)
606
+ if(!hasRoleToShowCanned){
607
+ return false
608
+ }
609
+
610
+ return true
611
+ }
612
+
613
+ checkCannedResponsesRoles(project: Project): { [key: string]: boolean } {
614
+ const permissionKeys = [
615
+ 'CANNED_RESPONSES_CREATE',
616
+ 'CANNED_RESPONSES_READ',
617
+ 'CANNED_RESPONSES_UPDATE',
618
+ 'CANNED_RESPONSES_DELETE',
619
+ ] as const;
620
+
621
+ const roles: { [key: string]: boolean } = {};
622
+ for (const key of permissionKeys) {
623
+ const permission = PERMISSIONS[key];
624
+ roles[permission] = hasRole(this.projectUser, permission);
625
+ }
626
+
627
+ return roles;
628
+
629
+ }
630
+
589
631
  // getProjectIdSelectedConversation(conversationWith: string): string{
590
632
  // const conversationWith_segments = conversationWith.split('-')
591
633
  // // Removes the last element of the array if is = to the separator
@@ -88,8 +88,8 @@ export class GlobalSettingsService {
88
88
  this.logger.debug('[GLOBAL-SET] setVariableFromStorage :::::::: SET VARIABLE ---------->', Object.keys(globals));
89
89
  for (const key of Object.keys(globals)) {
90
90
  const val = this.appStorageService.getItem(key);
91
- // this.logger.debug('[GLOBAL-SET] setVariableFromStorage SET globals KEY ---------->', key);
92
- // this.logger.debug('[GLOBAL-SET] setVariableFromStorage SET globals VAL ---------->', val);
91
+ this.logger.debug('[GLOBAL-SET] setVariableFromStorage SET globals KEY ---------->', key);
92
+ this.logger.debug('[GLOBAL-SET] setVariableFromStorage SET globals VAL ---------->', val);
93
93
  if (val && val !== null) {
94
94
  // globals.setParameter(key, val);
95
95
  globals[key] = stringToBoolean(val);
@@ -111,8 +111,10 @@ export class GlobalSettingsService {
111
111
  }
112
112
 
113
113
  TEMP = getParameterByName(windowContext, 'tiledesk_supportMode');
114
+ console.log('TEMP supportMode', TEMP);
114
115
  if (TEMP) {
115
116
  globals.supportMode = stringToBoolean(TEMP);
117
+ console.log('globals.supportMode', globals.supportMode);
116
118
  }
117
119
 
118
120
  TEMP = getParameterByName(windowContext, 'tiledesk_lang');
@@ -0,0 +1,16 @@
1
+ import { TestBed } from '@angular/core/testing';
2
+
3
+ import { ProjectUsersService } from './project-users.service';
4
+
5
+ describe('ProjectUsersService', () => {
6
+ let service: ProjectUsersService;
7
+
8
+ beforeEach(() => {
9
+ TestBed.configureTestingModule({});
10
+ service = TestBed.inject(ProjectUsersService);
11
+ });
12
+
13
+ it('should be created', () => {
14
+ expect(service).toBeTruthy();
15
+ });
16
+ });
@@ -0,0 +1,63 @@
1
+ import { HttpClient, HttpHeaders } from '@angular/common/http';
2
+ import { Injectable } from '@angular/core';
3
+ import { Observable } from 'rxjs';
4
+ import { map } from 'rxjs/operators';
5
+ import { ProjectUser } from 'src/chat21-core/models/projectUsers';
6
+ import { AppStorageService } from 'src/chat21-core/providers/abstract/app-storage.service';
7
+ import { LoggerService } from 'src/chat21-core/providers/abstract/logger.service';
8
+ import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance';
9
+
10
+ @Injectable({
11
+ providedIn: 'root'
12
+ })
13
+ export class ProjectUsersService {
14
+
15
+ private SERVER_BASE_URL: string;
16
+ private tiledeskToken: string;
17
+
18
+ private logger: LoggerService = LoggerInstance.getInstance();
19
+ constructor(
20
+ public http: HttpClient,
21
+ public appStorageService: AppStorageService
22
+ ) {}
23
+
24
+ initialize(serverBaseUrl: string) {
25
+ this.logger.log('[TILEDESK-PROJECT_USERS-SERV] - initialize serverBaseUrl', serverBaseUrl);
26
+ this.SERVER_BASE_URL = serverBaseUrl;
27
+ this.tiledeskToken = this.appStorageService.getItem('tiledeskToken')
28
+ }
29
+
30
+ public getProjectUsersByProjectId(project_id: string): Observable<ProjectUser[]> {
31
+ const url = this.SERVER_BASE_URL + project_id + '/project_users/';
32
+ this.logger.log('[TILEDESK-SERVICE] - GET PROJECT-USER URL', url);
33
+
34
+ const httpOptions = {
35
+ headers: new HttpHeaders({
36
+ 'Content-Type': 'application/json',
37
+ Authorization: this.tiledeskToken
38
+ })
39
+ };
40
+ return this.http.get(url, httpOptions).pipe(map((res: any) => {
41
+ this.logger.log('[TILEDESK-SERVICE] - GET PROJECT-USER RES ', res);
42
+ return res
43
+ }))
44
+ }
45
+
46
+ public getProjectUserByProjectId(project_id: string): Promise<ProjectUser> {
47
+ const url = this.SERVER_BASE_URL + project_id + '/project_users/me';
48
+ this.logger.log('[TILEDESK-SERVICE]- GET PROJECT-USER BY USER-ID - URL', url);
49
+
50
+ const httpOptions = {
51
+ headers: new HttpHeaders({
52
+ 'Content-Type': 'application/json',
53
+ Authorization: this.tiledeskToken
54
+ })
55
+ };
56
+
57
+ return this.http.get(url, httpOptions).pipe(map((res: any) => {
58
+ this.logger.log('[TILEDESK-SERVICE] - GET PROJECT-USER RES ', res);
59
+ return res[0]
60
+ })).toPromise();
61
+ }
62
+
63
+ }
@@ -93,22 +93,6 @@ export class TiledeskService {
93
93
  }))
94
94
  }
95
95
 
96
- public getProjectUsersByProjectId(project_id: string) {
97
- const url = this.SERVER_BASE_URL + project_id + '/project_users/';
98
- this.logger.log('[TILEDESK-SERVICE] - GET PROJECT-USER URL', url);
99
-
100
- const httpOptions = {
101
- headers: new HttpHeaders({
102
- 'Content-Type': 'application/json',
103
- Authorization: this.tiledeskToken
104
- })
105
- };
106
- return this.http.get(url, httpOptions).pipe(map((res: any) => {
107
- this.logger.log('[TILEDESK-SERVICE] - GET PROJECT-USER RES ', res);
108
- return res
109
- }))
110
- }
111
-
112
96
  public getAllLeadsActiveWithLimit(project_id: string, limit: number) {
113
97
  const url = this.SERVER_BASE_URL + project_id + '/leads?limit=' + limit + '&with_fullname=true';
114
98
  this.logger.log('[TILEDESK-SERVICE] - GET ALL ACTIVE LEADS (LIMIT 10000) - URL', url);
@@ -22,6 +22,9 @@ import { MatTooltipModule } from '@angular/material/tooltip';
22
22
  import { MatSnackBarModule } from '@angular/material/snack-bar';
23
23
  import { MatSlideToggleModule } from '@angular/material/slide-toggle';
24
24
  import { SafeHtmlPipe } from '../directives/safe-html.pipe';
25
+ import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
26
+ import { createTranslateLoader } from 'src/chat21-core/utils/utils';
27
+ import { HttpClient } from '@angular/common/http';
25
28
 
26
29
  // import { MessageTextAreaComponent } from '../components/conversation-detail/message-text-area/message-text-area.component'; // MessageTextAreaComponent is part of the declarations ConversationDetailPageModule
27
30
 
@@ -140,6 +143,14 @@ import { SafeHtmlPipe } from '../directives/safe-html.pipe';
140
143
  NgSelectModule,
141
144
  FormsModule,
142
145
 
146
+ TranslateModule.forChild({
147
+ loader: {
148
+ provide: TranslateLoader,
149
+ useFactory: (createTranslateLoader),
150
+ deps: [HttpClient]
151
+ }
152
+ })
153
+
143
154
  ],
144
155
  schemas: [
145
156
  CUSTOM_ELEMENTS_SCHEMA,
@@ -0,0 +1,135 @@
1
+
2
+ export const PERMISSIONS = {
3
+ REQUEST_READ_ALL: 'request_read_all',
4
+ REQUEST_READ_GROUP: 'request_read_group',
5
+ REQUEST_READ_MY: 'request_read_my',
6
+
7
+ INBOX_READ: 'inbox_read',
8
+ REQUEST_UPDATE: 'request_update',
9
+ REQUEST_SEND: 'request_send',
10
+ REQUEST_CREATE_TICKET: 'request_create_ticket',
11
+ REQUEST_CLOSE: 'request_close',
12
+ REQUEST_JOIN: 'request_join',
13
+ REQUEST_REOPEN: 'request_reopen',
14
+ REQUEST_DELETE: 'request_delete',
15
+ REQUEST_UPDATE_STATUS: 'request_update_status',
16
+ REQUEST_UPDATE_PRIORITY: 'request_update_priority',
17
+ REQUEST_UPDATE_FOLLOWERS: 'request_update_followers',
18
+ REQUEST_UPDATE_SMART_ASSIGNMENT: 'request_update_smart_assignment',
19
+ REQUEST_UPDATE_TAGS: 'request_update_tags',
20
+ REQUEST_UPDATE_NOTES: 'request_update_notes',
21
+ REQUEST_REASSIGN:'request_reassign',
22
+ REQUEST_ADD:'request_add',
23
+ REQUEST_LEFT: 'request_left',
24
+ REQUEST_TRANSCRIPT_SEND: 'request_transcript_send',
25
+
26
+ HISTORY_READ: 'history_read',
27
+
28
+ AUTOMATIONSLOG_READ: "automationslog_read",
29
+ AUTOMATIONSLOG_CREATE: "automationslog_create",
30
+
31
+ KB_READ: 'kb_read',
32
+ KB_CONTENTS_ADD:'kb_contents_add',
33
+ KB_CONTENT_UPDATE: 'kb_content_update',
34
+ KB_CONTENT_REINDEX: 'kb_content_reindex',
35
+ KB_CONTENT_CHECK_STATUS: 'kb_content_check_status',
36
+ KB_NAMESPACE_ADD:'kb_namespace_add',
37
+ KB_SETTINGS_EDIT:'kb_settings_edit',
38
+ KB_DELETE: 'kb_delete',
39
+ KB_CONTENTS_EXPORT: 'kb_contents_export',
40
+ // KB_NAMESPACE_DELETE:'kb_namespace_delete',
41
+ // KB_CONTENTS_DELETE:'kb_contents_delete',
42
+
43
+
44
+
45
+ FLOWS_READ: 'flows_read',
46
+ FLOW_ADD: 'flow_add',
47
+ FLOW_EDIT: 'flow_edit',
48
+ FLOW_TEST: 'flow_test',
49
+ FLOW_DUPLICATE: 'flow_duplicate',
50
+ FLOW_DELETE: 'flow_delete',
51
+ FLOW_SHARE: 'flow_share',
52
+ FLOW_EXPORT: 'flow_export',
53
+ FLOW_WEBHOOK_COPY:"flow_webhook_copy",
54
+ FLOW_WEBHOOK_EDIT:"flow_webhook_edit",
55
+ FLOW_WEBHOOK_DELETE:"flow_webhook_delete",
56
+ // FLOW_VIEW_MESSAGE_GRAPH: 'flow_view_message_graph',
57
+
58
+ LEADS_READ: 'leads_read',
59
+ LEAD_UPDATE: 'lead_update',
60
+ LEAD_RESTORE: 'lead_restore',
61
+ LEAD_TRASH: 'lead_trash',
62
+ LEAD_DELETE: 'lead_delete',
63
+ LEADS_EXPORT: 'leads_export',
64
+ LEAD_BAN: 'lead_ban',
65
+ LEAD_UNBAN: 'lead_unban',
66
+
67
+ ANALYTICS_READ: 'analytics_read',
68
+ ACTIVITIES_READ: 'activities_read',
69
+
70
+ WIDGETSETUP_READ: 'widgetsetup_read',
71
+ INSTALLATION_READ: 'installation_read',
72
+ TRANSLATIONS_READ: 'translations_read',
73
+
74
+ DEPARTMENTS_LIST_READ: 'department_list_read',
75
+ DEPARTMENT_DETAIL_READ: 'department_detail_read',
76
+ DEPARTMENT_CREATE_READ: 'department_create_read',
77
+
78
+ TEAMMATES_READ: 'teammates_read',
79
+ TEAMMATES_DETAILS_READ: 'teammates_detail_read',
80
+ TEAMMATES_CREATE: 'teammates_create',
81
+ ROLES_READ: 'roles_read',
82
+ GROUPS_READ: 'groups_read',
83
+
84
+ TEAMMATE_STATUS_UPDATE: 'teammate_status_update',
85
+
86
+ EMAIL_TICKETING_READ:'email_ticketing_read',
87
+ EMAIL_TICKETING_UPDATE:'email_ticketing_update',
88
+
89
+ CANNED_RESPONSES_READ:'canned_responses_read',
90
+ CANNED_RESPONSES_UPDATE:'canned_responses_update',
91
+ CANNED_RESPONSES_CREATE:'canned_responses_create',
92
+ CANNED_RESPONSES_DELETE:'canned_responses_delete',
93
+
94
+ TAGS_READ:'tags_read',
95
+ TAG_CREATE:'tag_create',
96
+ TAG_DELETE:'tag_delete',
97
+ TAG_UPDATE:'tag_update',
98
+
99
+ HOURS_READ: 'hours_read',
100
+ HOURS_UPDATE: 'hours_update',
101
+ HOURS_DELETE: 'hours_delete',
102
+ HOURS_CREATE: 'hours_create',
103
+
104
+ INTEGRATIONS_READ: 'integrations_read',
105
+ INTEGRATIONS_UPDATE: 'integrations_update',
106
+
107
+ APPS_READ:'apps_read',
108
+ APPS_UPDATE:'apps_update',
109
+
110
+ PROJECTSETTINGS_GENERAL_READ: 'projectsettings_general_read',
111
+ PROJECTSETTINGS_GENERAL_UPDATE: 'projectsettings_general_update',
112
+
113
+ PROJECTSETTINGS_SUBSCRIPTION_READ: 'projectsettings_subscription_read',
114
+
115
+ PROJECTSETTINGS_DEVELOPER_READ: 'projectsettings_developer_read',
116
+ PROJECTSETTINGS_DEVELOPER_UPDATE: 'projectsettings_developer_update',
117
+
118
+ PROJECTSETTINGS_SMARTASSIGNMENT_READ: 'projectsettings_smartassignment_read',
119
+ PROJECTSETTINGS_SMARTASSIGNMENT_UPDATE: 'projectsettings_smartassignment_update',
120
+
121
+ PROJECTSETTINGS_NOTIFICATION_READ: 'projectsettings_notification_read',
122
+
123
+ PROJECTSETTINGS_SECURITY_READ: 'projectsettings_security_read',
124
+
125
+ PROJECTSETTINGS_BANNED_READ: 'projectsettings_banned_read',
126
+
127
+ PROJECTSETTINGS_ADVANCED_READ: 'projectsettings_advanced_read',
128
+
129
+
130
+ ACCESS_LISTS: 'accessLists',
131
+ PROFILE_PAGES: 'profilePages',
132
+ LEAD_DATA: 'leadData',
133
+ IMPORT_DATA: 'importData',
134
+ MANAGE_TAGS: 'manageTags'
135
+ };
@@ -129,9 +129,9 @@ export class ProjectPlanUtils {
129
129
 
130
130
  //case PAYMENT plan
131
131
  if(project && project.isActiveSubscription && project.profile.type=== 'payment'){
132
- check = true
133
- }else if(project && !project.isActiveSubscription && project.profile.type=== 'payment'){
134
132
  check = false
133
+ }else if(project && !project.isActiveSubscription && project.profile.type=== 'payment'){
134
+ check = true
135
135
  }
136
136
 
137
137
  return check
@@ -1,10 +1,12 @@
1
+ import { ProjectUser } from "src/chat21-core/models/projectUsers";
2
+
1
3
  export function getOSCode(key: string, token: string): boolean {
2
4
 
3
5
  if (token) {
4
6
  const keys: String[] = token.split("-");
5
7
 
6
8
  let element = keys.find(el => el.includes(key))
7
- console.log('keys', keys)
9
+ // console.log('keys', keys)
8
10
  if(element){
9
11
  element = element.split(":")[1]
10
12
  if(element && element === "F"){
@@ -21,4 +23,19 @@ export function getOSCode(key: string, token: string): boolean {
21
23
  }
22
24
 
23
25
  return false
26
+ }
27
+
28
+
29
+ export function hasRole(projectUser: ProjectUser, role: string ): boolean {
30
+ let roles = ['owner', 'admin', 'agent'];
31
+ if(roles.includes(projectUser.role)){
32
+ return true
33
+ }
34
+
35
+ if(Array.isArray(projectUser.rolePermissions) && projectUser.rolePermissions.includes(role)){
36
+ return true
37
+ }
38
+
39
+ return false
40
+
24
41
  }
@@ -0,0 +1,19 @@
1
+ export interface ProjectUser {
2
+ _id?: string;
3
+ updatedAt?: any;
4
+ createdAt?: any;
5
+ id_project?: string;
6
+ user_available?: boolean;
7
+ role?: string;
8
+ createdBy?: string;
9
+ is_group_member?: boolean;
10
+ // id_user?: string;
11
+ isAuthenticated?: boolean;
12
+ isBusy?: boolean;
13
+ status?: string;
14
+ id_user?: any;
15
+ rolePermissions?: string[];
16
+ profileStatus?: string;
17
+ presence?: { [key: string]: string}
18
+ __v?: any;
19
+ }
@@ -392,7 +392,7 @@ export class FirebaseConversationHandler extends ConversationHandlerService {
392
392
  verb = INFO_SUPPORT_USER_ADDED_VERB;
393
393
  complement = INFO_SUPPORT_USER_ADDED_COMPLEMENT;
394
394
  } else {
395
- // other user has been added to the group (and he has not a fullname, so use hes useruid)
395
+ // other user has been added to the group (and he has not a firstname, so use hes useruid)
396
396
  subject = message.attributes.messagelabel.parameters.member_id;
397
397
  verb = INFO_SUPPORT_USER_ADDED_VERB;
398
398
  complement = INFO_SUPPORT_USER_ADDED_COMPLEMENT;
@@ -370,7 +370,7 @@ export class MQTTConversationHandler extends ConversationHandlerService {
370
370
  verb = INFO_SUPPORT_USER_ADDED_VERB;
371
371
  complement = INFO_SUPPORT_USER_ADDED_COMPLEMENT;
372
372
  } else {
373
- // other user has been added to the group (and he has not a fullname, so use hes useruid)
373
+ // other user has been added to the group (and he has not a firstname, so use hes useruid)
374
374
  subject = message.attributes.messagelabel.parameters.member_id;
375
375
  verb = INFO_SUPPORT_USER_ADDED_VERB;
376
376
  complement = INFO_SUPPORT_USER_ADDED_COMPLEMENT;
@@ -1018,6 +1018,14 @@ export function isAllowedUrlInText(text: string, allowedUrls: string[]) {
1018
1018
  }
1019
1019
 
1020
1020
  function extractUrls(text: string): string[] {
1021
- const urlRegex = /https?:\/\/[^\s]+/g;
1022
- return text.match(urlRegex) || [];
1021
+ // Rileva URL con o senza protocollo (http/https)
1022
+ const urlRegex = /\b((https?:\/\/)?(www\.)?[a-z0-9.-]+\.[a-z]{2,})(\/[^\s]*)?/gi;
1023
+ const matches = text.match(urlRegex) || [];
1024
+ // Normalizza: aggiunge https:// se manca, così il parsing con new URL() funziona
1025
+ return matches.map((url) => {
1026
+ if (!/^https?:\/\//i.test(url)) {
1027
+ return 'https://' + url;
1028
+ }
1029
+ return url;
1030
+ });
1023
1031
  }