@consilioweb/payload-support 5.0.0 → 6.0.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.
- package/README.md +249 -22
- package/dist/components/RichTextEditor/index.js +1 -1
- package/dist/components/TicketConversation/RewriteDropdown.js +1 -1
- package/dist/components/TicketConversation/components/AISummaryPanel.js +3 -0
- package/dist/components/TicketConversation/components/ActionPanels.js +13 -8
- package/dist/components/TicketConversation/components/ClientHistory.js +1 -0
- package/dist/components/TicketConversation/components/CodeBlock.js +1 -0
- package/dist/components/TicketConversation/components/CodeBlockInserter.js +1 -1
- package/dist/components/TicketConversation/components/QuickActions.js +10 -8
- package/dist/components/TicketConversation/components/TicketHeader.js +2 -0
- package/dist/components/TicketConversation/components/TimeTrackingPanel.js +15 -9
- package/dist/components/TicketConversation/index.d.ts +10 -0
- package/dist/components/TicketConversation/index.js +25 -7
- package/dist/index.cjs +306 -60
- package/dist/index.d.cts +107 -38
- package/dist/index.d.ts +107 -38
- package/dist/index.js +300 -59
- package/dist/styles/BillingView.module.scss +11 -11
- package/dist/styles/ChatView.module.scss +7 -7
- package/dist/styles/CommandPalette.module.scss +1 -1
- package/dist/styles/CrmView.module.scss +9 -9
- package/dist/styles/EmailTracking.module.scss +6 -6
- package/dist/styles/ImportConversation.module.scss +5 -5
- package/dist/styles/Logs.module.scss +5 -5
- package/dist/styles/NewTicket.module.scss +2 -2
- package/dist/styles/PendingEmails.module.scss +13 -13
- package/dist/styles/SupportDashboard.module.scss +24 -8
- package/dist/styles/TicketDetail.module.scss +20 -20
- package/dist/styles/TicketInbox.module.scss +6 -6
- package/dist/styles/TicketingSettings.module.scss +10 -10
- package/dist/styles/TimeDashboard.module.scss +6 -6
- package/dist/styles/_tokens.scss +3 -1
- package/dist/views/BillingView/client.js +15 -8
- package/dist/views/ChatView/client.js +2 -0
- package/dist/views/CrmView/client.js +2 -0
- package/dist/views/EmailTrackingView/client.js +23 -11
- package/dist/views/ImportConversationView/client.js +1 -0
- package/dist/views/LogsView/client.js +1 -1
- package/dist/views/NewTicketView/client.js +20 -13
- package/dist/views/PendingEmailsView/client.js +9 -3
- package/dist/views/SupportDashboardView/client.js +17 -19
- package/dist/views/TicketDetailView/client.js +23 -11
- package/dist/views/TicketInboxView/client.js +52 -26
- package/dist/views/TicketingSettingsView/TicketingSettings.module.scss +7 -7
- package/dist/views/TicketingSettingsView/client.js +44 -26
- package/dist/views/TimeDashboardView/client.js +10 -7
- package/dist/views/shared/ErrorBoundary.d.ts +36 -1
- package/dist/views/shared/ErrorBoundary.js +59 -28
- package/dist/views/shared/adminTokens.d.ts +15 -5
- package/dist/views/shared/adminTokens.js +23 -7
- package/dist/views/shared/icons.d.ts +49 -0
- package/dist/views/shared/icons.js +92 -0
- package/dist/views/shared/locales/en.json +15 -1
- package/dist/views/shared/locales/fr.json +15 -1
- package/package.json +6 -2
- package/scripts/uninstall-data.mjs +178 -0
- package/scripts/uninstall.mjs +184 -0
- package/src/collections/Tickets.ts +7 -0
- package/src/collections/TimeEntries.ts +11 -3
- package/src/components/RichTextEditor/index.tsx +1 -1
- package/src/components/TicketConversation/RewriteDropdown.tsx +1 -1
- package/src/components/TicketConversation/components/AISummaryPanel.tsx +3 -0
- package/src/components/TicketConversation/components/ActionPanels.tsx +13 -8
- package/src/components/TicketConversation/components/ClientHistory.tsx +1 -0
- package/src/components/TicketConversation/components/CodeBlock.tsx +1 -0
- package/src/components/TicketConversation/components/CodeBlockInserter.tsx +1 -1
- package/src/components/TicketConversation/components/QuickActions.tsx +10 -8
- package/src/components/TicketConversation/components/TicketHeader.tsx +2 -0
- package/src/components/TicketConversation/components/TimeTrackingPanel.tsx +17 -9
- package/src/components/TicketConversation/index.tsx +41 -7
- package/src/endpoints/delete-account.ts +191 -47
- package/src/endpoints/export-data.ts +104 -4
- package/src/endpoints/purge-logs.ts +6 -15
- package/src/index.ts +2 -0
- package/src/plugin.ts +51 -1
- package/src/portal/LiveChat.tsx +1 -1
- package/src/portal/auth/profile/page.tsx +3 -2
- package/src/portal/icons.tsx +91 -0
- package/src/styles/BillingView.module.scss +11 -11
- package/src/styles/ChatView.module.scss +7 -7
- package/src/styles/CommandPalette.module.scss +1 -1
- package/src/styles/CrmView.module.scss +9 -9
- package/src/styles/EmailTracking.module.scss +6 -6
- package/src/styles/ImportConversation.module.scss +5 -5
- package/src/styles/Logs.module.scss +5 -5
- package/src/styles/NewTicket.module.scss +2 -2
- package/src/styles/PendingEmails.module.scss +13 -13
- package/src/styles/SupportDashboard.module.scss +24 -8
- package/src/styles/TicketDetail.module.scss +20 -20
- package/src/styles/TicketInbox.module.scss +6 -6
- package/src/styles/TicketingSettings.module.scss +10 -10
- package/src/styles/TimeDashboard.module.scss +6 -6
- package/src/styles/_tokens.scss +3 -1
- package/src/types.ts +20 -0
- package/src/utils/retention.ts +111 -0
- package/src/utils/slugs.ts +15 -0
- package/src/views/BillingView/client.tsx +17 -8
- package/src/views/ChatView/client.tsx +2 -0
- package/src/views/CrmView/client.tsx +2 -0
- package/src/views/EmailTrackingView/client.tsx +18 -7
- package/src/views/ImportConversationView/client.tsx +1 -0
- package/src/views/LogsView/client.tsx +1 -1
- package/src/views/NewTicketView/client.tsx +22 -13
- package/src/views/PendingEmailsView/client.tsx +9 -3
- package/src/views/SupportDashboardView/client.tsx +11 -5
- package/src/views/TicketDetailView/client.tsx +25 -11
- package/src/views/TicketInboxView/client.tsx +47 -7
- package/src/views/TicketingSettingsView/TicketingSettings.module.scss +7 -7
- package/src/views/TicketingSettingsView/client.tsx +201 -144
- package/src/views/TimeDashboardView/client.tsx +10 -7
- package/src/views/shared/ErrorBoundary.tsx +95 -31
- package/src/views/shared/adminTokens.ts +28 -11
- package/src/views/shared/icons.tsx +146 -0
- package/src/views/shared/locales/en.json +15 -1
- package/src/views/shared/locales/fr.json +15 -1
- 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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
266
|
+
color: var(--theme-elevation-650);
|
|
267
267
|
font-size: 12px;
|
|
268
268
|
}
|
|
269
269
|
|
package/src/styles/_tokens.scss
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Design tokens — based on Payload CMS CSS variables
|
|
2
2
|
$text: var(--theme-text);
|
|
3
|
-
|
|
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);
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { RateLimitStore } from './utils/rateLimiter'
|
|
2
|
+
import type { RetentionConfig } from './utils/retention'
|
|
2
3
|
import type { InboundEmailInput } from './utils/webhookSecurity'
|
|
3
4
|
import type { Payload, PayloadRequest } from 'payload'
|
|
4
5
|
|
|
@@ -151,6 +152,25 @@ export interface SupportPluginConfig {
|
|
|
151
152
|
/** Shared rate-limit storage. Defaults to process-local memory. */
|
|
152
153
|
rateLimitStore?: RateLimitStore | 'payload'
|
|
153
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Automatic retention of the two journals the plugin writes.
|
|
157
|
+
*
|
|
158
|
+
* The plugin registers a daily Payload Jobs task that deletes rows older
|
|
159
|
+
* than the configured number of days. Set a value to 0 to keep that journal
|
|
160
|
+
* forever (the manual `DELETE /api/support/purge-logs` endpoint stays
|
|
161
|
+
* available either way).
|
|
162
|
+
*
|
|
163
|
+
* Defaults: auth-logs 180 days, email-logs 365 days, cron `0 30 3 * * *`.
|
|
164
|
+
*
|
|
165
|
+
* `false` registers no task at all. That is the ONLY value that changes the
|
|
166
|
+
* schema footprint: registering a task turns Payload's job queue on, which
|
|
167
|
+
* adds the `payload-jobs` collection and the `payload-jobs-stats` global to
|
|
168
|
+
* an app that had no jobs — two tables you would have to migrate. Pass
|
|
169
|
+
* `false` if you would rather purge from your own cron, calling
|
|
170
|
+
* `DELETE /api/support/purge-logs`.
|
|
171
|
+
*/
|
|
172
|
+
retention?: RetentionConfig | false
|
|
173
|
+
|
|
154
174
|
/** Sequential ticket number formatting. */
|
|
155
175
|
ticketNumber?: { prefix?: string; padding?: number }
|
|
156
176
|
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { Payload, PayloadRequest } from 'payload'
|
|
2
|
+
import type { CollectionSlugs } from './slugs'
|
|
3
|
+
import { dbDelete } from './db'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* How long the two journals are kept, in days.
|
|
7
|
+
*
|
|
8
|
+
* Defaults follow the CNIL guidance the README documents: six months would be
|
|
9
|
+
* the floor for connection logs, a year is the ceiling usually accepted, and
|
|
10
|
+
* these two values sit inside that range while staying long enough to
|
|
11
|
+
* investigate an intrusion. Set a value to 0 to disable the automatic purge of
|
|
12
|
+
* that journal — never to mean "delete everything", which is what `days=0`
|
|
13
|
+
* means on the manual endpoint.
|
|
14
|
+
*/
|
|
15
|
+
export interface RetentionConfig {
|
|
16
|
+
/** Retention of `auth-logs` in days. `0` disables the scheduled purge. */
|
|
17
|
+
authLogsDays?: number
|
|
18
|
+
/** Retention of `email-logs` in days. `0` disables the scheduled purge. */
|
|
19
|
+
emailLogsDays?: number
|
|
20
|
+
/**
|
|
21
|
+
* Cron for the scheduled purge. Payload's cron takes an optional leading
|
|
22
|
+
* seconds field. Default: every day at 03:30.
|
|
23
|
+
*/
|
|
24
|
+
cron?: string
|
|
25
|
+
/** Queue the scheduled job is pushed to. Default: `default`. */
|
|
26
|
+
queue?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const DEFAULT_RETENTION = {
|
|
30
|
+
authLogsDays: 180,
|
|
31
|
+
emailLogsDays: 365,
|
|
32
|
+
cron: '0 30 3 * * *',
|
|
33
|
+
queue: 'default',
|
|
34
|
+
} as const
|
|
35
|
+
|
|
36
|
+
export const PURGE_LOGS_TASK_SLUG = 'support-purge-logs'
|
|
37
|
+
|
|
38
|
+
/** Journals the purge is allowed to touch, keyed by their public name. */
|
|
39
|
+
export function purgeableCollections(slugs: CollectionSlugs): Record<string, string> {
|
|
40
|
+
return {
|
|
41
|
+
'email-logs': slugs.emailLogs,
|
|
42
|
+
'auth-logs': slugs.authLogs,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Delete rows of `slug` created more than `days` days ago.
|
|
48
|
+
*
|
|
49
|
+
* `days <= 0` means "everything" and is only reachable from the manual
|
|
50
|
+
* admin-only endpoint. The scheduled task refuses it: a misconfigured cron
|
|
51
|
+
* that wipes the whole security journal every night is a far worse outcome
|
|
52
|
+
* than a journal that grows.
|
|
53
|
+
*/
|
|
54
|
+
export async function purgeOlderThan(
|
|
55
|
+
payload: Payload,
|
|
56
|
+
slug: string,
|
|
57
|
+
days: number,
|
|
58
|
+
req?: PayloadRequest,
|
|
59
|
+
): Promise<number> {
|
|
60
|
+
const cutoff = days > 0 ? new Date(Date.now() - days * 86400000).toISOString() : null
|
|
61
|
+
|
|
62
|
+
const result = await dbDelete(payload, slug, {
|
|
63
|
+
where: cutoff
|
|
64
|
+
? { createdAt: { less_than: cutoff } }
|
|
65
|
+
: { id: { exists: true } },
|
|
66
|
+
overrideAccess: true,
|
|
67
|
+
...(req ? { req } : {}),
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
return Array.isArray(result?.docs) ? result.docs.length : 0
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface ScheduledPurgeResult {
|
|
74
|
+
purged: Record<string, number>
|
|
75
|
+
skipped: string[]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The scheduled half of the purge. Never accepts `days <= 0`, and silently
|
|
80
|
+
* skips a journal whose collection is not registered (its feature flag is off).
|
|
81
|
+
*/
|
|
82
|
+
export async function runScheduledPurge(
|
|
83
|
+
payload: Payload,
|
|
84
|
+
slugs: CollectionSlugs,
|
|
85
|
+
retention: RetentionConfig | false | undefined,
|
|
86
|
+
req?: PayloadRequest,
|
|
87
|
+
): Promise<ScheduledPurgeResult> {
|
|
88
|
+
if (retention === false) return { purged: {}, skipped: ['auth-logs', 'email-logs'] }
|
|
89
|
+
|
|
90
|
+
const plan: Array<[string, string, number]> = [
|
|
91
|
+
['auth-logs', slugs.authLogs, retention?.authLogsDays ?? DEFAULT_RETENTION.authLogsDays],
|
|
92
|
+
['email-logs', slugs.emailLogs, retention?.emailLogsDays ?? DEFAULT_RETENTION.emailLogsDays],
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
const purged: Record<string, number> = {}
|
|
96
|
+
const skipped: string[] = []
|
|
97
|
+
|
|
98
|
+
for (const [name, slug, days] of plan) {
|
|
99
|
+
if (!Number.isFinite(days) || days <= 0) {
|
|
100
|
+
skipped.push(name)
|
|
101
|
+
continue
|
|
102
|
+
}
|
|
103
|
+
if (!(payload.collections as Record<string, unknown> | undefined)?.[slug]) {
|
|
104
|
+
skipped.push(name)
|
|
105
|
+
continue
|
|
106
|
+
}
|
|
107
|
+
purged[name] = await purgeOlderThan(payload, slug, days, req)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return { purged, skipped }
|
|
111
|
+
}
|
package/src/utils/slugs.ts
CHANGED
|
@@ -58,6 +58,21 @@ export const DEFAULT_SLUGS: CollectionSlugs = {
|
|
|
58
58
|
media: 'media',
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Collections the plugin registers under a FIXED slug.
|
|
63
|
+
*
|
|
64
|
+
* Unlike everything in `CollectionSlugs`, these three hardcode their `slug`
|
|
65
|
+
* (ClientSummaries.ts, TicketCollaborators.ts, TicketFeedback.ts), so any code
|
|
66
|
+
* that reads or deletes their rows must use these literals rather than a
|
|
67
|
+
* configurable slug — otherwise an app that overrides `collectionSlugs` would
|
|
68
|
+
* address a collection that does not exist.
|
|
69
|
+
*/
|
|
70
|
+
export const FIXED_SLUGS = {
|
|
71
|
+
clientSummaries: 'client-summaries',
|
|
72
|
+
ticketCollaborators: 'ticket-collaborators',
|
|
73
|
+
ticketFeedback: 'ticket-feedback',
|
|
74
|
+
} as const
|
|
75
|
+
|
|
61
76
|
/**
|
|
62
77
|
* Resolve collection slugs merging user overrides with defaults.
|
|
63
78
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import React, { useState, useCallback } from 'react'
|
|
3
|
+
import React, { useState, useCallback, useId } from 'react'
|
|
4
4
|
import { useTranslation } from '../../components/TicketConversation/hooks/useTranslation'
|
|
5
5
|
import { DATE_LOCALE } from '../shared/dateLocale'
|
|
6
6
|
import styles from '../../styles/BillingView.module.scss'
|
|
@@ -86,6 +86,12 @@ function getQuarterRange(offset: number): { from: string; to: string } {
|
|
|
86
86
|
|
|
87
87
|
export const BillingClient: React.FC = () => {
|
|
88
88
|
const { t } = useTranslation()
|
|
89
|
+
// The `<label>`s in the filter row already carry the translated text; they
|
|
90
|
+
// just were not associated with their control.
|
|
91
|
+
const fromId = useId()
|
|
92
|
+
const toId = useId()
|
|
93
|
+
const projectFilterId = useId()
|
|
94
|
+
const rateId = useId()
|
|
89
95
|
const [from, setFrom] = useState(() => getMonthRange(0).from)
|
|
90
96
|
const [to, setTo] = useState(() => getMonthRange(0).to)
|
|
91
97
|
const [projectId, setProjectId] = useState('')
|
|
@@ -287,16 +293,16 @@ export const BillingClient: React.FC = () => {
|
|
|
287
293
|
|
|
288
294
|
<div className={styles.filterRow}>
|
|
289
295
|
<div className={styles.fieldGroup}>
|
|
290
|
-
<label className={styles.label}>{t('billing.filters.from')}</label>
|
|
291
|
-
<input type="date" value={from} onChange={(e) => setFrom(e.target.value)} className={styles.input} />
|
|
296
|
+
<label className={styles.label} htmlFor={fromId}>{t('billing.filters.from')}</label>
|
|
297
|
+
<input id={fromId} type="date" value={from} onChange={(e) => setFrom(e.target.value)} className={styles.input} />
|
|
292
298
|
</div>
|
|
293
299
|
<div className={styles.fieldGroup}>
|
|
294
|
-
<label className={styles.label}>{t('billing.filters.to')}</label>
|
|
295
|
-
<input type="date" value={to} onChange={(e) => setTo(e.target.value)} className={styles.input} />
|
|
300
|
+
<label className={styles.label} htmlFor={toId}>{t('billing.filters.to')}</label>
|
|
301
|
+
<input id={toId} type="date" value={to} onChange={(e) => setTo(e.target.value)} className={styles.input} />
|
|
296
302
|
</div>
|
|
297
303
|
<div className={styles.fieldGroup}>
|
|
298
|
-
<label className={styles.label}>{t('billing.filters.project')}</label>
|
|
299
|
-
<select value={projectId} onChange={(e) => setProjectId(e.target.value)} className={styles.select}>
|
|
304
|
+
<label className={styles.label} htmlFor={projectFilterId}>{t('billing.filters.project')}</label>
|
|
305
|
+
<select id={projectFilterId} value={projectId} onChange={(e) => setProjectId(e.target.value)} className={styles.select}>
|
|
300
306
|
<option value="">{t('ticket.allProjects')}</option>
|
|
301
307
|
{projects.map((p) => (
|
|
302
308
|
<option key={p.id} value={p.id}>{p.name}</option>
|
|
@@ -304,9 +310,10 @@ export const BillingClient: React.FC = () => {
|
|
|
304
310
|
</select>
|
|
305
311
|
</div>
|
|
306
312
|
<div className={styles.fieldGroup}>
|
|
307
|
-
<label className={styles.label}>{t('billing.filters.hourlyRate')}</label>
|
|
313
|
+
<label className={styles.label} htmlFor={rateId}>{t('billing.filters.hourlyRate')}</label>
|
|
308
314
|
<div className={styles.rateRow}>
|
|
309
315
|
<input
|
|
316
|
+
id={rateId}
|
|
310
317
|
type="number"
|
|
311
318
|
value={rate}
|
|
312
319
|
onChange={(e) => setRate(Number(e.target.value))}
|
|
@@ -370,6 +377,7 @@ export const BillingClient: React.FC = () => {
|
|
|
370
377
|
<th className={styles.thCheckbox}>
|
|
371
378
|
<input
|
|
372
379
|
type="checkbox"
|
|
380
|
+
aria-label={t('billing.tableCheckboxTitle')}
|
|
373
381
|
checked={group.tickets.every((t) => billedTickets.has(t.id))}
|
|
374
382
|
onChange={() => {
|
|
375
383
|
const ids = group.tickets.map((t) => t.id)
|
|
@@ -405,6 +413,7 @@ export const BillingClient: React.FC = () => {
|
|
|
405
413
|
<td className={styles.td} rowSpan={rowSpan} style={{ verticalAlign: 'middle' }}>
|
|
406
414
|
<input
|
|
407
415
|
type="checkbox"
|
|
416
|
+
aria-label={isBilled ? t('billing.markUnbilledTitle') : t('billing.markBilledTitle')}
|
|
408
417
|
checked={isBilled}
|
|
409
418
|
onChange={() => toggleBilled(ticket.id)}
|
|
410
419
|
className={styles.checkbox}
|
|
@@ -343,6 +343,7 @@ export const ChatViewClient: React.FC = () => {
|
|
|
343
343
|
<form onSubmit={sendMessage} className={styles.composer}>
|
|
344
344
|
{cannedResponses.length > 0 && (
|
|
345
345
|
<select
|
|
346
|
+
aria-label={t('chat.quickReply')}
|
|
346
347
|
onChange={(e) => {
|
|
347
348
|
const cr = cannedResponses.find((c) => String(c.id) === e.target.value)
|
|
348
349
|
if (cr) setInput(cr.body)
|
|
@@ -359,6 +360,7 @@ export const ChatViewClient: React.FC = () => {
|
|
|
359
360
|
<div className={styles.composerRow}>
|
|
360
361
|
<input
|
|
361
362
|
type="text"
|
|
363
|
+
aria-label={t('chat.inputPlaceholder')}
|
|
362
364
|
value={input}
|
|
363
365
|
onChange={(e) => setInput(e.target.value)}
|
|
364
366
|
placeholder={t('chat.inputPlaceholder')}
|
|
@@ -275,6 +275,7 @@ export const CrmClient: React.FC = () => {
|
|
|
275
275
|
<div className={styles.sidebarSearch}>
|
|
276
276
|
<input
|
|
277
277
|
type="text"
|
|
278
|
+
aria-label={t('crm.searchLabel')}
|
|
278
279
|
placeholder={t('crm.searchPlaceholder')}
|
|
279
280
|
value={search}
|
|
280
281
|
onChange={(e) => setSearch(e.target.value)}
|
|
@@ -379,6 +380,7 @@ export const CrmClient: React.FC = () => {
|
|
|
379
380
|
</p>
|
|
380
381
|
<input
|
|
381
382
|
type="text"
|
|
383
|
+
aria-label={t('crm.mergeTitle')}
|
|
382
384
|
value={mergeSearch}
|
|
383
385
|
onChange={(e) => setMergeSearch(e.target.value)}
|
|
384
386
|
placeholder={t('crm.mergeSearchPlaceholder')}
|