@consilioweb/payload-support 5.0.0 → 6.0.1

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 (120) hide show
  1. package/README.md +249 -22
  2. package/dist/components/RichTextEditor/index.js +1 -1
  3. package/dist/components/TicketConversation/RewriteDropdown.js +1 -1
  4. package/dist/components/TicketConversation/components/AISummaryPanel.js +3 -0
  5. package/dist/components/TicketConversation/components/ActionPanels.js +13 -8
  6. package/dist/components/TicketConversation/components/ClientHistory.js +1 -0
  7. package/dist/components/TicketConversation/components/CodeBlock.js +1 -0
  8. package/dist/components/TicketConversation/components/CodeBlockInserter.js +1 -1
  9. package/dist/components/TicketConversation/components/QuickActions.js +10 -8
  10. package/dist/components/TicketConversation/components/TicketHeader.js +2 -0
  11. package/dist/components/TicketConversation/components/TimeTrackingPanel.js +15 -9
  12. package/dist/components/TicketConversation/index.d.ts +10 -0
  13. package/dist/components/TicketConversation/index.js +25 -7
  14. package/dist/index.cjs +306 -60
  15. package/dist/index.d.cts +107 -38
  16. package/dist/index.d.ts +107 -38
  17. package/dist/index.js +300 -59
  18. package/dist/styles/BillingView.module.scss +11 -11
  19. package/dist/styles/ChatView.module.scss +7 -7
  20. package/dist/styles/CommandPalette.module.scss +1 -1
  21. package/dist/styles/CrmView.module.scss +9 -9
  22. package/dist/styles/EmailTracking.module.scss +6 -6
  23. package/dist/styles/ImportConversation.module.scss +5 -5
  24. package/dist/styles/Logs.module.scss +5 -5
  25. package/dist/styles/NewTicket.module.scss +2 -2
  26. package/dist/styles/PendingEmails.module.scss +13 -13
  27. package/dist/styles/SupportDashboard.module.scss +24 -8
  28. package/dist/styles/TicketDetail.module.scss +20 -20
  29. package/dist/styles/TicketInbox.module.scss +6 -6
  30. package/dist/styles/TicketingSettings.module.scss +10 -10
  31. package/dist/styles/TimeDashboard.module.scss +6 -6
  32. package/dist/styles/_tokens.scss +3 -1
  33. package/dist/utils/db.js +20 -0
  34. package/dist/utils/readSettings.js +150 -0
  35. package/dist/views/BillingView/client.js +15 -8
  36. package/dist/views/ChatView/client.js +2 -0
  37. package/dist/views/CrmView/client.js +2 -0
  38. package/dist/views/EmailTrackingView/client.js +23 -11
  39. package/dist/views/ImportConversationView/client.js +1 -0
  40. package/dist/views/LogsView/client.js +1 -1
  41. package/dist/views/NewTicketView/client.js +20 -13
  42. package/dist/views/PendingEmailsView/client.js +9 -3
  43. package/dist/views/SupportDashboardView/client.js +17 -19
  44. package/dist/views/TicketDetailView/client.js +23 -11
  45. package/dist/views/TicketInboxView/client.js +52 -26
  46. package/dist/views/TicketingSettingsView/TicketingSettings.module.scss +7 -7
  47. package/dist/views/TicketingSettingsView/client.js +44 -26
  48. package/dist/views/TimeDashboardView/client.js +10 -7
  49. package/dist/views/shared/ErrorBoundary.d.ts +36 -1
  50. package/dist/views/shared/ErrorBoundary.js +59 -28
  51. package/dist/views/shared/adminTokens.d.ts +15 -5
  52. package/dist/views/shared/adminTokens.js +23 -7
  53. package/dist/views/shared/icons.d.ts +49 -0
  54. package/dist/views/shared/icons.js +92 -0
  55. package/dist/views/shared/locales/en.json +15 -1
  56. package/dist/views/shared/locales/fr.json +15 -1
  57. package/package.json +9 -4
  58. package/scripts/copy-subpath-types.mjs +103 -0
  59. package/scripts/uninstall-data.mjs +178 -0
  60. package/scripts/uninstall.mjs +184 -0
  61. package/scripts/verify-dist-imports.mjs +84 -0
  62. package/src/collections/Tickets.ts +7 -0
  63. package/src/collections/TimeEntries.ts +11 -3
  64. package/src/components/RichTextEditor/index.tsx +1 -1
  65. package/src/components/TicketConversation/RewriteDropdown.tsx +1 -1
  66. package/src/components/TicketConversation/components/AISummaryPanel.tsx +3 -0
  67. package/src/components/TicketConversation/components/ActionPanels.tsx +13 -8
  68. package/src/components/TicketConversation/components/ClientHistory.tsx +1 -0
  69. package/src/components/TicketConversation/components/CodeBlock.tsx +1 -0
  70. package/src/components/TicketConversation/components/CodeBlockInserter.tsx +1 -1
  71. package/src/components/TicketConversation/components/QuickActions.tsx +10 -8
  72. package/src/components/TicketConversation/components/TicketHeader.tsx +2 -0
  73. package/src/components/TicketConversation/components/TimeTrackingPanel.tsx +17 -9
  74. package/src/components/TicketConversation/index.tsx +41 -7
  75. package/src/endpoints/delete-account.ts +191 -47
  76. package/src/endpoints/export-data.ts +104 -4
  77. package/src/endpoints/purge-logs.ts +6 -15
  78. package/src/index.ts +2 -0
  79. package/src/plugin.ts +51 -1
  80. package/src/portal/LiveChat.tsx +1 -1
  81. package/src/portal/auth/profile/page.tsx +3 -2
  82. package/src/portal/icons.tsx +91 -0
  83. package/src/styles/BillingView.module.scss +11 -11
  84. package/src/styles/ChatView.module.scss +7 -7
  85. package/src/styles/CommandPalette.module.scss +1 -1
  86. package/src/styles/CrmView.module.scss +9 -9
  87. package/src/styles/EmailTracking.module.scss +6 -6
  88. package/src/styles/ImportConversation.module.scss +5 -5
  89. package/src/styles/Logs.module.scss +5 -5
  90. package/src/styles/NewTicket.module.scss +2 -2
  91. package/src/styles/PendingEmails.module.scss +13 -13
  92. package/src/styles/SupportDashboard.module.scss +24 -8
  93. package/src/styles/TicketDetail.module.scss +20 -20
  94. package/src/styles/TicketInbox.module.scss +6 -6
  95. package/src/styles/TicketingSettings.module.scss +10 -10
  96. package/src/styles/TimeDashboard.module.scss +6 -6
  97. package/src/styles/_tokens.scss +3 -1
  98. package/src/types.ts +20 -0
  99. package/src/utils/retention.ts +111 -0
  100. package/src/utils/slugs.ts +15 -0
  101. package/src/views/BillingView/client.tsx +17 -8
  102. package/src/views/ChatView/client.tsx +2 -0
  103. package/src/views/CrmView/client.tsx +2 -0
  104. package/src/views/EmailTrackingView/client.tsx +18 -7
  105. package/src/views/ImportConversationView/client.tsx +1 -0
  106. package/src/views/LogsView/client.tsx +1 -1
  107. package/src/views/NewTicketView/client.tsx +22 -13
  108. package/src/views/PendingEmailsView/client.tsx +9 -3
  109. package/src/views/SupportDashboardView/client.tsx +11 -5
  110. package/src/views/TicketDetailView/client.tsx +25 -11
  111. package/src/views/TicketInboxView/client.tsx +47 -7
  112. package/src/views/TicketingSettingsView/TicketingSettings.module.scss +7 -7
  113. package/src/views/TicketingSettingsView/client.tsx +201 -144
  114. package/src/views/TimeDashboardView/client.tsx +10 -7
  115. package/src/views/shared/ErrorBoundary.tsx +95 -31
  116. package/src/views/shared/adminTokens.ts +28 -11
  117. package/src/views/shared/icons.tsx +146 -0
  118. package/src/views/shared/locales/en.json +15 -1
  119. package/src/views/shared/locales/fr.json +15 -1
  120. package/src/types/lucide-react.d.ts +0 -42
@@ -52,7 +52,7 @@
52
52
  font-family: ui-monospace, 'SF Mono', 'JetBrains Mono', Menlo, Consolas, monospace;
53
53
  font-size: 12px;
54
54
  font-weight: 400;
55
- color: var(--theme-text-tertiary, var(--theme-elevation-500));
55
+ color: var(--theme-text-tertiary, var(--theme-elevation-650));
56
56
  display: inline-flex;
57
57
  align-items: center;
58
58
  gap: 8px;
@@ -64,7 +64,7 @@
64
64
 
65
65
  > .ticketNumberCat,
66
66
  > .ticketNumberSource {
67
- color: var(--theme-text-tertiary, var(--theme-elevation-500));
67
+ color: var(--theme-text-tertiary, var(--theme-elevation-650));
68
68
  display: inline-flex;
69
69
  align-items: center;
70
70
  gap: 4px;
@@ -194,7 +194,7 @@
194
194
  text-transform: uppercase;
195
195
  letter-spacing: 0.08em;
196
196
  line-height: 1.3;
197
- color: var(--theme-text-tertiary, var(--theme-elevation-500));
197
+ color: var(--theme-text-tertiary, var(--theme-elevation-650));
198
198
  margin: 0 0 12px;
199
199
  display: flex;
200
200
  justify-content: space-between;
@@ -353,7 +353,7 @@
353
353
  font-size: 11px;
354
354
  font-weight: 500;
355
355
  line-height: 1.3;
356
- color: var(--theme-text-tertiary, var(--theme-elevation-500));
356
+ color: var(--theme-text-tertiary, var(--theme-elevation-650));
357
357
  white-space: nowrap;
358
358
  text-transform: uppercase;
359
359
  letter-spacing: 0.08em;
@@ -450,7 +450,7 @@
450
450
  font-size: 11px;
451
451
  font-weight: 500;
452
452
  line-height: 1.3;
453
- color: var(--theme-text-tertiary, var(--theme-elevation-500));
453
+ color: var(--theme-text-tertiary, var(--theme-elevation-650));
454
454
  }
455
455
 
456
456
  // Pills "Interne" / "Solution" — pill style, 22px height
@@ -499,7 +499,7 @@
499
499
  margin-top: 4px;
500
500
  font-size: 11px;
501
501
  font-weight: 500;
502
- color: var(--theme-text-tertiary, var(--theme-elevation-500));
502
+ color: var(--theme-text-tertiary, var(--theme-elevation-650));
503
503
  opacity: 0;
504
504
  transition: opacity 80ms ease;
505
505
  }
@@ -514,7 +514,7 @@
514
514
  font-size: 11px;
515
515
  font-weight: 500;
516
516
  line-height: 1.3;
517
- color: var(--theme-text-tertiary, var(--theme-elevation-500));
517
+ color: var(--theme-text-tertiary, var(--theme-elevation-650));
518
518
  display: inline-flex;
519
519
  align-items: center;
520
520
  height: auto;
@@ -749,7 +749,7 @@
749
749
  display: inline-flex;
750
750
  align-items: center;
751
751
  gap: 4px;
752
- color: var(--theme-text-tertiary, var(--theme-elevation-500));
752
+ color: var(--theme-text-tertiary, var(--theme-elevation-650));
753
753
  font-size: 11px;
754
754
  font-weight: 500;
755
755
  margin-left: auto;
@@ -910,7 +910,7 @@
910
910
  background: none;
911
911
  border: none;
912
912
  cursor: pointer;
913
- color: var(--theme-elevation-500);
913
+ color: var(--theme-elevation-650);
914
914
  font-size: 13px;
915
915
  font-weight: 600;
916
916
  padding: 0;
@@ -941,7 +941,7 @@
941
941
 
942
942
  .activityContent { flex: 1; min-width: 0; }
943
943
  .activityText { font-weight: 500; color: var(--theme-text); font-size: 12px; }
944
- .activityTime { font-size: 11px; color: var(--theme-elevation-500); margin-top: 1px; }
944
+ .activityTime { font-size: 11px; color: var(--theme-elevation-650); margin-top: 1px; }
945
945
 
946
946
  // ─── DROPDOWN ───
947
947
  .dropdown { position: relative; display: inline-block; }
@@ -1055,7 +1055,7 @@
1055
1055
 
1056
1056
  .splitPreview {
1057
1057
  font-size: 12px;
1058
- color: var(--theme-elevation-500);
1058
+ color: var(--theme-elevation-650);
1059
1059
  margin: 0 0 16px;
1060
1060
  line-height: 1.5;
1061
1061
  max-height: 80px;
@@ -1092,7 +1092,7 @@
1092
1092
  background: none;
1093
1093
  font-size: 13px;
1094
1094
  font-weight: 600;
1095
- color: var(--theme-elevation-500);
1095
+ color: var(--theme-elevation-650);
1096
1096
  cursor: pointer;
1097
1097
  &:hover { background: var(--theme-elevation-100); }
1098
1098
  }
@@ -1145,7 +1145,7 @@
1145
1145
  cursor: pointer;
1146
1146
  font-size: 11px;
1147
1147
  line-height: 1;
1148
- color: var(--theme-text-tertiary, var(--theme-elevation-500));
1148
+ color: var(--theme-text-tertiary, var(--theme-elevation-650));
1149
1149
  padding: 0;
1150
1150
  display: inline-flex;
1151
1151
  align-items: center;
@@ -1166,7 +1166,7 @@
1166
1166
  font-size: 11px;
1167
1167
  font-weight: 500;
1168
1168
  line-height: 1;
1169
- color: var(--theme-text-tertiary, var(--theme-elevation-500));
1169
+ color: var(--theme-text-tertiary, var(--theme-elevation-650));
1170
1170
  cursor: pointer;
1171
1171
  transition: border-color 80ms ease, color 80ms ease;
1172
1172
  &:hover {
@@ -1607,7 +1607,7 @@
1607
1607
  align-items: center;
1608
1608
  gap: 6px;
1609
1609
  font-size: 10px;
1610
- color: var(--theme-elevation-500);
1610
+ color: var(--theme-elevation-650);
1611
1611
  }
1612
1612
 
1613
1613
  .previousTicketsStatus {
@@ -1755,7 +1755,7 @@
1755
1755
  text-transform: uppercase;
1756
1756
  letter-spacing: 0.08em;
1757
1757
  line-height: 1.3;
1758
- color: var(--theme-text-tertiary, var(--theme-elevation-500));
1758
+ color: var(--theme-text-tertiary, var(--theme-elevation-650));
1759
1759
  margin: 12px 0 8px;
1760
1760
  }
1761
1761
  .aiCardNextActions {
@@ -1805,13 +1805,13 @@
1805
1805
  }
1806
1806
  .aiCardNextLabelDone {
1807
1807
  text-decoration: line-through;
1808
- color: var(--theme-text-tertiary, var(--theme-elevation-500));
1808
+ color: var(--theme-text-tertiary, var(--theme-elevation-650));
1809
1809
  }
1810
1810
  .aiCardNextWhen {
1811
1811
  font-size: 11px;
1812
1812
  font-weight: 500;
1813
1813
  line-height: 1.3;
1814
- color: var(--theme-text-tertiary, var(--theme-elevation-500));
1814
+ color: var(--theme-text-tertiary, var(--theme-elevation-650));
1815
1815
  flex-shrink: 0;
1816
1816
  }
1817
1817
 
@@ -1856,12 +1856,12 @@
1856
1856
  margin: 0 2px;
1857
1857
  }
1858
1858
  .ticketNumberCat {
1859
- color: var(--theme-text-tertiary, var(--theme-elevation-500));
1859
+ color: var(--theme-text-tertiary, var(--theme-elevation-650));
1860
1860
  font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
1861
1861
  font-weight: 400;
1862
1862
  }
1863
1863
  .ticketNumberSource {
1864
- color: var(--theme-text-tertiary, var(--theme-elevation-500));
1864
+ color: var(--theme-text-tertiary, var(--theme-elevation-650));
1865
1865
  font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
1866
1866
  display: inline-flex;
1867
1867
  align-items: center;
@@ -26,7 +26,7 @@
26
26
 
27
27
  .subtitle {
28
28
  font-size: 13px;
29
- color: var(--theme-elevation-500);
29
+ color: var(--theme-elevation-650);
30
30
  margin: 2px 0 0;
31
31
  }
32
32
 
@@ -125,7 +125,7 @@
125
125
  padding: 0 12px;
126
126
  font-size: 13px;
127
127
  font-weight: 500;
128
- color: var(--theme-elevation-500);
128
+ color: var(--theme-elevation-650);
129
129
  background: none;
130
130
  border: none;
131
131
  border-radius: 6px;
@@ -155,7 +155,7 @@
155
155
  padding: 0 6px;
156
156
  font-size: 11px;
157
157
  font-weight: 600;
158
- color: var(--theme-elevation-500);
158
+ color: var(--theme-elevation-650);
159
159
  background: var(--theme-elevation-200);
160
160
  border-radius: 999px;
161
161
  }
@@ -179,7 +179,7 @@
179
179
  .sortSelect {
180
180
  font-size: 12px;
181
181
  font-weight: 600;
182
- color: var(--theme-elevation-500);
182
+ color: var(--theme-elevation-650);
183
183
  background: none;
184
184
  border: none;
185
185
  cursor: pointer;
@@ -268,7 +268,7 @@
268
268
 
269
269
  .client {
270
270
  font-size: 12px;
271
- color: var(--theme-elevation-500);
271
+ color: var(--theme-elevation-650);
272
272
  overflow: hidden;
273
273
  text-overflow: ellipsis;
274
274
  white-space: nowrap;
@@ -333,7 +333,7 @@
333
333
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
334
334
  font-size: 11px;
335
335
  font-weight: 500;
336
- color: var(--theme-elevation-500);
336
+ color: var(--theme-elevation-650);
337
337
  display: flex;
338
338
  gap: 12px;
339
339
  z-index: 10;
@@ -15,7 +15,7 @@ $green: #16a34a;
15
15
 
16
16
  .intro {
17
17
  font-size: 13px;
18
- color: var(--theme-elevation-500);
18
+ color: var(--theme-elevation-650);
19
19
  margin: 0 0 24px;
20
20
  line-height: 1.6;
21
21
  }
@@ -59,7 +59,7 @@ $green: #16a34a;
59
59
 
60
60
  .sectionChevron {
61
61
  font-size: 16px;
62
- color: var(--theme-elevation-500);
62
+ color: var(--theme-elevation-650);
63
63
  transition: transform 150ms;
64
64
 
65
65
  &.open {
@@ -73,7 +73,7 @@ $green: #16a34a;
73
73
 
74
74
  .sectionDescription {
75
75
  font-size: 12px;
76
- color: var(--theme-elevation-500);
76
+ color: var(--theme-elevation-650);
77
77
  margin: 0 0 16px;
78
78
  line-height: 1.5;
79
79
  }
@@ -97,7 +97,7 @@ $green: #16a34a;
97
97
 
98
98
  .fieldDescription {
99
99
  font-size: 11px;
100
- color: var(--theme-elevation-500);
100
+ color: var(--theme-elevation-650);
101
101
  font-weight: 400;
102
102
  margin-top: 2px;
103
103
  }
@@ -131,7 +131,7 @@ $green: #16a34a;
131
131
  border-radius: 6px;
132
132
  border: 1px solid var(--theme-elevation-300);
133
133
  font-size: 13px;
134
- color: var(--theme-elevation-500);
134
+ color: var(--theme-elevation-650);
135
135
  background-color: var(--theme-elevation-100);
136
136
  width: 100%;
137
137
  max-width: 400px;
@@ -179,7 +179,7 @@ $green: #16a34a;
179
179
 
180
180
  .inlineDesc {
181
181
  font-size: 12px;
182
- color: var(--theme-elevation-500);
182
+ color: var(--theme-elevation-650);
183
183
  }
184
184
 
185
185
  // ─── BADGE ───
@@ -251,7 +251,7 @@ $green: #16a34a;
251
251
 
252
252
  .featureDesc {
253
253
  font-size: 12px;
254
- color: var(--theme-elevation-500);
254
+ color: var(--theme-elevation-650);
255
255
  line-height: 1.4;
256
256
  }
257
257
 
@@ -271,7 +271,7 @@ $green: #16a34a;
271
271
 
272
272
  .aiToggleDesc {
273
273
  font-size: 12px;
274
- color: var(--theme-elevation-500);
274
+ color: var(--theme-elevation-650);
275
275
  margin-left: 8px;
276
276
  }
277
277
 
@@ -290,7 +290,7 @@ $green: #16a34a;
290
290
  background-color: transparent;
291
291
  cursor: pointer;
292
292
  font-size: 12px;
293
- color: var(--theme-elevation-500);
293
+ color: var(--theme-elevation-650);
294
294
  white-space: nowrap;
295
295
  }
296
296
 
@@ -303,7 +303,7 @@ $green: #16a34a;
303
303
 
304
304
  .slaHint {
305
305
  font-size: 12px;
306
- color: var(--theme-elevation-500);
306
+ color: var(--theme-elevation-650);
307
307
  }
308
308
 
309
309
  // ─── AI SUB FEATURES LABEL ───
@@ -28,7 +28,7 @@
28
28
  }
29
29
 
30
30
  .subtitle {
31
- color: var(--theme-elevation-500);
31
+ color: var(--theme-elevation-650);
32
32
  margin: 4px 0 0;
33
33
  font-size: 14px;
34
34
  }
@@ -80,7 +80,7 @@
80
80
  .label {
81
81
  font-size: 11px;
82
82
  font-weight: 700;
83
- color: var(--theme-elevation-500);
83
+ color: var(--theme-elevation-650);
84
84
  margin-bottom: 3px;
85
85
  }
86
86
 
@@ -146,7 +146,7 @@
146
146
  font-size: 10px;
147
147
  font-weight: 700;
148
148
  text-transform: uppercase;
149
- color: var(--theme-elevation-500);
149
+ color: var(--theme-elevation-650);
150
150
  }
151
151
 
152
152
  .kpiValue {
@@ -201,7 +201,7 @@
201
201
  border: 1px solid var(--theme-elevation-300);
202
202
  border-radius: 8px;
203
203
  background: var(--theme-elevation-100);
204
- color: var(--theme-elevation-500);
204
+ color: var(--theme-elevation-650);
205
205
  font-size: 14px;
206
206
  }
207
207
 
@@ -257,13 +257,13 @@
257
257
 
258
258
  .tdSubject {
259
259
  padding: 6px 8px;
260
- color: var(--theme-elevation-500);
260
+ color: var(--theme-elevation-650);
261
261
  font-size: 12px;
262
262
  }
263
263
 
264
264
  .tdDescription {
265
265
  padding: 6px 8px;
266
- color: var(--theme-elevation-500);
266
+ color: var(--theme-elevation-650);
267
267
  font-size: 12px;
268
268
  }
269
269
 
@@ -1,6 +1,8 @@
1
1
  // Design tokens — based on Payload CMS CSS variables
2
2
  $text: var(--theme-text);
3
- $text-secondary: var(--theme-elevation-500);
3
+ // elevation-650, not 500: Payload's dark theme skips the 500 step, leaving it
4
+ // at rgb(128,128,128) — 3.62:1 light / 4.03:1 dark, below WCAG AA in both.
5
+ $text-secondary: var(--theme-elevation-650);
4
6
  $text-muted: var(--theme-elevation-400);
5
7
  $bg: var(--theme-elevation-50);
6
8
  $bg-card: var(--theme-elevation-100);
@@ -0,0 +1,20 @@
1
+ function dbFind(payload, slug, options = {}) {
2
+ return payload.find({ collection: slug, ...options });
3
+ }
4
+ function dbFindByID(payload, slug, options) {
5
+ return payload.findByID({ collection: slug, ...options });
6
+ }
7
+ function dbCreate(payload, slug, options) {
8
+ return payload.create({ collection: slug, ...options });
9
+ }
10
+ function dbUpdate(payload, slug, options) {
11
+ return payload.update({ collection: slug, ...options });
12
+ }
13
+ function dbCount(payload, slug, options = {}) {
14
+ return payload.count({ collection: slug, ...options });
15
+ }
16
+ function dbDelete(payload, slug, options) {
17
+ return payload.delete({ collection: slug, ...options });
18
+ }
19
+
20
+ export { dbCount, dbCreate, dbDelete, dbFind, dbFindByID, dbUpdate };
@@ -0,0 +1,150 @@
1
+ import { dbFind } from './db.js';
2
+ import { DEFAULT_TICKETING_FEATURES, projectAutoClose, normalizeFeatures } from './features.js';
3
+
4
+ const SUPPORT_SETTINGS_PREF_KEY = "support-settings";
5
+ const PREF_KEY = SUPPORT_SETTINGS_PREF_KEY;
6
+ const USER_PREFS_KEY_PREFIX = "support-user-prefs";
7
+ const LEGACY_ROUND_ROBIN_KEY = "support-round-robin";
8
+ const SUPPORT_STAFF_SLUG_CONFIG_KEY = "supportStaffCollection";
9
+ function resolveStaffPrefSlug(payload, staffSlug) {
10
+ if (staffSlug) return staffSlug;
11
+ const config = payload.config;
12
+ const registered = config?.custom?.[SUPPORT_STAFF_SLUG_CONFIG_KEY];
13
+ if (typeof registered === "string" && registered) return registered;
14
+ return config?.admin?.user || "users";
15
+ }
16
+ const DEFAULT_SETTINGS = {
17
+ email: { fromAddress: "", fromName: "Support", replyToAddress: "" },
18
+ ai: { provider: "anthropic", model: "claude-haiku-4-5-20251001", enableSentiment: true, enableSynthesis: true, enableSuggestion: true, enableRewrite: true },
19
+ sla: { firstResponseMinutes: 120, resolutionMinutes: 1440, businessHoursOnly: true, escalationEmail: "" },
20
+ autoClose: { enabled: true, daysBeforeClose: 7, reminderDaysBefore: 2 },
21
+ features: { ...DEFAULT_TICKETING_FEATURES }
22
+ };
23
+ const DEFAULT_USER_PREFS = {
24
+ locale: "fr",
25
+ signature: ""
26
+ };
27
+ const settingsCache = /* @__PURE__ */ new Map();
28
+ const SETTINGS_TTL_MS = 6e4;
29
+ const SETTINGS_CACHE_MAX = 8;
30
+ const warnedForeignSettingsRow = /* @__PURE__ */ new Set();
31
+ function invalidateSupportSettingsCache() {
32
+ settingsCache.clear();
33
+ warnedForeignSettingsRow.clear();
34
+ }
35
+ function mergeSupportSettings(stored, base = DEFAULT_SETTINGS) {
36
+ const autoClose = { ...base.autoClose, ...stored?.autoClose };
37
+ return {
38
+ email: { ...base.email, ...stored?.email },
39
+ ai: { ...base.ai, ...stored?.ai },
40
+ sla: { ...base.sla, ...stored?.sla },
41
+ autoClose,
42
+ // `autoClose` / `autoCloseDays` are projections of the block above, so they
43
+ // are recomputed here rather than trusted from whatever was persisted.
44
+ features: projectAutoClose(
45
+ normalizeFeatures({ ...base.features, ...stored?.features }),
46
+ autoClose
47
+ )
48
+ };
49
+ }
50
+ async function readSupportSettingsState(payload, staffSlug) {
51
+ const staff = resolveStaffPrefSlug(payload, staffSlug);
52
+ const cached = settingsCache.get(staff);
53
+ if (cached && Date.now() - cached.ts < SETTINGS_TTL_MS) {
54
+ return cached.value;
55
+ }
56
+ let value = {
57
+ settings: mergeSupportSettings(null),
58
+ featuresConfigured: false
59
+ };
60
+ try {
61
+ const prefs = await dbFind(payload, "payload-preferences", {
62
+ // Sibling keys are AND-ed by Payload. The `user.relationTo` clause is the
63
+ // security boundary: without it any authenticated principal can plant a
64
+ // `support-settings` row and own the plugin's server settings.
65
+ where: { key: { equals: PREF_KEY }, "user.relationTo": { equals: staff } },
66
+ // The upsert is scoped per admin user, so several rows can share the key.
67
+ // Sorting makes "last write wins" deterministic instead of arbitrary.
68
+ sort: "-updatedAt",
69
+ limit: 1,
70
+ depth: 0,
71
+ overrideAccess: true
72
+ });
73
+ if (prefs.docs.length > 0) {
74
+ const stored = prefs.docs[0].value;
75
+ const featuresConfigured = !!stored.features && typeof stored.features === "object";
76
+ const settings = mergeSupportSettings(stored);
77
+ if (!featuresConfigured) {
78
+ settings.features.roundRobin = await readLegacyRoundRobin(payload, staff);
79
+ }
80
+ value = { settings, featuresConfigured };
81
+ } else {
82
+ await warnOnForeignSettingsRow(payload, staff);
83
+ }
84
+ } catch {
85
+ }
86
+ if (settingsCache.size >= SETTINGS_CACHE_MAX && !settingsCache.has(staff)) settingsCache.clear();
87
+ settingsCache.set(staff, { value, ts: Date.now() });
88
+ return value;
89
+ }
90
+ async function warnOnForeignSettingsRow(payload, staff) {
91
+ if (warnedForeignSettingsRow.has(staff)) return;
92
+ try {
93
+ const any = await dbFind(payload, "payload-preferences", {
94
+ where: { key: { equals: PREF_KEY } },
95
+ limit: 1,
96
+ depth: 0,
97
+ overrideAccess: true
98
+ });
99
+ if (any.docs.length === 0) return;
100
+ if (warnedForeignSettingsRow.size >= SETTINGS_CACHE_MAX) warnedForeignSettingsRow.clear();
101
+ warnedForeignSettingsRow.add(staff);
102
+ console.warn(
103
+ `[support] A "${PREF_KEY}" preference row exists but none is owned by the "${staff}" collection: the plugin is running on its DEFAULT settings. Either the staff auth collection differs from \`admin.user\`, or the row was written by a principal that is not staff \u2014 in which case it is ignored on purpose.`
104
+ );
105
+ } catch {
106
+ }
107
+ }
108
+ async function readSupportSettings(payload, staffSlug) {
109
+ return (await readSupportSettingsState(payload, staffSlug)).settings;
110
+ }
111
+ async function readLegacyRoundRobin(payload, staff) {
112
+ try {
113
+ const prefs = await dbFind(payload, "payload-preferences", {
114
+ where: { key: { equals: LEGACY_ROUND_ROBIN_KEY }, "user.relationTo": { equals: staff } },
115
+ limit: 1,
116
+ depth: 0,
117
+ overrideAccess: true
118
+ });
119
+ if (prefs.docs.length > 0) {
120
+ return prefs.docs[0].value?.enabled === true;
121
+ }
122
+ } catch {
123
+ }
124
+ return DEFAULT_TICKETING_FEATURES.roundRobin;
125
+ }
126
+ async function readUserPrefs(payload, userId, staffSlug) {
127
+ try {
128
+ const key = `${USER_PREFS_KEY_PREFIX}-${userId}`;
129
+ const prefs = await dbFind(payload, "payload-preferences", {
130
+ where: {
131
+ key: { equals: key },
132
+ "user.relationTo": { equals: resolveStaffPrefSlug(payload, staffSlug) }
133
+ },
134
+ limit: 1,
135
+ depth: 0,
136
+ overrideAccess: true
137
+ });
138
+ if (prefs.docs.length > 0) {
139
+ const stored = prefs.docs[0].value;
140
+ return {
141
+ locale: stored.locale || DEFAULT_USER_PREFS.locale,
142
+ signature: stored.signature ?? DEFAULT_USER_PREFS.signature
143
+ };
144
+ }
145
+ } catch {
146
+ }
147
+ return { ...DEFAULT_USER_PREFS };
148
+ }
149
+
150
+ export { DEFAULT_SETTINGS, DEFAULT_USER_PREFS, SUPPORT_SETTINGS_PREF_KEY, SUPPORT_STAFF_SLUG_CONFIG_KEY, invalidateSupportSettingsCache, mergeSupportSettings, readSupportSettings, readSupportSettingsState, readUserPrefs, resolveStaffPrefSlug };
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
- import React, { useState, useCallback } from 'react';
3
+ import React, { useId, useState, useCallback } from 'react';
4
4
  import { useTranslation } from '../../components/TicketConversation/hooks/useTranslation.js';
5
5
  import { DATE_LOCALE } from '../shared/dateLocale.js';
6
6
  import styles from '../../styles/BillingView.module.scss';
@@ -42,6 +42,10 @@ function getQuarterRange(offset) {
42
42
  }
43
43
  const BillingClient = () => {
44
44
  const { t } = useTranslation();
45
+ const fromId = useId();
46
+ const toId = useId();
47
+ const projectFilterId = useId();
48
+ const rateId = useId();
45
49
  const [from, setFrom] = useState(() => getMonthRange(0).from);
46
50
  const [to, setTo] = useState(() => getMonthRange(0).to);
47
51
  const [projectId, setProjectId] = useState("");
@@ -218,26 +222,27 @@ const BillingClient = () => {
218
222
  ] }),
219
223
  /* @__PURE__ */ jsxs("div", { className: styles.filterRow, children: [
220
224
  /* @__PURE__ */ jsxs("div", { className: styles.fieldGroup, children: [
221
- /* @__PURE__ */ jsx("label", { className: styles.label, children: t("billing.filters.from") }),
222
- /* @__PURE__ */ jsx("input", { type: "date", value: from, onChange: (e) => setFrom(e.target.value), className: styles.input })
225
+ /* @__PURE__ */ jsx("label", { className: styles.label, htmlFor: fromId, children: t("billing.filters.from") }),
226
+ /* @__PURE__ */ jsx("input", { id: fromId, type: "date", value: from, onChange: (e) => setFrom(e.target.value), className: styles.input })
223
227
  ] }),
224
228
  /* @__PURE__ */ jsxs("div", { className: styles.fieldGroup, children: [
225
- /* @__PURE__ */ jsx("label", { className: styles.label, children: t("billing.filters.to") }),
226
- /* @__PURE__ */ jsx("input", { type: "date", value: to, onChange: (e) => setTo(e.target.value), className: styles.input })
229
+ /* @__PURE__ */ jsx("label", { className: styles.label, htmlFor: toId, children: t("billing.filters.to") }),
230
+ /* @__PURE__ */ jsx("input", { id: toId, type: "date", value: to, onChange: (e) => setTo(e.target.value), className: styles.input })
227
231
  ] }),
228
232
  /* @__PURE__ */ jsxs("div", { className: styles.fieldGroup, children: [
229
- /* @__PURE__ */ jsx("label", { className: styles.label, children: t("billing.filters.project") }),
230
- /* @__PURE__ */ jsxs("select", { value: projectId, onChange: (e) => setProjectId(e.target.value), className: styles.select, children: [
233
+ /* @__PURE__ */ jsx("label", { className: styles.label, htmlFor: projectFilterId, children: t("billing.filters.project") }),
234
+ /* @__PURE__ */ jsxs("select", { id: projectFilterId, value: projectId, onChange: (e) => setProjectId(e.target.value), className: styles.select, children: [
231
235
  /* @__PURE__ */ jsx("option", { value: "", children: t("ticket.allProjects") }),
232
236
  projects.map((p) => /* @__PURE__ */ jsx("option", { value: p.id, children: p.name }, p.id))
233
237
  ] })
234
238
  ] }),
235
239
  /* @__PURE__ */ jsxs("div", { className: styles.fieldGroup, children: [
236
- /* @__PURE__ */ jsx("label", { className: styles.label, children: t("billing.filters.hourlyRate") }),
240
+ /* @__PURE__ */ jsx("label", { className: styles.label, htmlFor: rateId, children: t("billing.filters.hourlyRate") }),
237
241
  /* @__PURE__ */ jsxs("div", { className: styles.rateRow, children: [
238
242
  /* @__PURE__ */ jsx(
239
243
  "input",
240
244
  {
245
+ id: rateId,
241
246
  type: "number",
242
247
  value: rate,
243
248
  onChange: (e) => setRate(Number(e.target.value)),
@@ -293,6 +298,7 @@ const BillingClient = () => {
293
298
  "input",
294
299
  {
295
300
  type: "checkbox",
301
+ "aria-label": t("billing.tableCheckboxTitle"),
296
302
  checked: group.tickets.every((t2) => billedTickets.has(t2.id)),
297
303
  onChange: () => {
298
304
  const ids = group.tickets.map((t2) => t2.id);
@@ -326,6 +332,7 @@ const BillingClient = () => {
326
332
  "input",
327
333
  {
328
334
  type: "checkbox",
335
+ "aria-label": isBilled ? t("billing.markUnbilledTitle") : t("billing.markBilledTitle"),
329
336
  checked: isBilled,
330
337
  onChange: () => toggleBilled(ticket.id),
331
338
  className: styles.checkbox,
@@ -252,6 +252,7 @@ const ChatViewClient = () => {
252
252
  cannedResponses.length > 0 && /* @__PURE__ */ jsxs(
253
253
  "select",
254
254
  {
255
+ "aria-label": t("chat.quickReply"),
255
256
  onChange: (e) => {
256
257
  const cr = cannedResponses.find((c) => String(c.id) === e.target.value);
257
258
  if (cr) setInput(cr.body);
@@ -269,6 +270,7 @@ const ChatViewClient = () => {
269
270
  "input",
270
271
  {
271
272
  type: "text",
273
+ "aria-label": t("chat.inputPlaceholder"),
272
274
  value: input,
273
275
  onChange: (e) => setInput(e.target.value),
274
276
  placeholder: t("chat.inputPlaceholder"),
@@ -211,6 +211,7 @@ Cette action est irr\xE9versible.`)) return;
211
211
  "input",
212
212
  {
213
213
  type: "text",
214
+ "aria-label": t("crm.searchLabel"),
214
215
  placeholder: t("crm.searchPlaceholder"),
215
216
  value: search,
216
217
  onChange: (e) => setSearch(e.target.value),
@@ -295,6 +296,7 @@ Cette action est irr\xE9versible.`)) return;
295
296
  "input",
296
297
  {
297
298
  type: "text",
299
+ "aria-label": t("crm.mergeTitle"),
298
300
  value: mergeSearch,
299
301
  onChange: (e) => setMergeSearch(e.target.value),
300
302
  placeholder: t("crm.mergeSearchPlaceholder"),