@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
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
function createIcon(displayName, nodes) {
|
|
5
|
+
const Icon = ({ size = 24, strokeWidth = 2, ...rest }) => /* @__PURE__ */ jsx(
|
|
6
|
+
"svg",
|
|
7
|
+
{
|
|
8
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
9
|
+
width: size,
|
|
10
|
+
height: size,
|
|
11
|
+
viewBox: "0 0 24 24",
|
|
12
|
+
fill: "none",
|
|
13
|
+
stroke: "currentColor",
|
|
14
|
+
strokeWidth,
|
|
15
|
+
strokeLinecap: "round",
|
|
16
|
+
strokeLinejoin: "round",
|
|
17
|
+
"aria-hidden": "true",
|
|
18
|
+
...rest,
|
|
19
|
+
children: nodes.map(([tag, attrs], index) => React.createElement(tag, { key: index, ...attrs }))
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
Icon.displayName = displayName;
|
|
23
|
+
return Icon;
|
|
24
|
+
}
|
|
25
|
+
const Bot = createIcon("Bot", [
|
|
26
|
+
["path", { d: "M12 8V4H8" }],
|
|
27
|
+
["rect", { width: "16", height: "12", x: "4", y: "8", rx: "2" }],
|
|
28
|
+
["path", { d: "M2 14h2" }],
|
|
29
|
+
["path", { d: "M20 14h2" }],
|
|
30
|
+
["path", { d: "M15 13v2" }],
|
|
31
|
+
["path", { d: "M9 13v2" }]
|
|
32
|
+
]);
|
|
33
|
+
const ChevronDown = createIcon("ChevronDown", [
|
|
34
|
+
["path", { d: "m6 9 6 6 6-6" }]
|
|
35
|
+
]);
|
|
36
|
+
const ChevronUp = createIcon("ChevronUp", [
|
|
37
|
+
["path", { d: "m18 15-6-6-6 6" }]
|
|
38
|
+
]);
|
|
39
|
+
const Clock = createIcon("Clock", [
|
|
40
|
+
["circle", { cx: "12", cy: "12", r: "10" }],
|
|
41
|
+
["path", { d: "M12 6v6l4 2" }]
|
|
42
|
+
]);
|
|
43
|
+
const FileSignature = createIcon("FileSignature", [
|
|
44
|
+
["path", { d: "M14.364 13.634a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506l4.013-4.009a1 1 0 0 0-3.004-3.004z" }],
|
|
45
|
+
["path", { d: "M14.487 7.858A1 1 0 0 1 14 7V2" }],
|
|
46
|
+
["path", { d: "M20 19.645V20a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l2.516 2.516" }],
|
|
47
|
+
["path", { d: "M8 18h1" }]
|
|
48
|
+
]);
|
|
49
|
+
const Globe = createIcon("Globe", [
|
|
50
|
+
["circle", { cx: "12", cy: "12", r: "10" }],
|
|
51
|
+
["path", { d: "M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" }],
|
|
52
|
+
["path", { d: "M2 12h20" }]
|
|
53
|
+
]);
|
|
54
|
+
const Inbox = createIcon("Inbox", [
|
|
55
|
+
["polyline", { points: "22 12 16 12 14 15 10 15 8 12 2 12" }],
|
|
56
|
+
["path", { d: "M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" }]
|
|
57
|
+
]);
|
|
58
|
+
const Link2 = createIcon("Link2", [
|
|
59
|
+
["path", { d: "M9 17H7A5 5 0 0 1 7 7h2" }],
|
|
60
|
+
["path", { d: "M15 7h2a5 5 0 1 1 0 10h-2" }],
|
|
61
|
+
["line", { x1: "8", x2: "16", y1: "12", y2: "12" }]
|
|
62
|
+
]);
|
|
63
|
+
const Mail = createIcon("Mail", [
|
|
64
|
+
["path", { d: "m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7" }],
|
|
65
|
+
["rect", { x: "2", y: "4", width: "20", height: "16", rx: "2" }]
|
|
66
|
+
]);
|
|
67
|
+
const Paperclip = createIcon("Paperclip", [
|
|
68
|
+
["path", { d: "m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551" }]
|
|
69
|
+
]);
|
|
70
|
+
const Plus = createIcon("Plus", [
|
|
71
|
+
["path", { d: "M5 12h14" }],
|
|
72
|
+
["path", { d: "M12 5v14" }]
|
|
73
|
+
]);
|
|
74
|
+
const Search = createIcon("Search", [
|
|
75
|
+
["path", { d: "m21 21-4.34-4.34" }],
|
|
76
|
+
["circle", { cx: "11", cy: "11", r: "8" }]
|
|
77
|
+
]);
|
|
78
|
+
const Settings = createIcon("Settings", [
|
|
79
|
+
["path", { d: "M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" }],
|
|
80
|
+
["circle", { cx: "12", cy: "12", r: "3" }]
|
|
81
|
+
]);
|
|
82
|
+
const Timer = createIcon("Timer", [
|
|
83
|
+
["line", { x1: "10", x2: "14", y1: "2", y2: "2" }],
|
|
84
|
+
["line", { x1: "12", x2: "15", y1: "14", y2: "11" }],
|
|
85
|
+
["circle", { cx: "12", cy: "14", r: "8" }]
|
|
86
|
+
]);
|
|
87
|
+
const X = createIcon("X", [
|
|
88
|
+
["path", { d: "M18 6 6 18" }],
|
|
89
|
+
["path", { d: "m6 6 12 12" }]
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
export { Bot, ChevronDown, ChevronUp, Clock, FileSignature, Globe, Inbox, Link2, Mail, Paperclip, Plus, Search, Settings, Timer, X };
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
+
"errorBoundary": {
|
|
3
|
+
"title": "Something went wrong",
|
|
4
|
+
"description": "This section could not be displayed. The technical details were logged to the browser console.",
|
|
5
|
+
"retry": "Retry"
|
|
6
|
+
},
|
|
2
7
|
"common": {
|
|
3
8
|
"loading": "Loading...",
|
|
4
9
|
"cancel": "Cancel",
|
|
@@ -144,6 +149,7 @@
|
|
|
144
149
|
"title": "CRM Clients",
|
|
145
150
|
"subtitle": "Client management and history",
|
|
146
151
|
"searchPlaceholder": "Search a client...",
|
|
152
|
+
"searchLabel": "Search a client",
|
|
147
153
|
"noClientFound": "No client found",
|
|
148
154
|
"selectClient": "Select a client to see their details",
|
|
149
155
|
"editButton": "Edit",
|
|
@@ -316,6 +322,8 @@
|
|
|
316
322
|
"resolved": "Resolved",
|
|
317
323
|
"slaBreach": "SLA breached"
|
|
318
324
|
},
|
|
325
|
+
"searchLabel": "Search a ticket",
|
|
326
|
+
"sortLabel": "Sort the tickets",
|
|
319
327
|
"sort": {
|
|
320
328
|
"newest": "Most recent",
|
|
321
329
|
"oldest": "Oldest first",
|
|
@@ -324,6 +332,7 @@
|
|
|
324
332
|
},
|
|
325
333
|
"selected": "{{count}} selected",
|
|
326
334
|
"selectedPlural": "{{count}} selected",
|
|
335
|
+
"tabsLabel": "Inbox filters",
|
|
327
336
|
"closeAction": "Close",
|
|
328
337
|
"reopenAction": "Reopen",
|
|
329
338
|
"moreActions": "More actions...",
|
|
@@ -344,6 +353,7 @@
|
|
|
344
353
|
"emailTracking": {
|
|
345
354
|
"title": "Email tracking",
|
|
346
355
|
"searchPlaceholder": "Search by recipient or subject...",
|
|
356
|
+
"searchLabel": "Search an email",
|
|
347
357
|
"noLogs": "No logs found for this period",
|
|
348
358
|
"tabs": {
|
|
349
359
|
"all": "All",
|
|
@@ -366,7 +376,8 @@
|
|
|
366
376
|
"action": "Action",
|
|
367
377
|
"time": "Time"
|
|
368
378
|
},
|
|
369
|
-
"errorLabel": "Error"
|
|
379
|
+
"errorLabel": "Error",
|
|
380
|
+
"toggleError": "Show the error detail"
|
|
370
381
|
},
|
|
371
382
|
"logs": {
|
|
372
383
|
"title": "Email Logs",
|
|
@@ -530,8 +541,11 @@
|
|
|
530
541
|
"messageDeletedToast": "Message deleted",
|
|
531
542
|
"split": {
|
|
532
543
|
"subjectPlaceholder": "New ticket subject...",
|
|
544
|
+
"subjectLabel": "Subject of the new ticket",
|
|
533
545
|
"createBtn": "Create ticket"
|
|
534
546
|
},
|
|
547
|
+
"sendAs": "Send as",
|
|
548
|
+
"manualMinutes": "Minutes to add",
|
|
535
549
|
"editMessage": "Edit",
|
|
536
550
|
"editMessageLabel": "Edit message",
|
|
537
551
|
"uploadItem": "Attachment {{name}}",
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
+
"errorBoundary": {
|
|
3
|
+
"title": "Une erreur est survenue",
|
|
4
|
+
"description": "Cette section n'a pas pu s'afficher. Le detail technique est consigne dans la console du navigateur.",
|
|
5
|
+
"retry": "Reessayer"
|
|
6
|
+
},
|
|
2
7
|
"common": {
|
|
3
8
|
"loading": "Chargement...",
|
|
4
9
|
"cancel": "Annuler",
|
|
@@ -144,6 +149,7 @@
|
|
|
144
149
|
"title": "CRM Clients",
|
|
145
150
|
"subtitle": "Gestion des clients et historique",
|
|
146
151
|
"searchPlaceholder": "Rechercher un client...",
|
|
152
|
+
"searchLabel": "Rechercher un client",
|
|
147
153
|
"noClientFound": "Aucun client trouvé",
|
|
148
154
|
"selectClient": "Sélectionnez un client pour voir ses détails",
|
|
149
155
|
"editButton": "Modifier",
|
|
@@ -316,6 +322,8 @@
|
|
|
316
322
|
"resolved": "Résolus",
|
|
317
323
|
"slaBreach": "SLA dépassé"
|
|
318
324
|
},
|
|
325
|
+
"searchLabel": "Rechercher un ticket",
|
|
326
|
+
"sortLabel": "Trier les tickets",
|
|
319
327
|
"sort": {
|
|
320
328
|
"newest": "Plus récent",
|
|
321
329
|
"oldest": "Plus ancien",
|
|
@@ -324,6 +332,7 @@
|
|
|
324
332
|
},
|
|
325
333
|
"selected": "{{count}} sélectionné",
|
|
326
334
|
"selectedPlural": "{{count}} sélectionnés",
|
|
335
|
+
"tabsLabel": "Filtres de la boite de reception",
|
|
327
336
|
"closeAction": "Fermer",
|
|
328
337
|
"reopenAction": "Rouvrir",
|
|
329
338
|
"moreActions": "Plus d'actions...",
|
|
@@ -344,6 +353,7 @@
|
|
|
344
353
|
"emailTracking": {
|
|
345
354
|
"title": "Suivi des emails",
|
|
346
355
|
"searchPlaceholder": "Rechercher par destinataire ou sujet...",
|
|
356
|
+
"searchLabel": "Rechercher un email",
|
|
347
357
|
"noLogs": "Aucun log trouvé pour cette période",
|
|
348
358
|
"tabs": {
|
|
349
359
|
"all": "Tous",
|
|
@@ -366,7 +376,8 @@
|
|
|
366
376
|
"action": "Action",
|
|
367
377
|
"time": "Temps"
|
|
368
378
|
},
|
|
369
|
-
"errorLabel": "Erreur"
|
|
379
|
+
"errorLabel": "Erreur",
|
|
380
|
+
"toggleError": "Afficher le detail de l'erreur"
|
|
370
381
|
},
|
|
371
382
|
"logs": {
|
|
372
383
|
"title": "Logs Email",
|
|
@@ -530,8 +541,11 @@
|
|
|
530
541
|
"messageDeletedToast": "Message supprimé",
|
|
531
542
|
"split": {
|
|
532
543
|
"subjectPlaceholder": "Sujet du nouveau ticket...",
|
|
544
|
+
"subjectLabel": "Sujet du nouveau ticket",
|
|
533
545
|
"createBtn": "Créer le ticket"
|
|
534
546
|
},
|
|
547
|
+
"sendAs": "Envoyer en tant que",
|
|
548
|
+
"manualMinutes": "Minutes a ajouter",
|
|
535
549
|
"editMessage": "Éditer",
|
|
536
550
|
"editMessageLabel": "Éditer le message",
|
|
537
551
|
"uploadItem": "PJ {{name}}",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@consilioweb/payload-support",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "Payload CMS plugin — professional support & ticketing system with AI, SLA, time tracking, live chat, and more",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -40,12 +40,17 @@
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
},
|
|
43
|
+
"bin": {
|
|
44
|
+
"support-uninstall": "./scripts/uninstall.mjs"
|
|
45
|
+
},
|
|
43
46
|
"files": [
|
|
44
47
|
"dist",
|
|
45
48
|
"src",
|
|
46
49
|
"!src/__tests__",
|
|
47
50
|
"!src/**/*.test.ts",
|
|
48
51
|
"!src/**/*.test.tsx",
|
|
52
|
+
"scripts/uninstall.mjs",
|
|
53
|
+
"scripts/uninstall-data.mjs",
|
|
49
54
|
"README.md",
|
|
50
55
|
"LICENSE"
|
|
51
56
|
],
|
|
@@ -86,7 +91,6 @@
|
|
|
86
91
|
"license": "MIT",
|
|
87
92
|
"peerDependencies": {
|
|
88
93
|
"@payloadcms/next": "^3.79.1",
|
|
89
|
-
"lucide-react": ">=0.300.0",
|
|
90
94
|
"next": "^15.2.9 || ^16.0.0",
|
|
91
95
|
"payload": "^3.79.1",
|
|
92
96
|
"react": "^19.0.0",
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Data half of `support-uninstall`. It is NOT meant to be run directly: it is
|
|
5
|
+
* spawned by `scripts/uninstall.mjs` through `payload run`, which is what puts
|
|
6
|
+
* the TypeScript loader and the environment in place so the host's
|
|
7
|
+
* `payload.config.ts` can be imported.
|
|
8
|
+
*
|
|
9
|
+
* Everything here goes through the Payload local API rather than a `sqlite3`
|
|
10
|
+
* binary. The plugin accepts custom collection slugs and runs on SQLite,
|
|
11
|
+
* PostgreSQL and MongoDB; raw SQL against hardcoded table names would only
|
|
12
|
+
* work on one of the three, and silently do nothing on the other two.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { getPayload } from 'payload'
|
|
16
|
+
import { findConfig } from 'payload/node'
|
|
17
|
+
import { pathToFileURL } from 'node:url'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Deletion order matters: a child row whose `ticket` / `client` relationship is
|
|
21
|
+
* `required` blocks the delete of its parent (NOT NULL on the foreign key).
|
|
22
|
+
*
|
|
23
|
+
* These are the DEFAULT slugs. They are duplicated from `src/utils/slugs.ts`
|
|
24
|
+
* on purpose — importing `dist/index.js` here would pull pdfkit, web-push and
|
|
25
|
+
* sanitize-html into a script that only deletes rows. `src/__tests__/
|
|
26
|
+
* uninstallScript.test.ts` fails if the two lists ever drift apart.
|
|
27
|
+
*/
|
|
28
|
+
const ORDERED_SLUGS = [
|
|
29
|
+
// 1. Rows pointing at a ticket.
|
|
30
|
+
'ticket-collaborators',
|
|
31
|
+
'ticket-feedback',
|
|
32
|
+
'satisfaction-surveys',
|
|
33
|
+
'ticket-activity-log',
|
|
34
|
+
'ticket-messages',
|
|
35
|
+
'time-entries',
|
|
36
|
+
// 2. The tickets themselves.
|
|
37
|
+
'tickets',
|
|
38
|
+
// 3. Rows pointing at a client, or at nothing.
|
|
39
|
+
'chat-messages',
|
|
40
|
+
'pending-emails',
|
|
41
|
+
'client-summaries',
|
|
42
|
+
'notification-queue',
|
|
43
|
+
'push-subscriptions',
|
|
44
|
+
'canned-responses',
|
|
45
|
+
'knowledge-base',
|
|
46
|
+
'macros',
|
|
47
|
+
'automation-rules',
|
|
48
|
+
'ticket-statuses',
|
|
49
|
+
'webhook-endpoints',
|
|
50
|
+
'sla-policies',
|
|
51
|
+
'support-teams',
|
|
52
|
+
'auth-logs',
|
|
53
|
+
'email-logs',
|
|
54
|
+
'support-counters',
|
|
55
|
+
'support-rate-limits',
|
|
56
|
+
// 4. The portal accounts.
|
|
57
|
+
'support-clients',
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
/** `payload-preferences` keys the plugin writes. */
|
|
61
|
+
const PREFERENCE_KEYS = {
|
|
62
|
+
exact: ['support-settings', 'support-round-robin'],
|
|
63
|
+
prefix: ['support-user-prefs'],
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function parseArgs(argv) {
|
|
67
|
+
const confirm = argv.includes('--confirm')
|
|
68
|
+
const keepData = argv.includes('--keep-data')
|
|
69
|
+
const extra = argv.find((a) => a.startsWith('--extra-slugs='))
|
|
70
|
+
return {
|
|
71
|
+
confirm,
|
|
72
|
+
keepData,
|
|
73
|
+
extraSlugs: extra ? extra.slice('--extra-slugs='.length).split(',').map((s) => s.trim()).filter(Boolean) : [],
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function main() {
|
|
78
|
+
const { confirm, keepData, extraSlugs } = parseArgs(process.argv.slice(2))
|
|
79
|
+
|
|
80
|
+
const configPath = findConfig()
|
|
81
|
+
const imported = await import(pathToFileURL(configPath).toString())
|
|
82
|
+
const config = await (imported.default ?? imported)
|
|
83
|
+
const payload = await getPayload({ config })
|
|
84
|
+
|
|
85
|
+
const candidates = [...ORDERED_SLUGS, ...extraSlugs]
|
|
86
|
+
const present = candidates.filter((slug) => Boolean(payload.collections?.[slug]))
|
|
87
|
+
const absent = candidates.filter((slug) => !payload.collections?.[slug])
|
|
88
|
+
|
|
89
|
+
console.log('')
|
|
90
|
+
console.log(` Config: ${configPath}`)
|
|
91
|
+
console.log(` Mode: ${confirm ? 'DELETE' : 'DRY RUN (nothing is written)'}`)
|
|
92
|
+
console.log('')
|
|
93
|
+
|
|
94
|
+
if (!keepData) {
|
|
95
|
+
console.log(' Collections found in this app:')
|
|
96
|
+
for (const slug of present) {
|
|
97
|
+
const { totalDocs } = await payload.count({ collection: slug, overrideAccess: true })
|
|
98
|
+
console.log(` ${String(totalDocs).padStart(7)} ${slug}`)
|
|
99
|
+
}
|
|
100
|
+
if (present.length === 0) {
|
|
101
|
+
console.log(' (none — nothing of this plugin is registered in this config)')
|
|
102
|
+
}
|
|
103
|
+
if (absent.length > 0) {
|
|
104
|
+
console.log('')
|
|
105
|
+
console.log(` Not registered here, skipped: ${absent.join(', ')}`)
|
|
106
|
+
console.log(' If you renamed any of them through `collectionSlugs`, re-run with')
|
|
107
|
+
console.log(' --extra-slugs=your-slug,another-slug — this script cannot guess them.')
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
console.log(' --keep-data: no collection row will be deleted.')
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log('')
|
|
114
|
+
console.log(' payload-preferences keys:')
|
|
115
|
+
for (const key of PREFERENCE_KEYS.exact) console.log(` ${key}`)
|
|
116
|
+
for (const key of PREFERENCE_KEYS.prefix) console.log(` ${key}* (one row per agent)`)
|
|
117
|
+
|
|
118
|
+
if (!confirm) {
|
|
119
|
+
console.log('')
|
|
120
|
+
console.log(' Nothing was deleted. Re-run with --confirm to apply.')
|
|
121
|
+
console.log('')
|
|
122
|
+
process.exit(0)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!keepData) {
|
|
126
|
+
console.log('')
|
|
127
|
+
for (const slug of present) {
|
|
128
|
+
const result = await payload.delete({
|
|
129
|
+
collection: slug,
|
|
130
|
+
where: { id: { exists: true } },
|
|
131
|
+
overrideAccess: true,
|
|
132
|
+
})
|
|
133
|
+
const deleted = Array.isArray(result?.docs) ? result.docs.length : 0
|
|
134
|
+
const failed = Array.isArray(result?.errors) ? result.errors.length : 0
|
|
135
|
+
console.log(` deleted ${String(deleted).padStart(7)} ${slug}${failed ? ` (${failed} failed)` : ''}`)
|
|
136
|
+
if (failed) {
|
|
137
|
+
console.log(` ${JSON.stringify(result.errors.slice(0, 3))}`)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
console.log('')
|
|
143
|
+
for (const key of PREFERENCE_KEYS.exact) {
|
|
144
|
+
const result = await payload.delete({
|
|
145
|
+
collection: 'payload-preferences',
|
|
146
|
+
where: { key: { equals: key } },
|
|
147
|
+
overrideAccess: true,
|
|
148
|
+
})
|
|
149
|
+
console.log(` deleted ${String(result?.docs?.length ?? 0).padStart(7)} payload-preferences/${key}`)
|
|
150
|
+
}
|
|
151
|
+
for (const key of PREFERENCE_KEYS.prefix) {
|
|
152
|
+
const result = await payload.delete({
|
|
153
|
+
collection: 'payload-preferences',
|
|
154
|
+
where: { key: { like: key } },
|
|
155
|
+
overrideAccess: true,
|
|
156
|
+
})
|
|
157
|
+
console.log(` deleted ${String(result?.docs?.length ?? 0).padStart(7)} payload-preferences/${key}*`)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.log('')
|
|
161
|
+
console.log(' Left untouched, on purpose:')
|
|
162
|
+
console.log(' - your `media` collection. Attachments uploaded through the support')
|
|
163
|
+
console.log(' desk are ordinary uploads once their messages are gone, and this')
|
|
164
|
+
console.log(' script has no way to tell them apart from the rest of your media.')
|
|
165
|
+
console.log(' Review them yourself before deleting anything there.')
|
|
166
|
+
console.log(' - the tables. Rows are gone; run `payload migrate:create` and apply')
|
|
167
|
+
console.log(' the migration to drop the now-unused tables.')
|
|
168
|
+
console.log('')
|
|
169
|
+
|
|
170
|
+
process.exit(0)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
main().catch((err) => {
|
|
174
|
+
console.error('')
|
|
175
|
+
console.error(' Uninstall failed:', err)
|
|
176
|
+
console.error('')
|
|
177
|
+
process.exit(1)
|
|
178
|
+
})
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `support-uninstall` — removes this plugin from a host application.
|
|
5
|
+
*
|
|
6
|
+
* Contract, deliberately identical to the other ConsilioWEB plugins:
|
|
7
|
+
* - DRY RUN by default. Nothing is written without `--confirm`.
|
|
8
|
+
* - a detection gate: the destructive step refuses to run unless this project
|
|
9
|
+
* really uses the plugin (a source file references it, or it is a declared
|
|
10
|
+
* dependency). `--force-db` overrides it.
|
|
11
|
+
* - `--keep-data` cleans the code and the preferences but keeps every row.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* npx support-uninstall list what would be removed
|
|
15
|
+
* npx support-uninstall --confirm do it
|
|
16
|
+
* npx support-uninstall --confirm --keep-data code only, rows kept
|
|
17
|
+
* npx support-uninstall --confirm --force-db delete rows even if undetected
|
|
18
|
+
* npx support-uninstall --extra-slugs=a,b add renamed collections
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import fs from 'node:fs'
|
|
22
|
+
import path from 'node:path'
|
|
23
|
+
import { execSync, spawnSync } from 'node:child_process'
|
|
24
|
+
import { fileURLToPath } from 'node:url'
|
|
25
|
+
|
|
26
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url))
|
|
27
|
+
|
|
28
|
+
function readOwnPackageName() {
|
|
29
|
+
try {
|
|
30
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(HERE, '..', 'package.json'), 'utf-8'))
|
|
31
|
+
if (typeof pkg.name === 'string' && pkg.name) return pkg.name
|
|
32
|
+
} catch {
|
|
33
|
+
// fall through
|
|
34
|
+
}
|
|
35
|
+
return '@consilioweb/payload-support'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const PACKAGE_NAME = readOwnPackageName()
|
|
39
|
+
|
|
40
|
+
function detectPackageManager(dir) {
|
|
41
|
+
if (fs.existsSync(path.join(dir, 'pnpm-lock.yaml'))) return 'pnpm'
|
|
42
|
+
if (fs.existsSync(path.join(dir, 'yarn.lock'))) return 'yarn'
|
|
43
|
+
if (fs.existsSync(path.join(dir, 'bun.lockb')) || fs.existsSync(path.join(dir, 'bun.lock'))) return 'bun'
|
|
44
|
+
return 'npm'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function findSourceFiles(dir, acc = []) {
|
|
48
|
+
if (!fs.existsSync(dir)) return acc
|
|
49
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
50
|
+
const full = path.join(dir, entry.name)
|
|
51
|
+
if (entry.isDirectory()) {
|
|
52
|
+
if (entry.name === 'node_modules' || entry.name === '.next' || entry.name.startsWith('.')) continue
|
|
53
|
+
findSourceFiles(full, acc)
|
|
54
|
+
} else if (/\.(?:ts|tsx|js|jsx|mjs|cjs)$/.test(entry.name)) {
|
|
55
|
+
acc.push(full)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return acc
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isDeclaredDependency(projectDir) {
|
|
62
|
+
try {
|
|
63
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(projectDir, 'package.json'), 'utf-8'))
|
|
64
|
+
return Boolean(
|
|
65
|
+
pkg.dependencies?.[PACKAGE_NAME]
|
|
66
|
+
|| pkg.devDependencies?.[PACKAGE_NAME]
|
|
67
|
+
|| pkg.peerDependencies?.[PACKAGE_NAME]
|
|
68
|
+
|| pkg.optionalDependencies?.[PACKAGE_NAME],
|
|
69
|
+
)
|
|
70
|
+
} catch {
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function run(cmd, cwd) {
|
|
76
|
+
console.log(` $ ${cmd}`)
|
|
77
|
+
try {
|
|
78
|
+
execSync(cmd, { cwd, stdio: 'inherit' })
|
|
79
|
+
return true
|
|
80
|
+
} catch {
|
|
81
|
+
return false
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function main() {
|
|
86
|
+
const projectDir = process.env.INIT_CWD || process.cwd()
|
|
87
|
+
const argv = process.argv.slice(2)
|
|
88
|
+
const confirm = argv.includes('--confirm')
|
|
89
|
+
const keepData = argv.includes('--keep-data')
|
|
90
|
+
const forceDb = argv.includes('--force-db')
|
|
91
|
+
const extraSlugs = argv.find((a) => a.startsWith('--extra-slugs='))
|
|
92
|
+
const pm = detectPackageManager(projectDir)
|
|
93
|
+
|
|
94
|
+
console.log('')
|
|
95
|
+
console.log(` ${PACKAGE_NAME} — uninstall`)
|
|
96
|
+
console.log(' ─────────────────────────────────────────────')
|
|
97
|
+
console.log(` Project: ${projectDir}`)
|
|
98
|
+
console.log(` Package manager: ${pm}`)
|
|
99
|
+
console.log(` Mode: ${confirm ? 'APPLY' : 'DRY RUN — nothing will be written'}`)
|
|
100
|
+
if (keepData) console.log(' --keep-data: rows will be kept')
|
|
101
|
+
console.log('')
|
|
102
|
+
|
|
103
|
+
// ── 1. Where is the plugin referenced? ───────────────────
|
|
104
|
+
const sourceFiles = findSourceFiles(path.join(projectDir, 'src'))
|
|
105
|
+
const referencing = sourceFiles.filter((file) => {
|
|
106
|
+
try {
|
|
107
|
+
return fs.readFileSync(file, 'utf-8').includes(PACKAGE_NAME)
|
|
108
|
+
} catch {
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
console.log(' [1/3] Source references')
|
|
114
|
+
if (referencing.length === 0) {
|
|
115
|
+
console.log(' none found under src/')
|
|
116
|
+
} else {
|
|
117
|
+
for (const file of referencing) console.log(` ${path.relative(projectDir, file)}`)
|
|
118
|
+
console.log('')
|
|
119
|
+
console.log(' Remove the `supportPlugin(...)` call and its import yourself.')
|
|
120
|
+
console.log(' This script never rewrites your source: the plugin call carries')
|
|
121
|
+
console.log(' your configuration, and a regex that guesses where it ends is how')
|
|
122
|
+
console.log(' a config file gets silently corrupted.')
|
|
123
|
+
}
|
|
124
|
+
console.log('')
|
|
125
|
+
|
|
126
|
+
const declared = isDeclaredDependency(projectDir)
|
|
127
|
+
const detected = referencing.length > 0 || declared || forceDb
|
|
128
|
+
|
|
129
|
+
// ── 2. Data ──────────────────────────────────────────────
|
|
130
|
+
console.log(' [2/3] Data')
|
|
131
|
+
if (!detected) {
|
|
132
|
+
console.log(` ${PACKAGE_NAME} was not found in this project`)
|
|
133
|
+
console.log(' (no source reference, not in package.json dependencies).')
|
|
134
|
+
console.log(' Skipping the data step. Force it with --force-db.')
|
|
135
|
+
console.log('')
|
|
136
|
+
} else {
|
|
137
|
+
// The Payload local API needs the host's TypeScript config loaded, which is
|
|
138
|
+
// exactly what `payload run` sets up. Spawning it beats reimplementing a
|
|
139
|
+
// TypeScript loader here.
|
|
140
|
+
const dataScript = path.join(HERE, 'uninstall-data.mjs')
|
|
141
|
+
const args = ['payload', 'run', dataScript]
|
|
142
|
+
if (confirm) args.push('--confirm')
|
|
143
|
+
if (keepData) args.push('--keep-data')
|
|
144
|
+
if (extraSlugs) args.push(extraSlugs)
|
|
145
|
+
|
|
146
|
+
const result = spawnSync('npx', args, { cwd: projectDir, stdio: 'inherit' })
|
|
147
|
+
if (result.status !== 0) {
|
|
148
|
+
console.log('')
|
|
149
|
+
console.log(' Could not run the data step through `npx payload run`.')
|
|
150
|
+
console.log(' Run it by hand from your project root:')
|
|
151
|
+
console.log(` npx payload run ${dataScript} ${confirm ? '--confirm' : ''}`)
|
|
152
|
+
console.log('')
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ── 3. The package ───────────────────────────────────────
|
|
157
|
+
console.log(' [3/3] Package')
|
|
158
|
+
if (!confirm) {
|
|
159
|
+
const removeCmd = pm === 'npm' ? 'npm uninstall' : `${pm} remove`
|
|
160
|
+
console.log(` would run: ${removeCmd} ${PACKAGE_NAME}`)
|
|
161
|
+
console.log(` would run: ${pm === 'npm' ? 'npx' : pm} payload generate:importmap`)
|
|
162
|
+
console.log('')
|
|
163
|
+
console.log(' Dry run finished. Nothing was changed. Re-run with --confirm.')
|
|
164
|
+
console.log('')
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (referencing.length > 0) {
|
|
169
|
+
console.log(' Skipped: the plugin is still imported by the files listed above.')
|
|
170
|
+
console.log(' Remove those references first, then re-run.')
|
|
171
|
+
console.log('')
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const removeCmd = pm === 'npm' ? 'npm uninstall' : `${pm} remove`
|
|
176
|
+
run(`${removeCmd} ${PACKAGE_NAME}`, projectDir)
|
|
177
|
+
run(`${pm === 'npm' ? 'npx' : pm} payload generate:importmap`, projectDir)
|
|
178
|
+
|
|
179
|
+
console.log('')
|
|
180
|
+
console.log(' Done.')
|
|
181
|
+
console.log('')
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
main()
|
|
@@ -525,9 +525,16 @@ function createCascadeDelete(slugs: CollectionSlugs): CollectionBeforeDeleteHook
|
|
|
525
525
|
return async ({ id, req }) => {
|
|
526
526
|
const collections = [slugs.ticketMessages, slugs.ticketActivityLog, slugs.timeEntries, slugs.satisfactionSurveys]
|
|
527
527
|
for (const slug of collections) {
|
|
528
|
+
// `req` must be forwarded: it carries the ambient transaction. Without
|
|
529
|
+
// it these cascades open their own connection and, on SQLite, deadlock
|
|
530
|
+
// against the transaction that is deleting the ticket — the delete then
|
|
531
|
+
// fails silently (Payload collects per-document errors instead of
|
|
532
|
+
// throwing) and the children survive the parent.
|
|
533
|
+
if (!req.payload.collections?.[slug as never]) continue
|
|
528
534
|
await dbDelete(req.payload, slug, {
|
|
529
535
|
where: { ticket: { equals: id } },
|
|
530
536
|
overrideAccess: true,
|
|
537
|
+
req,
|
|
531
538
|
})
|
|
532
539
|
}
|
|
533
540
|
}
|
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
import type { CollectionConfig, CollectionAfterChangeHook, CollectionAfterDeleteHook } from 'payload'
|
|
1
|
+
import type { CollectionConfig, CollectionAfterChangeHook, CollectionAfterDeleteHook, PayloadRequest } from 'payload'
|
|
2
2
|
import type { Payload } from 'payload'
|
|
3
3
|
import type { CollectionSlugs } from '../utils/slugs'
|
|
4
4
|
|
|
5
5
|
// ─── Hooks ───────────────────────────────────────────────
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* `req` is forwarded to every operation so the rollup joins the caller's
|
|
9
|
+
* transaction. Reading or writing outside it would see a stale total and, on
|
|
10
|
+
* SQLite, contend with the very transaction that triggered the hook.
|
|
11
|
+
*/
|
|
7
12
|
async function recalculateTicketTime(
|
|
8
13
|
payload: Payload,
|
|
9
14
|
slugs: CollectionSlugs,
|
|
10
15
|
ticketId: number | string,
|
|
16
|
+
req?: PayloadRequest,
|
|
11
17
|
): Promise<void> {
|
|
12
18
|
// Sum all time entries for this ticket, paginating to avoid loading all at once
|
|
13
19
|
let totalMinutes = 0
|
|
@@ -23,6 +29,7 @@ async function recalculateTicketTime(
|
|
|
23
29
|
depth: 0,
|
|
24
30
|
overrideAccess: true,
|
|
25
31
|
select: { duration: true },
|
|
32
|
+
req,
|
|
26
33
|
})
|
|
27
34
|
|
|
28
35
|
for (const entry of entries.docs) {
|
|
@@ -38,6 +45,7 @@ async function recalculateTicketTime(
|
|
|
38
45
|
id: ticketId,
|
|
39
46
|
data: { totalTimeMinutes: totalMinutes },
|
|
40
47
|
overrideAccess: true,
|
|
48
|
+
req,
|
|
41
49
|
})
|
|
42
50
|
}
|
|
43
51
|
|
|
@@ -45,7 +53,7 @@ function createRecalculateTicketTime(slugs: CollectionSlugs): CollectionAfterCha
|
|
|
45
53
|
return async ({ doc, req }) => {
|
|
46
54
|
if (!doc.ticket) return
|
|
47
55
|
const ticketId = typeof doc.ticket === 'object' ? doc.ticket.id : doc.ticket
|
|
48
|
-
await recalculateTicketTime(req.payload, slugs, ticketId)
|
|
56
|
+
await recalculateTicketTime(req.payload, slugs, ticketId, req)
|
|
49
57
|
}
|
|
50
58
|
}
|
|
51
59
|
|
|
@@ -59,7 +67,7 @@ function createRecalculateTicketTimeOnDelete(slugs: CollectionSlugs): Collection
|
|
|
59
67
|
if (!doc?.ticket) return
|
|
60
68
|
const ticketId = typeof doc.ticket === 'object' ? doc.ticket.id : doc.ticket
|
|
61
69
|
try {
|
|
62
|
-
await recalculateTicketTime(req.payload, slugs, ticketId)
|
|
70
|
+
await recalculateTicketTime(req.payload, slugs, ticketId, req)
|
|
63
71
|
} catch (err) {
|
|
64
72
|
// The entry is already gone; never fail the delete on the rollup refresh.
|
|
65
73
|
console.error('[support] Failed to recalculate ticket time after delete:', err)
|