@consilioweb/payload-support 0.5.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.
- package/LICENSE +21 -0
- package/README.md +525 -0
- package/dist/client.cjs +7 -0
- package/dist/client.d.cts +3 -0
- package/dist/client.d.ts +3 -0
- package/dist/client.js +5 -0
- package/dist/index.cjs +7766 -0
- package/dist/index.d.cts +384 -0
- package/dist/index.d.ts +384 -0
- package/dist/index.js +7730 -0
- package/dist/views.d.cts +30 -0
- package/dist/views.d.ts +30 -0
- package/package.json +131 -0
- package/src/client.ts +1 -0
- package/src/collections/AuthLogs.ts +65 -0
- package/src/collections/CannedResponses.ts +69 -0
- package/src/collections/ChatMessages.ts +98 -0
- package/src/collections/EmailLogs.ts +94 -0
- package/src/collections/KnowledgeBase.ts +99 -0
- package/src/collections/Macros.ts +98 -0
- package/src/collections/PendingEmails.ts +122 -0
- package/src/collections/SatisfactionSurveys.ts +98 -0
- package/src/collections/SlaPolicies.ts +123 -0
- package/src/collections/SupportClients.ts +210 -0
- package/src/collections/TicketActivityLog.ts +81 -0
- package/src/collections/TicketMessages.ts +364 -0
- package/src/collections/TicketStatuses.ts +108 -0
- package/src/collections/Tickets.ts +704 -0
- package/src/collections/TimeEntries.ts +105 -0
- package/src/collections/WebhookEndpoints.ts +96 -0
- package/src/collections/index.ts +16 -0
- package/src/components/TicketConversation/components/AISummaryPanel.tsx +85 -0
- package/src/components/TicketConversation/components/ActionPanels.tsx +140 -0
- package/src/components/TicketConversation/components/ActivityLog.tsx +39 -0
- package/src/components/TicketConversation/components/ClientBar.tsx +37 -0
- package/src/components/TicketConversation/components/ClientHistory.tsx +117 -0
- package/src/components/TicketConversation/components/CodeBlock.tsx +186 -0
- package/src/components/TicketConversation/components/CodeBlockInserter.tsx +166 -0
- package/src/components/TicketConversation/components/QuickActions.tsx +82 -0
- package/src/components/TicketConversation/components/TicketHeader.tsx +91 -0
- package/src/components/TicketConversation/components/TimeTrackingPanel.tsx +161 -0
- package/src/components/TicketConversation/config.ts +82 -0
- package/src/components/TicketConversation/constants.ts +74 -0
- package/src/components/TicketConversation/context.ts +63 -0
- package/src/components/TicketConversation/hooks/useAI.ts +180 -0
- package/src/components/TicketConversation/hooks/useMessageActions.ts +131 -0
- package/src/components/TicketConversation/hooks/useReply.ts +190 -0
- package/src/components/TicketConversation/hooks/useTicketActions.ts +205 -0
- package/src/components/TicketConversation/hooks/useTimeTracking.ts +107 -0
- package/src/components/TicketConversation/hooks/useTranslation.ts +116 -0
- package/src/components/TicketConversation/index.tsx +1110 -0
- package/src/components/TicketConversation/locales/en.json +878 -0
- package/src/components/TicketConversation/locales/fr.json +878 -0
- package/src/components/TicketConversation/types.ts +54 -0
- package/src/components/TicketConversation/utils.ts +25 -0
- package/src/endpoints/admin-chat-stream.ts +238 -0
- package/src/endpoints/admin-chat.ts +263 -0
- package/src/endpoints/admin-stats.ts +200 -0
- package/src/endpoints/ai.ts +199 -0
- package/src/endpoints/apply-macro.ts +144 -0
- package/src/endpoints/auth-2fa.ts +163 -0
- package/src/endpoints/auto-close.ts +175 -0
- package/src/endpoints/billing.ts +167 -0
- package/src/endpoints/bulk-action.ts +103 -0
- package/src/endpoints/chat-stream.ts +127 -0
- package/src/endpoints/chat.ts +188 -0
- package/src/endpoints/chatbot.ts +113 -0
- package/src/endpoints/delete-account.ts +129 -0
- package/src/endpoints/email-stats.ts +109 -0
- package/src/endpoints/export-csv.ts +84 -0
- package/src/endpoints/export-data.ts +104 -0
- package/src/endpoints/import-conversation.ts +307 -0
- package/src/endpoints/index.ts +154 -0
- package/src/endpoints/login.ts +92 -0
- package/src/endpoints/merge-clients.ts +132 -0
- package/src/endpoints/merge-tickets.ts +137 -0
- package/src/endpoints/oauth-google.ts +179 -0
- package/src/endpoints/pending-emails-process.ts +224 -0
- package/src/endpoints/presence.ts +104 -0
- package/src/endpoints/process-scheduled.ts +144 -0
- package/src/endpoints/purge-logs.ts +58 -0
- package/src/endpoints/resend-notification.ts +99 -0
- package/src/endpoints/round-robin-config.ts +92 -0
- package/src/endpoints/satisfaction.ts +93 -0
- package/src/endpoints/search.ts +106 -0
- package/src/endpoints/seed-kb.ts +153 -0
- package/src/endpoints/settings.ts +144 -0
- package/src/endpoints/signature.ts +93 -0
- package/src/endpoints/sla-check.ts +124 -0
- package/src/endpoints/split-ticket.ts +131 -0
- package/src/endpoints/statuses.ts +45 -0
- package/src/endpoints/track-open.ts +154 -0
- package/src/endpoints/typing.ts +101 -0
- package/src/endpoints/user-prefs.ts +125 -0
- package/src/hooks/checkSLA.ts +414 -0
- package/src/hooks/ticketStatusEmail.ts +182 -0
- package/src/index.ts +51 -0
- package/src/plugin.ts +157 -0
- package/src/portal/LiveChat.tsx +1353 -0
- package/src/portal/auth/ChatWidget.tsx +350 -0
- package/src/portal/auth/ChatbotWidget.tsx +285 -0
- package/src/portal/auth/SupportHeader.tsx +409 -0
- package/src/portal/auth/dashboard/DashboardClient.tsx +650 -0
- package/src/portal/auth/dashboard/page.tsx +84 -0
- package/src/portal/auth/faq/FAQSearch.tsx +117 -0
- package/src/portal/auth/faq/page.tsx +199 -0
- package/src/portal/auth/layout.tsx +61 -0
- package/src/portal/auth/profile/page.tsx +705 -0
- package/src/portal/auth/tickets/detail/CloseTicketButton.tsx +74 -0
- package/src/portal/auth/tickets/detail/CollapsibleMessages.tsx +46 -0
- package/src/portal/auth/tickets/detail/MarkSolutionButton.tsx +50 -0
- package/src/portal/auth/tickets/detail/MessageActions.tsx +158 -0
- package/src/portal/auth/tickets/detail/PrintButton.tsx +16 -0
- package/src/portal/auth/tickets/detail/ReadReceipt.tsx +34 -0
- package/src/portal/auth/tickets/detail/ReopenTicketButton.tsx +74 -0
- package/src/portal/auth/tickets/detail/SatisfactionForm.tsx +156 -0
- package/src/portal/auth/tickets/detail/TicketPolling.tsx +57 -0
- package/src/portal/auth/tickets/detail/TicketReplyForm.tsx +294 -0
- package/src/portal/auth/tickets/detail/TypingIndicator.tsx +58 -0
- package/src/portal/auth/tickets/detail/page.tsx +738 -0
- package/src/portal/auth/tickets/new/page.tsx +515 -0
- package/src/portal/forgot-password/page.tsx +114 -0
- package/src/portal/layout.tsx +26 -0
- package/src/portal/locales/en.json +374 -0
- package/src/portal/locales/fr.json +374 -0
- package/src/portal/login/page.tsx +351 -0
- package/src/portal/page.tsx +162 -0
- package/src/portal/register/page.tsx +281 -0
- package/src/portal/reset-password/page.tsx +152 -0
- package/src/styles/BillingView.module.scss +311 -0
- package/src/styles/ChatView.module.scss +438 -0
- package/src/styles/CommandPalette.module.scss +160 -0
- package/src/styles/CrmView.module.scss +554 -0
- package/src/styles/EmailTracking.module.scss +238 -0
- package/src/styles/ImportConversation.module.scss +267 -0
- package/src/styles/Layout.module.scss +55 -0
- package/src/styles/Logs.module.scss +164 -0
- package/src/styles/NewTicket.module.scss +143 -0
- package/src/styles/PendingEmails.module.scss +629 -0
- package/src/styles/SupportDashboard.module.scss +649 -0
- package/src/styles/TicketDetail.module.scss +1043 -0
- package/src/styles/TicketInbox.module.scss +296 -0
- package/src/styles/TicketingSettings.module.scss +358 -0
- package/src/styles/TimeDashboard.module.scss +287 -0
- package/src/styles/_tokens.scss +78 -0
- package/src/styles/theme.css +633 -0
- package/src/types.ts +255 -0
- package/src/utils/adminNotification.ts +38 -0
- package/src/utils/auth.ts +46 -0
- package/src/utils/emailTemplate.ts +343 -0
- package/src/utils/fireWebhooks.ts +84 -0
- package/src/utils/index.ts +22 -0
- package/src/utils/rateLimiter.ts +52 -0
- package/src/utils/readSettings.ts +67 -0
- package/src/utils/slugs.ts +54 -0
- package/src/utils/webhookDispatcher.ts +120 -0
- package/src/views/BillingView/client.tsx +137 -0
- package/src/views/BillingView/index.tsx +33 -0
- package/src/views/ChatView/client.tsx +294 -0
- package/src/views/ChatView/index.tsx +33 -0
- package/src/views/CrmView/client.tsx +206 -0
- package/src/views/CrmView/index.tsx +33 -0
- package/src/views/EmailTrackingView/client.tsx +124 -0
- package/src/views/EmailTrackingView/index.tsx +33 -0
- package/src/views/ImportConversationView/client.tsx +133 -0
- package/src/views/ImportConversationView/index.tsx +33 -0
- package/src/views/LogsView/client.tsx +151 -0
- package/src/views/LogsView/index.tsx +30 -0
- package/src/views/NewTicketView/client.tsx +227 -0
- package/src/views/NewTicketView/index.tsx +30 -0
- package/src/views/PendingEmailsView/client.tsx +177 -0
- package/src/views/PendingEmailsView/index.tsx +33 -0
- package/src/views/SupportDashboardView/client.tsx +424 -0
- package/src/views/SupportDashboardView/index.tsx +33 -0
- package/src/views/TicketDetailView/client.tsx +775 -0
- package/src/views/TicketDetailView/index.tsx +33 -0
- package/src/views/TicketInboxView/client.tsx +313 -0
- package/src/views/TicketInboxView/index.tsx +30 -0
- package/src/views/TicketingSettingsView/client.tsx +866 -0
- package/src/views/TicketingSettingsView/index.tsx +33 -0
- package/src/views/TimeDashboardView/client.tsx +144 -0
- package/src/views/TimeDashboardView/index.tsx +33 -0
- package/src/views/shared/AdminViewHeader.tsx +69 -0
- package/src/views/shared/ErrorBoundary.tsx +68 -0
- package/src/views/shared/Skeleton.tsx +125 -0
- package/src/views/shared/adminTokens.ts +37 -0
- package/src/views/shared/config.ts +82 -0
- package/src/views/shared/index.ts +6 -0
- package/src/views.ts +16 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { CollectionConfig } from 'payload'
|
|
2
|
+
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
|
+
|
|
4
|
+
// ─── Collection factory ──────────────────────────────────
|
|
5
|
+
|
|
6
|
+
export function createMacrosCollection(slugs: CollectionSlugs): CollectionConfig {
|
|
7
|
+
return {
|
|
8
|
+
slug: slugs.macros,
|
|
9
|
+
labels: {
|
|
10
|
+
singular: 'Macro',
|
|
11
|
+
plural: 'Macros',
|
|
12
|
+
},
|
|
13
|
+
admin: {
|
|
14
|
+
useAsTitle: 'name',
|
|
15
|
+
group: 'Support',
|
|
16
|
+
defaultColumns: ['name', 'description', 'sortOrder', 'isActive', 'updatedAt'],
|
|
17
|
+
},
|
|
18
|
+
fields: [
|
|
19
|
+
{
|
|
20
|
+
name: 'name',
|
|
21
|
+
type: 'text',
|
|
22
|
+
required: true,
|
|
23
|
+
label: 'Nom',
|
|
24
|
+
admin: {
|
|
25
|
+
description: 'Nom court pour identifier la macro (ex: "Clôturer & remercier")',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'description',
|
|
30
|
+
type: 'text',
|
|
31
|
+
label: 'Description',
|
|
32
|
+
admin: {
|
|
33
|
+
description: 'Description optionnelle de ce que fait la macro',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'actions',
|
|
38
|
+
type: 'array',
|
|
39
|
+
required: true,
|
|
40
|
+
label: 'Actions',
|
|
41
|
+
minRows: 1,
|
|
42
|
+
admin: {
|
|
43
|
+
description: 'Liste des actions exécutées dans l\'ordre',
|
|
44
|
+
},
|
|
45
|
+
fields: [
|
|
46
|
+
{
|
|
47
|
+
name: 'type',
|
|
48
|
+
type: 'select',
|
|
49
|
+
required: true,
|
|
50
|
+
label: 'Type d\'action',
|
|
51
|
+
options: [
|
|
52
|
+
{ label: 'Changer le statut', value: 'set_status' },
|
|
53
|
+
{ label: 'Changer la priorité', value: 'set_priority' },
|
|
54
|
+
{ label: 'Ajouter un tag', value: 'add_tag' },
|
|
55
|
+
{ label: 'Envoyer une réponse', value: 'send_reply' },
|
|
56
|
+
{ label: 'Assigner', value: 'assign' },
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'value',
|
|
61
|
+
type: 'text',
|
|
62
|
+
required: true,
|
|
63
|
+
label: 'Valeur',
|
|
64
|
+
admin: {
|
|
65
|
+
description: 'Statut, priorité, tag, corps de la réponse, ou ID utilisateur selon le type',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'sortOrder',
|
|
72
|
+
type: 'number',
|
|
73
|
+
defaultValue: 0,
|
|
74
|
+
label: 'Ordre',
|
|
75
|
+
admin: {
|
|
76
|
+
position: 'sidebar',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'isActive',
|
|
81
|
+
type: 'checkbox',
|
|
82
|
+
defaultValue: true,
|
|
83
|
+
label: 'Active',
|
|
84
|
+
admin: {
|
|
85
|
+
position: 'sidebar',
|
|
86
|
+
description: 'Désactiver pour masquer la macro sans la supprimer',
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
access: {
|
|
91
|
+
create: ({ req }) => req.user?.collection === slugs.users,
|
|
92
|
+
read: ({ req }) => req.user?.collection === slugs.users,
|
|
93
|
+
update: ({ req }) => req.user?.collection === slugs.users,
|
|
94
|
+
delete: ({ req }) => req.user?.collection === slugs.users,
|
|
95
|
+
},
|
|
96
|
+
timestamps: true,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { CollectionConfig } from 'payload'
|
|
2
|
+
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
|
+
|
|
4
|
+
// ─── Collection factory ──────────────────────────────────
|
|
5
|
+
|
|
6
|
+
export function createPendingEmailsCollection(slugs: CollectionSlugs): CollectionConfig {
|
|
7
|
+
return {
|
|
8
|
+
slug: slugs.pendingEmails,
|
|
9
|
+
admin: {
|
|
10
|
+
group: 'Support',
|
|
11
|
+
useAsTitle: 'subject',
|
|
12
|
+
defaultColumns: ['status', 'senderEmail', 'subject', 'createdAt'],
|
|
13
|
+
hidden: true, // Managed via custom admin view
|
|
14
|
+
},
|
|
15
|
+
access: {
|
|
16
|
+
read: ({ req }) => Boolean(req.user?.collection === slugs.users),
|
|
17
|
+
update: ({ req }) => Boolean(req.user?.collection === slugs.users),
|
|
18
|
+
delete: ({ req }) => Boolean(req.user?.collection === slugs.users),
|
|
19
|
+
create: ({ req }) => {
|
|
20
|
+
// Allow admin users or webhook calls with secret header
|
|
21
|
+
if (req.user?.collection === slugs.users) return true
|
|
22
|
+
const webhookSecret = req.headers.get('x-webhook-secret')
|
|
23
|
+
if (webhookSecret && process.env.SUPPORT_WEBHOOK_SECRET && webhookSecret === process.env.SUPPORT_WEBHOOK_SECRET) return true
|
|
24
|
+
return false
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
fields: [
|
|
28
|
+
{
|
|
29
|
+
name: 'senderEmail',
|
|
30
|
+
type: 'email',
|
|
31
|
+
required: true,
|
|
32
|
+
index: true,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'senderName',
|
|
36
|
+
type: 'text',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'subject',
|
|
40
|
+
type: 'text',
|
|
41
|
+
required: true,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'rawSubject',
|
|
45
|
+
type: 'text',
|
|
46
|
+
admin: { readOnly: true },
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'body',
|
|
50
|
+
type: 'textarea',
|
|
51
|
+
required: true,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'bodyHtml',
|
|
55
|
+
type: 'textarea',
|
|
56
|
+
admin: { readOnly: true },
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'client',
|
|
60
|
+
type: 'relationship',
|
|
61
|
+
relationTo: slugs.supportClients,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'attachments',
|
|
65
|
+
type: 'array',
|
|
66
|
+
fields: [
|
|
67
|
+
{
|
|
68
|
+
name: 'file',
|
|
69
|
+
type: 'upload',
|
|
70
|
+
relationTo: slugs.media,
|
|
71
|
+
required: true,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'status',
|
|
77
|
+
type: 'select',
|
|
78
|
+
defaultValue: 'pending',
|
|
79
|
+
index: true,
|
|
80
|
+
options: [
|
|
81
|
+
{ label: 'En attente', value: 'pending' },
|
|
82
|
+
{ label: 'Traité', value: 'processed' },
|
|
83
|
+
{ label: 'Ignoré', value: 'ignored' },
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'processedAction',
|
|
88
|
+
type: 'select',
|
|
89
|
+
options: [
|
|
90
|
+
{ label: 'Ticket créé', value: 'ticket_created' },
|
|
91
|
+
{ label: 'Message ajouté', value: 'message_added' },
|
|
92
|
+
{ label: 'Ignoré', value: 'ignored' },
|
|
93
|
+
],
|
|
94
|
+
admin: { condition: (data) => data?.status !== 'pending' },
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'processedTicket',
|
|
98
|
+
type: 'relationship',
|
|
99
|
+
relationTo: slugs.tickets,
|
|
100
|
+
admin: { condition: (data) => data?.processedAction === 'ticket_created' || data?.processedAction === 'message_added' },
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'processedAt',
|
|
104
|
+
type: 'date',
|
|
105
|
+
admin: { condition: (data) => data?.status !== 'pending' },
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: 'suggestedTickets',
|
|
109
|
+
type: 'json',
|
|
110
|
+
admin: { readOnly: true },
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: 'cc',
|
|
114
|
+
type: 'text',
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'recipientEmail',
|
|
118
|
+
type: 'text',
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { CollectionConfig } from 'payload'
|
|
2
|
+
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
|
+
|
|
4
|
+
// ─── Collection factory ──────────────────────────────────
|
|
5
|
+
|
|
6
|
+
export function createSatisfactionSurveysCollection(slugs: CollectionSlugs): CollectionConfig {
|
|
7
|
+
return {
|
|
8
|
+
slug: slugs.satisfactionSurveys,
|
|
9
|
+
labels: {
|
|
10
|
+
singular: 'Enquête satisfaction',
|
|
11
|
+
plural: 'Enquêtes satisfaction',
|
|
12
|
+
},
|
|
13
|
+
admin: {
|
|
14
|
+
hidden: true,
|
|
15
|
+
group: 'Support',
|
|
16
|
+
defaultColumns: ['source', 'rating', 'client', 'createdAt'],
|
|
17
|
+
},
|
|
18
|
+
fields: [
|
|
19
|
+
{
|
|
20
|
+
name: 'source',
|
|
21
|
+
type: 'select',
|
|
22
|
+
options: [
|
|
23
|
+
{ label: 'Ticket', value: 'ticket' },
|
|
24
|
+
{ label: 'Live Chat', value: 'live-chat' },
|
|
25
|
+
],
|
|
26
|
+
defaultValue: 'ticket',
|
|
27
|
+
required: true,
|
|
28
|
+
label: 'Source',
|
|
29
|
+
admin: {
|
|
30
|
+
description: 'Ticket de support ou session de chat en direct',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'ticket',
|
|
35
|
+
type: 'relationship',
|
|
36
|
+
relationTo: slugs.tickets,
|
|
37
|
+
unique: true,
|
|
38
|
+
label: 'Ticket',
|
|
39
|
+
admin: {
|
|
40
|
+
condition: (data) => data?.source === 'ticket',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'chatSession',
|
|
45
|
+
type: 'text',
|
|
46
|
+
unique: true,
|
|
47
|
+
label: 'Session Chat',
|
|
48
|
+
admin: {
|
|
49
|
+
condition: (data) => data?.source === 'live-chat',
|
|
50
|
+
description: 'ID de la session live chat',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'client',
|
|
55
|
+
type: 'relationship',
|
|
56
|
+
relationTo: slugs.supportClients,
|
|
57
|
+
required: true,
|
|
58
|
+
label: 'Client',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'rating',
|
|
62
|
+
type: 'number',
|
|
63
|
+
required: true,
|
|
64
|
+
min: 1,
|
|
65
|
+
max: 5,
|
|
66
|
+
label: 'Note (1-5)',
|
|
67
|
+
admin: {
|
|
68
|
+
description: '1 = Très insatisfait, 5 = Très satisfait',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'comment',
|
|
73
|
+
type: 'textarea',
|
|
74
|
+
label: 'Commentaire',
|
|
75
|
+
admin: {
|
|
76
|
+
description: 'Feedback libre du client',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
access: {
|
|
81
|
+
create: ({ req }) => {
|
|
82
|
+
if (req.user?.collection === slugs.users) return true
|
|
83
|
+
if (req.user?.collection === slugs.supportClients) return true
|
|
84
|
+
return false
|
|
85
|
+
},
|
|
86
|
+
read: ({ req }) => {
|
|
87
|
+
if (req.user?.collection === slugs.users) return true
|
|
88
|
+
if (req.user?.collection === slugs.supportClients) {
|
|
89
|
+
return { client: { equals: req.user.id } }
|
|
90
|
+
}
|
|
91
|
+
return false
|
|
92
|
+
},
|
|
93
|
+
update: () => false, // Surveys are immutable once submitted
|
|
94
|
+
delete: ({ req }) => req.user?.collection === slugs.users,
|
|
95
|
+
},
|
|
96
|
+
timestamps: true,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { CollectionConfig } from 'payload'
|
|
2
|
+
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
|
+
|
|
4
|
+
// ─── Collection factory ──────────────────────────────────
|
|
5
|
+
|
|
6
|
+
export function createSlaPoliciesCollection(slugs: CollectionSlugs): CollectionConfig {
|
|
7
|
+
return {
|
|
8
|
+
slug: slugs.slaPolicies,
|
|
9
|
+
labels: {
|
|
10
|
+
singular: 'Politique SLA',
|
|
11
|
+
plural: 'Politiques SLA',
|
|
12
|
+
},
|
|
13
|
+
admin: {
|
|
14
|
+
useAsTitle: 'name',
|
|
15
|
+
group: 'Support',
|
|
16
|
+
defaultColumns: ['name', 'priority', 'firstResponseTime', 'resolutionTime', 'businessHoursOnly', 'isDefault'],
|
|
17
|
+
},
|
|
18
|
+
fields: [
|
|
19
|
+
{
|
|
20
|
+
name: 'name',
|
|
21
|
+
type: 'text',
|
|
22
|
+
required: true,
|
|
23
|
+
label: 'Nom',
|
|
24
|
+
admin: {
|
|
25
|
+
description: 'Ex: Standard, Premium, Urgence',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
type: 'row',
|
|
30
|
+
fields: [
|
|
31
|
+
{
|
|
32
|
+
name: 'firstResponseTime',
|
|
33
|
+
type: 'number',
|
|
34
|
+
required: true,
|
|
35
|
+
label: 'Délai 1ère réponse (min)',
|
|
36
|
+
min: 1,
|
|
37
|
+
admin: {
|
|
38
|
+
width: '50%',
|
|
39
|
+
description: 'Temps cible en minutes pour la première réponse',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'resolutionTime',
|
|
44
|
+
type: 'number',
|
|
45
|
+
required: true,
|
|
46
|
+
label: 'Délai résolution (min)',
|
|
47
|
+
min: 1,
|
|
48
|
+
admin: {
|
|
49
|
+
width: '50%',
|
|
50
|
+
description: 'Temps cible en minutes pour la résolution complète',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'priority',
|
|
57
|
+
type: 'select',
|
|
58
|
+
required: true,
|
|
59
|
+
label: 'Priorité',
|
|
60
|
+
options: [
|
|
61
|
+
{ label: 'Basse', value: 'low' },
|
|
62
|
+
{ label: 'Normale', value: 'normal' },
|
|
63
|
+
{ label: 'Haute', value: 'high' },
|
|
64
|
+
{ label: 'Urgente', value: 'urgent' },
|
|
65
|
+
],
|
|
66
|
+
admin: {
|
|
67
|
+
description: 'Priorité de ticket à laquelle cette politique s\'applique',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
type: 'row',
|
|
72
|
+
fields: [
|
|
73
|
+
{
|
|
74
|
+
name: 'businessHoursOnly',
|
|
75
|
+
type: 'checkbox',
|
|
76
|
+
defaultValue: true,
|
|
77
|
+
label: 'Heures ouvrées uniquement',
|
|
78
|
+
admin: {
|
|
79
|
+
width: '50%',
|
|
80
|
+
description: 'Compter uniquement les heures ouvrées (lun-ven, 9h-18h)',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'escalateOnBreach',
|
|
85
|
+
type: 'checkbox',
|
|
86
|
+
defaultValue: false,
|
|
87
|
+
label: 'Escalade auto sur dépassement',
|
|
88
|
+
admin: {
|
|
89
|
+
width: '50%',
|
|
90
|
+
description: 'Notifier automatiquement en cas de dépassement SLA',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'escalateTo',
|
|
97
|
+
type: 'relationship',
|
|
98
|
+
relationTo: slugs.users,
|
|
99
|
+
label: 'Escalader à',
|
|
100
|
+
admin: {
|
|
101
|
+
description: 'Utilisateur à notifier en cas de dépassement SLA',
|
|
102
|
+
condition: (data) => data?.escalateOnBreach === true,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: 'isDefault',
|
|
107
|
+
type: 'checkbox',
|
|
108
|
+
defaultValue: false,
|
|
109
|
+
label: 'Politique par défaut',
|
|
110
|
+
admin: {
|
|
111
|
+
description: 'Utiliser cette politique pour les tickets sans SLA spécifique (par priorité)',
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
access: {
|
|
116
|
+
create: ({ req }) => req.user?.collection === slugs.users,
|
|
117
|
+
read: ({ req }) => req.user?.collection === slugs.users,
|
|
118
|
+
update: ({ req }) => req.user?.collection === slugs.users,
|
|
119
|
+
delete: ({ req }) => req.user?.collection === slugs.users,
|
|
120
|
+
},
|
|
121
|
+
timestamps: true,
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import type { CollectionConfig, CollectionAfterChangeHook } from 'payload'
|
|
2
|
+
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
|
+
import { escapeHtml, emailWrapper, emailButton, emailParagraph } from '../utils/emailTemplate'
|
|
4
|
+
import { readSupportSettings } from '../utils/readSettings'
|
|
5
|
+
|
|
6
|
+
// ─── Hooks ───────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
function createSendInvitationOnCreate(slugs: CollectionSlugs): CollectionAfterChangeHook {
|
|
9
|
+
return async ({ doc, operation, req }) => {
|
|
10
|
+
if (operation !== 'create') return doc
|
|
11
|
+
|
|
12
|
+
// Only when an admin creates the client (not self-registration)
|
|
13
|
+
if (req.user?.collection !== slugs.users) return doc
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const { payload } = req
|
|
17
|
+
const settings = await readSupportSettings(payload)
|
|
18
|
+
|
|
19
|
+
// Generate reset token without sending the default forgotPassword email
|
|
20
|
+
const token = await payload.forgotPassword({
|
|
21
|
+
collection: slugs.supportClients,
|
|
22
|
+
data: { email: doc.email },
|
|
23
|
+
disableEmail: true,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const baseUrl = process.env.NEXT_PUBLIC_SERVER_URL || ''
|
|
27
|
+
const resetUrl = `${baseUrl}/support/reset-password?token=${token}`
|
|
28
|
+
|
|
29
|
+
const replyTo = settings.email.replyToAddress || process.env.SUPPORT_EMAIL || ''
|
|
30
|
+
await payload.sendEmail({
|
|
31
|
+
to: doc.email,
|
|
32
|
+
...(replyTo ? { replyTo } : {}),
|
|
33
|
+
subject: 'Activez votre compte support',
|
|
34
|
+
html: emailWrapper('Bienvenue sur votre espace support', [
|
|
35
|
+
emailParagraph(`Bonjour <strong>${escapeHtml(doc.firstName || '')}</strong>,`),
|
|
36
|
+
emailParagraph('Un espace support a ete cree pour vous. Vous pourrez y soumettre vos demandes, suivre vos tickets et echanger directement avec notre equipe.'),
|
|
37
|
+
emailParagraph('Pour activer votre compte, cliquez sur le bouton ci-dessous pour definir votre mot de passe :'),
|
|
38
|
+
emailButton('Definir mon mot de passe', resetUrl),
|
|
39
|
+
`<table cellpadding="0" cellspacing="0" border="0" width="100%" style="margin: 20px 0;">
|
|
40
|
+
<tr><td style="padding: 6px 0; font-size: 14px; color: #374151; line-height: 1.6;">• Soumettre des demandes de support</td></tr>
|
|
41
|
+
<tr><td style="padding: 6px 0; font-size: 14px; color: #374151; line-height: 1.6;">• Suivre l'avancement de vos tickets en temps reel</td></tr>
|
|
42
|
+
<tr><td style="padding: 6px 0; font-size: 14px; color: #374151; line-height: 1.6;">• Joindre des fichiers et captures d'ecran</td></tr>
|
|
43
|
+
<tr><td style="padding: 6px 0; font-size: 14px; color: #374151; line-height: 1.6;">• Consulter l'historique complet de vos echanges</td></tr>
|
|
44
|
+
</table>`,
|
|
45
|
+
emailParagraph('<span style="font-size: 13px; color: #6b7280;">Ce lien est valable 1 heure.</span>'),
|
|
46
|
+
].join('')),
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
// Invitation email sent successfully
|
|
50
|
+
} catch (err) {
|
|
51
|
+
console.error('[support-clients] Failed to send invitation email:', err)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return doc
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ─── Collection factory ──────────────────────────────────
|
|
59
|
+
|
|
60
|
+
export function createSupportClientsCollection(slugs: CollectionSlugs): CollectionConfig {
|
|
61
|
+
return {
|
|
62
|
+
slug: slugs.supportClients,
|
|
63
|
+
labels: {
|
|
64
|
+
singular: 'Client Support',
|
|
65
|
+
plural: 'Clients Support',
|
|
66
|
+
},
|
|
67
|
+
auth: {
|
|
68
|
+
tokenExpiration: 7200, // 2 hours
|
|
69
|
+
maxLoginAttempts: 10,
|
|
70
|
+
lockTime: 300 * 1000, // 5 minutes
|
|
71
|
+
cookies: {
|
|
72
|
+
secure: process.env.NODE_ENV === 'production',
|
|
73
|
+
sameSite: 'Lax',
|
|
74
|
+
},
|
|
75
|
+
forgotPassword: {
|
|
76
|
+
generateEmailSubject: () => 'Reinitialisation de votre mot de passe',
|
|
77
|
+
generateEmailHTML: (args) => {
|
|
78
|
+
const token = args?.token || ''
|
|
79
|
+
const user = args?.user as { firstName?: string } | undefined
|
|
80
|
+
const baseUrl = process.env.NEXT_PUBLIC_SERVER_URL || ''
|
|
81
|
+
const resetUrl = `${baseUrl}/support/reset-password?token=${token}`
|
|
82
|
+
const name = user?.firstName || ''
|
|
83
|
+
|
|
84
|
+
return emailWrapper('Reinitialisation de mot de passe', [
|
|
85
|
+
emailParagraph(`Bonjour${name ? ` <strong>${escapeHtml(name)}</strong>` : ''},`),
|
|
86
|
+
emailParagraph('Vous avez demande la reinitialisation de votre mot de passe pour votre espace support.'),
|
|
87
|
+
emailParagraph('Cliquez sur le bouton ci-dessous pour definir un nouveau mot de passe :'),
|
|
88
|
+
emailButton('Definir mon mot de passe', resetUrl),
|
|
89
|
+
emailParagraph('<span style="font-size: 13px; color: #6b7280;">Ce lien est valable 1 heure. Si vous n\'avez pas effectue cette demande, vous pouvez ignorer cet email.</span>'),
|
|
90
|
+
].join(''))
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
admin: {
|
|
95
|
+
useAsTitle: 'company',
|
|
96
|
+
group: 'Support',
|
|
97
|
+
defaultColumns: ['company', 'email', 'firstName', 'lastName', 'createdAt'],
|
|
98
|
+
},
|
|
99
|
+
fields: [
|
|
100
|
+
{
|
|
101
|
+
type: 'row',
|
|
102
|
+
fields: [
|
|
103
|
+
{
|
|
104
|
+
name: 'company',
|
|
105
|
+
type: 'text',
|
|
106
|
+
required: true,
|
|
107
|
+
label: 'Entreprise',
|
|
108
|
+
admin: { width: '50%' },
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'phone',
|
|
112
|
+
type: 'text',
|
|
113
|
+
label: 'Telephone',
|
|
114
|
+
admin: { width: '50%' },
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
type: 'row',
|
|
120
|
+
fields: [
|
|
121
|
+
{
|
|
122
|
+
name: 'firstName',
|
|
123
|
+
type: 'text',
|
|
124
|
+
required: true,
|
|
125
|
+
label: 'Prenom',
|
|
126
|
+
admin: { width: '50%' },
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'lastName',
|
|
130
|
+
type: 'text',
|
|
131
|
+
required: true,
|
|
132
|
+
label: 'Nom',
|
|
133
|
+
admin: { width: '50%' },
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'twoFactorEnabled',
|
|
139
|
+
type: 'checkbox',
|
|
140
|
+
defaultValue: false,
|
|
141
|
+
label: '2FA active',
|
|
142
|
+
admin: {
|
|
143
|
+
description: 'Verification par email a chaque connexion',
|
|
144
|
+
position: 'sidebar',
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: 'twoFactorCode',
|
|
149
|
+
type: 'text',
|
|
150
|
+
admin: { hidden: true },
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'twoFactorExpiry',
|
|
154
|
+
type: 'date',
|
|
155
|
+
admin: { hidden: true },
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: 'notifyOnReply',
|
|
159
|
+
type: 'checkbox',
|
|
160
|
+
defaultValue: true,
|
|
161
|
+
label: 'Notifications reponses',
|
|
162
|
+
admin: {
|
|
163
|
+
description: 'Recevoir un email a chaque reponse du support',
|
|
164
|
+
position: 'sidebar',
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: 'notifyOnStatusChange',
|
|
169
|
+
type: 'checkbox',
|
|
170
|
+
defaultValue: true,
|
|
171
|
+
label: 'Notifications statut',
|
|
172
|
+
admin: {
|
|
173
|
+
description: 'Recevoir un email quand le statut d\'un ticket change',
|
|
174
|
+
position: 'sidebar',
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: 'notes',
|
|
179
|
+
type: 'textarea',
|
|
180
|
+
label: 'Notes internes',
|
|
181
|
+
admin: {
|
|
182
|
+
description: 'Visible uniquement par les admins',
|
|
183
|
+
position: 'sidebar',
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
hooks: {
|
|
188
|
+
afterChange: [createSendInvitationOnCreate(slugs)],
|
|
189
|
+
},
|
|
190
|
+
access: {
|
|
191
|
+
create: ({ req }) => req.user?.collection === slugs.users,
|
|
192
|
+
update: ({ req }) => {
|
|
193
|
+
if (req.user?.collection === slugs.users) return true
|
|
194
|
+
if (req.user?.collection === slugs.supportClients) {
|
|
195
|
+
return { id: { equals: req.user.id } }
|
|
196
|
+
}
|
|
197
|
+
return false
|
|
198
|
+
},
|
|
199
|
+
delete: ({ req }) => req.user?.collection === slugs.users,
|
|
200
|
+
read: ({ req }) => {
|
|
201
|
+
if (req.user?.collection === slugs.users) return true
|
|
202
|
+
if (req.user?.collection === slugs.supportClients) {
|
|
203
|
+
return { id: { equals: req.user.id } }
|
|
204
|
+
}
|
|
205
|
+
return false
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
timestamps: true,
|
|
209
|
+
}
|
|
210
|
+
}
|