@consilioweb/payload-support 2.0.1 → 4.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.
Files changed (246) hide show
  1. package/README.md +670 -402
  2. package/dist/components/RichTextEditor/index.d.ts +19 -0
  3. package/dist/components/TicketConversation/RewriteDropdown.d.ts +6 -0
  4. package/dist/components/TicketConversation/SkeletonText.d.ts +4 -0
  5. package/dist/components/TicketConversation/components/AISummaryPanel.d.ts +14 -0
  6. package/dist/components/TicketConversation/components/ActionPanels.d.ts +47 -0
  7. package/dist/components/TicketConversation/components/ActivityLog.d.ts +5 -0
  8. package/dist/components/TicketConversation/components/ClientBar.d.ts +5 -0
  9. package/dist/components/TicketConversation/components/ClientHistory.d.ts +24 -0
  10. package/dist/components/TicketConversation/components/CodeBlock.d.ts +26 -0
  11. package/dist/components/TicketConversation/components/CodeBlockInserter.d.ts +12 -0
  12. package/dist/components/TicketConversation/components/QuickActions.d.ts +32 -0
  13. package/dist/components/TicketConversation/components/TicketHeader.d.ts +19 -0
  14. package/dist/components/TicketConversation/components/TimeTrackingPanel.d.ts +24 -0
  15. package/dist/components/TicketConversation/config.d.ts +10 -0
  16. package/dist/components/TicketConversation/config.js +1 -40
  17. package/dist/components/TicketConversation/constants.d.ts +56 -0
  18. package/dist/components/TicketConversation/context.d.ts +53 -0
  19. package/dist/components/TicketConversation/hooks/useAI.d.ts +21 -0
  20. package/dist/components/TicketConversation/hooks/useDocumentIdFromUrl.d.ts +3 -0
  21. package/dist/components/TicketConversation/hooks/useFeatures.d.ts +23 -0
  22. package/dist/components/TicketConversation/hooks/useFeatures.js +61 -0
  23. package/dist/components/TicketConversation/hooks/useMessageActions.d.ts +20 -0
  24. package/dist/components/TicketConversation/hooks/useReply.d.ts +27 -0
  25. package/dist/components/TicketConversation/hooks/useTicketActions.d.ts +58 -0
  26. package/dist/components/TicketConversation/hooks/useTimeTracking.d.ts +18 -0
  27. package/dist/components/TicketConversation/hooks/useTranslation.d.ts +33 -0
  28. package/dist/components/TicketConversation/index.d.ts +4 -0
  29. package/dist/components/TicketConversation/index.js +2 -2
  30. package/dist/components/TicketConversation/types.d.ts +56 -0
  31. package/dist/components/TicketConversation/utils.d.ts +2 -0
  32. package/dist/index.cjs +940 -404
  33. package/dist/index.d.cts +119 -7
  34. package/dist/index.d.ts +119 -7
  35. package/dist/index.js +936 -405
  36. package/dist/utils/features.d.ts +123 -0
  37. package/dist/utils/features.js +147 -0
  38. package/dist/views/BillingView/client.d.ts +2 -0
  39. package/dist/views/BillingView/index.d.ts +4 -0
  40. package/dist/views/ChatView/client.d.ts +2 -0
  41. package/dist/views/ChatView/client.js +9 -9
  42. package/dist/views/ChatView/index.d.ts +4 -0
  43. package/dist/views/CrmView/client.d.ts +2 -0
  44. package/dist/views/CrmView/client.js +6 -9
  45. package/dist/views/CrmView/index.d.ts +4 -0
  46. package/dist/views/EmailTrackingView/client.d.ts +2 -0
  47. package/dist/views/EmailTrackingView/index.d.ts +4 -0
  48. package/dist/views/ImportConversationView/client.d.ts +2 -0
  49. package/dist/views/ImportConversationView/index.d.ts +4 -0
  50. package/dist/views/LogsView/client.d.ts +2 -0
  51. package/dist/views/LogsView/index.d.ts +4 -0
  52. package/dist/views/NewTicketView/client.d.ts +2 -0
  53. package/dist/views/NewTicketView/index.d.ts +4 -0
  54. package/dist/views/PendingEmailsView/client.d.ts +2 -0
  55. package/dist/views/PendingEmailsView/index.d.ts +4 -0
  56. package/dist/views/SupportDashboardView/client.d.ts +2 -0
  57. package/dist/views/SupportDashboardView/index.d.ts +4 -0
  58. package/dist/views/TicketDetailView/NextActionItem.d.ts +6 -0
  59. package/dist/views/TicketDetailView/RewriteDropdown.d.ts +7 -0
  60. package/dist/views/TicketDetailView/client.d.ts +2 -0
  61. package/dist/views/TicketDetailView/client.js +2 -2
  62. package/dist/views/TicketDetailView/constants.d.ts +13 -0
  63. package/dist/views/TicketDetailView/helpers.d.ts +2 -0
  64. package/dist/views/TicketDetailView/index.d.ts +4 -0
  65. package/dist/views/TicketDetailView/types.d.ts +40 -0
  66. package/dist/views/TicketInboxView/client.d.ts +2 -0
  67. package/dist/views/TicketInboxView/client.js +5 -5
  68. package/dist/views/TicketInboxView/index.d.ts +4 -0
  69. package/dist/views/TicketingSettingsView/client.d.ts +2 -0
  70. package/dist/views/TicketingSettingsView/client.js +5 -4
  71. package/dist/views/TicketingSettingsView/index.d.ts +4 -0
  72. package/dist/views/TimeDashboardView/client.d.ts +2 -0
  73. package/dist/views/TimeDashboardView/index.d.ts +4 -0
  74. package/dist/views/shared/AdminViewHeader.d.ts +13 -0
  75. package/dist/views/shared/ErrorBoundary.d.ts +17 -0
  76. package/dist/views/shared/Skeleton.d.ts +21 -0
  77. package/dist/views/shared/StatusPill.d.ts +8 -0
  78. package/dist/views/shared/adminTokens.d.ts +19 -0
  79. package/dist/views/shared/config.d.ts +8 -0
  80. package/dist/views/shared/config.js +1 -40
  81. package/dist/views/shared/dateLocale.d.ts +5 -0
  82. package/dist/views/shared/index.d.ts +12 -0
  83. package/dist/views/shared/index.js +2 -1
  84. package/dist/views/shared/sla.d.ts +14 -0
  85. package/dist/views.d.ts +13 -13
  86. package/package.json +16 -31
  87. package/src/collections/ChatMessages.ts +59 -2
  88. package/src/collections/ClientSummaries.ts +10 -4
  89. package/src/collections/SupportClients.ts +29 -0
  90. package/src/collections/TicketCollaborators.ts +6 -1
  91. package/src/collections/TicketMessages.ts +49 -24
  92. package/src/collections/Tickets.ts +117 -79
  93. package/src/collections/TimeEntries.ts +57 -32
  94. package/src/collections/WebhookEndpoints.ts +44 -2
  95. package/src/components/TicketConversation/config.ts +16 -80
  96. package/src/components/TicketConversation/hooks/useFeatures.ts +109 -0
  97. package/src/components/TicketConversation/index.tsx +2 -2
  98. package/src/endpoints/admin-chat.ts +3 -3
  99. package/src/endpoints/ai-agent.ts +3 -3
  100. package/src/endpoints/ai.ts +5 -4
  101. package/src/endpoints/auth-2fa.ts +16 -4
  102. package/src/endpoints/capabilities.ts +6 -6
  103. package/src/endpoints/chat.ts +7 -5
  104. package/src/endpoints/chatbot.ts +48 -2
  105. package/src/endpoints/client-intelligence.ts +6 -5
  106. package/src/endpoints/email-stats.ts +19 -3
  107. package/src/endpoints/import-conversation.ts +1 -1
  108. package/src/endpoints/index.ts +1 -1
  109. package/src/endpoints/invite-collaborator.ts +29 -3
  110. package/src/endpoints/login.ts +15 -2
  111. package/src/endpoints/oauth-google.ts +130 -8
  112. package/src/endpoints/process-scheduled.ts +13 -8
  113. package/src/endpoints/resend-notification.ts +3 -3
  114. package/src/endpoints/round-robin-config.ts +30 -20
  115. package/src/endpoints/send-reminder.ts +3 -3
  116. package/src/endpoints/settings.ts +28 -77
  117. package/src/endpoints/signature.ts +9 -2
  118. package/src/endpoints/ticket-synthesis.ts +3 -3
  119. package/src/endpoints/transfer-ticket.ts +28 -3
  120. package/src/endpoints/typing.ts +117 -14
  121. package/src/endpoints/user-prefs.ts +5 -2
  122. package/src/index.ts +6 -2
  123. package/src/plugin.ts +12 -0
  124. package/src/portal/LiveChat.tsx +33 -22
  125. package/src/portal/auth/layout.tsx +19 -1
  126. package/src/portal/auth/tickets/detail/MessageBody.tsx +88 -0
  127. package/src/portal/auth/tickets/detail/TicketPolling.tsx +5 -1
  128. package/src/portal/auth/tickets/detail/page.tsx +2 -6
  129. package/src/portal/login/page.tsx +11 -3
  130. package/src/types/lucide-react.d.ts +10 -2
  131. package/src/utils/aiAgent.ts +2 -1
  132. package/src/utils/aiProvider.ts +21 -0
  133. package/src/utils/features.ts +297 -0
  134. package/src/utils/fireWebhooks.ts +10 -1
  135. package/src/utils/generateTicketSynthesis.ts +2 -1
  136. package/src/utils/index.ts +4 -2
  137. package/src/utils/rateLimiter.ts +39 -5
  138. package/src/utils/readSettings.ts +199 -16
  139. package/src/utils/ticketAccess.ts +16 -1
  140. package/src/utils/twoFactorChallenge.ts +80 -0
  141. package/src/utils/urlSafety.ts +230 -0
  142. package/src/utils/webhookDispatcher.ts +24 -8
  143. package/src/views/ChatView/client.tsx +13 -9
  144. package/src/views/CrmView/client.tsx +10 -11
  145. package/src/views/TicketDetailView/client.tsx +2 -2
  146. package/src/views/TicketInboxView/client.tsx +10 -5
  147. package/src/views/TicketingSettingsView/client.tsx +8 -6
  148. package/src/views/shared/config.ts +14 -80
  149. package/src/views/shared/index.ts +3 -1
  150. package/dist/components/RichTextEditor/index.cjs +0 -279
  151. package/dist/components/TicketConversation/RewriteDropdown.cjs +0 -98
  152. package/dist/components/TicketConversation/SkeletonText.cjs +0 -21
  153. package/dist/components/TicketConversation/components/AISummaryPanel.cjs +0 -85
  154. package/dist/components/TicketConversation/components/ActionPanels.cjs +0 -193
  155. package/dist/components/TicketConversation/components/ActivityLog.cjs +0 -24
  156. package/dist/components/TicketConversation/components/ClientBar.cjs +0 -43
  157. package/dist/components/TicketConversation/components/ClientHistory.cjs +0 -138
  158. package/dist/components/TicketConversation/components/CodeBlock.cjs +0 -171
  159. package/dist/components/TicketConversation/components/CodeBlockInserter.cjs +0 -156
  160. package/dist/components/TicketConversation/components/QuickActions.cjs +0 -87
  161. package/dist/components/TicketConversation/components/TicketHeader.cjs +0 -93
  162. package/dist/components/TicketConversation/components/TimeTrackingPanel.cjs +0 -134
  163. package/dist/components/TicketConversation/config.cjs +0 -44
  164. package/dist/components/TicketConversation/constants.cjs +0 -127
  165. package/dist/components/TicketConversation/context.cjs +0 -13
  166. package/dist/components/TicketConversation/hooks/useAI.cjs +0 -196
  167. package/dist/components/TicketConversation/hooks/useDocumentIdFromUrl.cjs +0 -20
  168. package/dist/components/TicketConversation/hooks/useMessageActions.cjs +0 -136
  169. package/dist/components/TicketConversation/hooks/useReply.cjs +0 -188
  170. package/dist/components/TicketConversation/hooks/useTicketActions.cjs +0 -290
  171. package/dist/components/TicketConversation/hooks/useTimeTracking.cjs +0 -123
  172. package/dist/components/TicketConversation/hooks/useTranslation.cjs +0 -101
  173. package/dist/components/TicketConversation/index.cjs +0 -1114
  174. package/dist/components/TicketConversation/types.cjs +0 -2
  175. package/dist/components/TicketConversation/utils.cjs +0 -27
  176. package/dist/views/BillingView/client.cjs +0 -469
  177. package/dist/views/BillingView/index.cjs +0 -34
  178. package/dist/views/ChatView/client.cjs +0 -301
  179. package/dist/views/ChatView/index.cjs +0 -34
  180. package/dist/views/CrmView/client.cjs +0 -458
  181. package/dist/views/CrmView/index.cjs +0 -34
  182. package/dist/views/EmailTrackingView/client.cjs +0 -171
  183. package/dist/views/EmailTrackingView/index.cjs +0 -34
  184. package/dist/views/ImportConversationView/client.cjs +0 -238
  185. package/dist/views/ImportConversationView/index.cjs +0 -34
  186. package/dist/views/LogsView/client.cjs +0 -148
  187. package/dist/views/LogsView/index.cjs +0 -32
  188. package/dist/views/NewTicketView/client.cjs +0 -213
  189. package/dist/views/NewTicketView/index.cjs +0 -32
  190. package/dist/views/PendingEmailsView/client.cjs +0 -471
  191. package/dist/views/PendingEmailsView/index.cjs +0 -34
  192. package/dist/views/SupportDashboardView/client.cjs +0 -437
  193. package/dist/views/SupportDashboardView/index.cjs +0 -34
  194. package/dist/views/TicketDetailView/NextActionItem.cjs +0 -40
  195. package/dist/views/TicketDetailView/RewriteDropdown.cjs +0 -80
  196. package/dist/views/TicketDetailView/client.cjs +0 -1412
  197. package/dist/views/TicketDetailView/constants.cjs +0 -20
  198. package/dist/views/TicketDetailView/helpers.cjs +0 -23
  199. package/dist/views/TicketDetailView/index.cjs +0 -34
  200. package/dist/views/TicketDetailView/types.cjs +0 -2
  201. package/dist/views/TicketInboxView/client.cjs +0 -359
  202. package/dist/views/TicketInboxView/index.cjs +0 -32
  203. package/dist/views/TicketingSettingsView/client.cjs +0 -740
  204. package/dist/views/TicketingSettingsView/index.cjs +0 -34
  205. package/dist/views/TimeDashboardView/client.cjs +0 -180
  206. package/dist/views/TimeDashboardView/index.cjs +0 -34
  207. package/dist/views/shared/AdminViewHeader.cjs +0 -68
  208. package/dist/views/shared/ErrorBoundary.cjs +0 -55
  209. package/dist/views/shared/Skeleton.cjs +0 -77
  210. package/dist/views/shared/StatusPill.cjs +0 -47
  211. package/dist/views/shared/adminTokens.cjs +0 -34
  212. package/dist/views/shared/config.cjs +0 -44
  213. package/dist/views/shared/dateLocale.cjs +0 -5
  214. package/dist/views/shared/index.cjs +0 -72
  215. package/dist/views/shared/sla.cjs +0 -37
  216. package/dist/views.cjs +0 -86
  217. package/dist/views.d.cts +0 -13
  218. package/src/__tests__/aiSummaryRendering.test.ts +0 -21
  219. package/src/__tests__/authResponses.test.ts +0 -69
  220. package/src/__tests__/capabilities.test.ts +0 -71
  221. package/src/__tests__/generateTrackingToken.test.ts +0 -67
  222. package/src/__tests__/integration/ai-agent.test.ts +0 -46
  223. package/src/__tests__/integration/auth.test.ts +0 -43
  224. package/src/__tests__/integration/automation-rules.test.ts +0 -68
  225. package/src/__tests__/integration/buildTestPayload.ts +0 -52
  226. package/src/__tests__/integration/channels.test.ts +0 -52
  227. package/src/__tests__/integration/core-behaviors.test.ts +0 -81
  228. package/src/__tests__/integration/dashboard-volume.test.ts +0 -31
  229. package/src/__tests__/integration/digest.test.ts +0 -48
  230. package/src/__tests__/integration/invoice-pdf.test.ts +0 -30
  231. package/src/__tests__/integration/isolation.test.ts +0 -81
  232. package/src/__tests__/integration/mentions-invoice.test.ts +0 -50
  233. package/src/__tests__/integration/nps.test.ts +0 -37
  234. package/src/__tests__/integration/per-team-sla.test.ts +0 -44
  235. package/src/__tests__/integration/push.test.ts +0 -75
  236. package/src/__tests__/integration/sanitization.test.ts +0 -35
  237. package/src/__tests__/integration/sla-pause.test.ts +0 -39
  238. package/src/__tests__/integration/smoke.test.ts +0 -26
  239. package/src/__tests__/integration/snooze.test.ts +0 -68
  240. package/src/__tests__/integration/teams.test.ts +0 -38
  241. package/src/__tests__/rateLimiter.test.ts +0 -83
  242. package/src/__tests__/sanitizeHtml.test.ts +0 -165
  243. package/src/__tests__/sla.test.ts +0 -258
  244. package/src/__tests__/trackOpenEndpoint.test.ts +0 -54
  245. package/src/__tests__/translations.test.ts +0 -67
  246. package/src/__tests__/webhookSecurity.test.ts +0 -58
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ interface SkeletonProps {
3
+ width?: string | number;
4
+ height?: string | number;
5
+ borderRadius?: string;
6
+ style?: React.CSSProperties;
7
+ }
8
+ export declare function Skeleton({ width, height, borderRadius, style }: SkeletonProps): React.JSX.Element;
9
+ export declare function SkeletonText({ lines, width }: {
10
+ lines?: number;
11
+ width?: string | number;
12
+ }): React.JSX.Element;
13
+ export declare function SkeletonCard({ height }: {
14
+ height?: string;
15
+ }): React.JSX.Element;
16
+ export declare function SkeletonTable({ rows, columns }: {
17
+ rows?: number;
18
+ columns?: number;
19
+ }): React.JSX.Element;
20
+ export declare function SkeletonDashboard(): React.JSX.Element;
21
+ export {};
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ export type TicketStatusVariant = 'open' | 'pending' | 'waiting_client' | 'resolved' | 'closed' | 'escalated' | 'sla-breach';
3
+ export interface StatusPillProps {
4
+ status: TicketStatusVariant | string;
5
+ label?: string;
6
+ className?: string;
7
+ }
8
+ export declare const StatusPill: React.FC<StatusPillProps>;
@@ -0,0 +1,19 @@
1
+ import type React from 'react';
2
+ export declare const V: {
3
+ readonly text: "var(--theme-text)";
4
+ readonly textSecondary: "var(--theme-elevation-500)";
5
+ readonly bg: "var(--theme-elevation-50)";
6
+ readonly bgCard: "var(--theme-elevation-100)";
7
+ readonly border: "var(--theme-elevation-300)";
8
+ readonly blue: "#2563eb";
9
+ readonly amber: "#d97706";
10
+ readonly orange: "#ea580c";
11
+ readonly green: "#16a34a";
12
+ readonly red: "#dc2626";
13
+ readonly cyan: "#2563eb";
14
+ readonly yellow: "#d97706";
15
+ };
16
+ export declare const btnStyle: (bg: string, opts?: {
17
+ disabled?: boolean;
18
+ small?: boolean;
19
+ }) => React.CSSProperties;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Ticketing feature flags — re-export of the shared implementation.
3
+ *
4
+ * See `utils/features.ts`. This module is kept so that
5
+ * `views/shared/index.ts` and existing view imports keep resolving.
6
+ */
7
+ export { DEFAULT_TICKETING_FEATURES, DEFAULT_TICKETING_FEATURES as DEFAULT_FEATURES, FEATURES_CACHE_KEY, fetchFeatures, getFeatures, normalizeFeatures, saveFeatures, } from '../../utils/features.js';
8
+ export type { TicketingFeatures } from '../../utils/features.js';
@@ -1,40 +1 @@
1
- const DEFAULT_FEATURES = {
2
- timeTracking: true,
3
- ai: true,
4
- satisfaction: true,
5
- chat: true,
6
- emailTracking: true,
7
- canned: true,
8
- merge: true,
9
- snooze: true,
10
- externalMessages: true,
11
- clientHistory: true,
12
- activityLog: true,
13
- splitTicket: true,
14
- scheduledReplies: true,
15
- autoClose: true,
16
- autoCloseDays: 7,
17
- roundRobin: false
18
- };
19
- const STORAGE_KEY = "ticketing_features";
20
- function getFeatures() {
21
- if (typeof window === "undefined") return DEFAULT_FEATURES;
22
- try {
23
- const stored = localStorage.getItem(STORAGE_KEY);
24
- if (stored) {
25
- const parsed = JSON.parse(stored);
26
- return { ...DEFAULT_FEATURES, ...parsed };
27
- }
28
- } catch {
29
- }
30
- return DEFAULT_FEATURES;
31
- }
32
- function saveFeatures(features) {
33
- if (typeof window === "undefined") return;
34
- try {
35
- localStorage.setItem(STORAGE_KEY, JSON.stringify(features));
36
- } catch {
37
- }
38
- }
39
-
40
- export { DEFAULT_FEATURES, getFeatures, saveFeatures };
1
+ export { DEFAULT_TICKETING_FEATURES as DEFAULT_FEATURES, DEFAULT_TICKETING_FEATURES, FEATURES_CACHE_KEY, fetchFeatures, getFeatures, normalizeFeatures, saveFeatures } from '../../utils/features.js';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Default date locale constant for formatting functions.
3
+ * Extracted from user preferences at runtime when available.
4
+ */
5
+ export declare const DATE_LOCALE = "fr-FR";
@@ -0,0 +1,12 @@
1
+ export { AdminErrorBoundary } from './ErrorBoundary.js';
2
+ export { V, btnStyle } from './adminTokens.js';
3
+ export { AdminViewHeader } from './AdminViewHeader.js';
4
+ export { Skeleton, SkeletonText, SkeletonCard, SkeletonTable, SkeletonDashboard } from './Skeleton.js';
5
+ export { getFeatures, saveFeatures, fetchFeatures, normalizeFeatures, DEFAULT_FEATURES } from './config.js';
6
+ export type { TicketingFeatures } from './config.js';
7
+ export { useFeatures } from '../../components/TicketConversation/hooks/useFeatures.js';
8
+ export type { UseFeaturesResult } from '../../components/TicketConversation/hooks/useFeatures.js';
9
+ export { StatusPill } from './StatusPill.js';
10
+ export type { StatusPillProps, TicketStatusVariant } from './StatusPill.js';
11
+ export { computeSlaState, formatSlaRemaining } from './sla.js';
12
+ export type { SlaState } from './sla.js';
@@ -2,6 +2,7 @@ export { AdminErrorBoundary } from './ErrorBoundary.js';
2
2
  export { V, btnStyle } from './adminTokens.js';
3
3
  export { AdminViewHeader } from './AdminViewHeader.js';
4
4
  export { Skeleton, SkeletonCard, SkeletonDashboard, SkeletonTable, SkeletonText } from './Skeleton.js';
5
- export { DEFAULT_FEATURES, getFeatures, saveFeatures } from './config.js';
5
+ export { DEFAULT_FEATURES, fetchFeatures, getFeatures, normalizeFeatures, saveFeatures } from './config.js';
6
+ export { useFeatures } from '../../components/TicketConversation/hooks/useFeatures.js';
6
7
  export { StatusPill } from './StatusPill.js';
7
8
  export { computeSlaState, formatSlaRemaining } from './sla.js';
@@ -0,0 +1,14 @@
1
+ export type SlaState = 'ok' | 'warn' | 'breach' | 'none' | 'met';
2
+ export interface SlaInput {
3
+ slaFirstResponseDue?: string | null;
4
+ slaFirstResponseBreached?: boolean | null;
5
+ firstResponseAt?: string | null;
6
+ slaResolutionDue?: string | null;
7
+ slaResolutionBreached?: boolean | null;
8
+ }
9
+ export declare function computeSlaState(t: SlaInput, now?: Date): {
10
+ state: SlaState;
11
+ remainingMs: number | null;
12
+ due: string | null;
13
+ };
14
+ export declare function formatSlaRemaining(remainingMs: number | null): string;
package/dist/views.d.ts CHANGED
@@ -1,13 +1,13 @@
1
- export { default as TicketInboxView } from './views/TicketInboxView/index';
2
- export { default as TicketDetailView } from './views/TicketDetailView/index';
3
- export { default as SupportDashboardView } from './views/SupportDashboardView/index';
4
- export { default as NewTicketView } from './views/NewTicketView/index';
5
- export { default as TicketingSettingsView } from './views/TicketingSettingsView/index';
6
- export { default as LogsView } from './views/LogsView/index';
7
- export { default as ChatView } from './views/ChatView/index';
8
- export { default as CrmView } from './views/CrmView/index';
9
- export { default as PendingEmailsView } from './views/PendingEmailsView/index';
10
- export { default as EmailTrackingView } from './views/EmailTrackingView/index';
11
- export { default as BillingView } from './views/BillingView/index';
12
- export { default as TimeDashboardView } from './views/TimeDashboardView/index';
13
- export { default as ImportConversationView } from './views/ImportConversationView/index';
1
+ export { default as TicketInboxView } from './views/TicketInboxView/index.js';
2
+ export { default as TicketDetailView } from './views/TicketDetailView/index.js';
3
+ export { default as SupportDashboardView } from './views/SupportDashboardView/index.js';
4
+ export { default as NewTicketView } from './views/NewTicketView/index.js';
5
+ export { default as TicketingSettingsView } from './views/TicketingSettingsView/index.js';
6
+ export { default as LogsView } from './views/LogsView/index.js';
7
+ export { default as ChatView } from './views/ChatView/index.js';
8
+ export { default as CrmView } from './views/CrmView/index.js';
9
+ export { default as PendingEmailsView } from './views/PendingEmailsView/index.js';
10
+ export { default as EmailTrackingView } from './views/EmailTrackingView/index.js';
11
+ export { default as BillingView } from './views/BillingView/index.js';
12
+ export { default as TimeDashboardView } from './views/TimeDashboardView/index.js';
13
+ export { default as ImportConversationView } from './views/ImportConversationView/index.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@consilioweb/payload-support",
3
- "version": "2.0.1",
3
+ "version": "4.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",
@@ -31,14 +31,11 @@
31
31
  "import": {
32
32
  "types": "./dist/views.d.ts",
33
33
  "default": "./dist/views.js"
34
- },
35
- "require": {
36
- "types": "./dist/views.d.cts",
37
- "default": "./dist/views.cjs"
38
34
  }
39
35
  },
40
36
  "./components/TicketConversation": {
41
37
  "import": {
38
+ "types": "./dist/components/TicketConversation/index.d.ts",
42
39
  "default": "./dist/components/TicketConversation/index.js"
43
40
  }
44
41
  }
@@ -46,6 +43,9 @@
46
43
  "files": [
47
44
  "dist",
48
45
  "src",
46
+ "!src/__tests__",
47
+ "!src/**/*.test.ts",
48
+ "!src/**/*.test.tsx",
49
49
  "README.md",
50
50
  "LICENSE"
51
51
  ],
@@ -85,34 +85,19 @@
85
85
  "author": "ConsilioWEB <contact@consilioweb.fr> (https://consilioweb.fr)",
86
86
  "license": "MIT",
87
87
  "peerDependencies": {
88
- "@payloadcms/next": "^3.0.0",
89
- "@payloadcms/translations": "^3.0.0",
90
- "@payloadcms/ui": "^3.0.0",
88
+ "@payloadcms/next": "^3.37.0",
91
89
  "lucide-react": ">=0.300.0",
92
- "next": "^14.0.0 || ^15.0.0 || ^16.0.0",
93
- "payload": "^3.0.0",
94
- "react": "^18.0.0 || ^19.0.0",
95
- "react-dom": "^18.0.0 || ^19.0.0"
96
- },
97
- "peerDependenciesMeta": {
98
- "@payloadcms/ui": {
99
- "optional": true
100
- },
101
- "@payloadcms/translations": {
102
- "optional": true
103
- },
104
- "@payloadcms/next": {
105
- "optional": true
106
- },
107
- "lucide-react": {
108
- "optional": true
109
- },
110
- "next": {
111
- "optional": true
112
- }
90
+ "next": "^15.2.9 || ^16.0.0",
91
+ "payload": "^3.37.0",
92
+ "react": "^19.0.0",
93
+ "react-dom": "^19.0.0"
113
94
  },
114
95
  "engines": {
115
- "node": ">=18"
96
+ "node": ">=20.9.0"
97
+ },
98
+ "publishConfig": {
99
+ "access": "public",
100
+ "provenance": true
116
101
  },
117
102
  "devDependencies": {
118
103
  "@payloadcms/db-sqlite": "^3.86.0",
@@ -142,7 +127,7 @@
142
127
  "web-push": "^3.6.7"
143
128
  },
144
129
  "scripts": {
145
- "build": "tsup",
130
+ "build": "tsup && tsc -p tsconfig.types.json && node scripts/copy-subpath-types.mjs",
146
131
  "typecheck": "tsc --noEmit",
147
132
  "test": "vitest run",
148
133
  "test:watch": "vitest",
@@ -1,5 +1,56 @@
1
- import type { CollectionConfig } from 'payload'
1
+ import { APIError } from 'payload'
2
+ import type { CollectionBeforeChangeHook, CollectionConfig } from 'payload'
2
3
  import type { CollectionSlugs } from '../utils/slugs'
4
+ import { dbFind } from '../utils/db'
5
+
6
+ /**
7
+ * Cross-tenant write guard for support-clients.
8
+ *
9
+ * `access.create` used to be `!!req.user`, which any principal of ANY auth
10
+ * collection satisfies — including a front-office member of the host app. With
11
+ * no hook validating the row, an attacker could POST a message attributed to
12
+ * someone else's `client` and flagged `senderType: 'agent'` with an arbitrary
13
+ * `agent` relation: the agent console and the targeted client both render it as
14
+ * a genuine conversation (phishing over a trusted channel).
15
+ *
16
+ * Mirrors `createRestrictClientTicketTarget` on ticket-messages: the client's
17
+ * own identity is FORCED onto the row instead of being trusted from the body.
18
+ * Internal writers (endpoints/chat.ts, endpoints/admin-chat.ts,
19
+ * collections/TicketMessages.ts) all write with `overrideAccess: true` and carry
20
+ * no support-client user, so they are untouched.
21
+ */
22
+ function createRestrictClientChatWrite(slugs: CollectionSlugs): CollectionBeforeChangeHook {
23
+ return async ({ data, req }) => {
24
+ if (req.user?.collection !== slugs.supportClients) return data
25
+ data.client = req.user.id
26
+ data.senderType = 'client'
27
+ delete data.agent
28
+
29
+ // …and the SESSION has to be theirs too. Forcing `client` alone still let a
30
+ // client drop a line INTO someone else's conversation: the agent console
31
+ // loads a thread by session id with `overrideAccess: true`
32
+ // (endpoints/admin-chat.ts), so the injected message surfaces in the target's
33
+ // thread even though `access.read` hides it from the target themselves.
34
+ // Session ids are 16 random bytes, so this is depth rather than a reachable
35
+ // hole — and it costs one indexed lookup on a path no internal writer takes
36
+ // (chat.ts / admin-chat.ts write without a support-client `req`).
37
+ const session = typeof data.session === 'string' ? data.session : null
38
+ if (!session) return data
39
+
40
+ const existing = await dbFind(req.payload, slugs.chatMessages, {
41
+ where: { session: { equals: session } },
42
+ limit: 1,
43
+ depth: 0,
44
+ overrideAccess: true,
45
+ })
46
+ const owner = existing.docs[0]?.client
47
+ const ownerId = owner && typeof owner === 'object' ? (owner as { id?: unknown }).id : owner
48
+ if (ownerId !== undefined && ownerId !== null && String(ownerId) !== String(req.user.id)) {
49
+ throw new APIError('Session inaccessible.', 403)
50
+ }
51
+ return data
52
+ }
53
+ }
3
54
 
4
55
  // ─── Collection factory ──────────────────────────────────
5
56
 
@@ -24,7 +75,10 @@ export function createChatMessagesCollection(slugs: CollectionSlugs): Collection
24
75
  }
25
76
  return false
26
77
  },
27
- create: ({ req }) => !!req.user,
78
+ // Staff and support-clients only — NOT "any authenticated principal":
79
+ // a user of any other auth collection of the host app satisfied `!!req.user`.
80
+ create: ({ req }) =>
81
+ req.user?.collection === slugs.users || req.user?.collection === slugs.supportClients,
28
82
  update: ({ req }) => req.user?.collection === slugs.users,
29
83
  delete: ({ req }) => req.user?.collection === slugs.users,
30
84
  },
@@ -93,6 +147,9 @@ export function createChatMessagesCollection(slugs: CollectionSlugs): Collection
93
147
  },
94
148
  },
95
149
  ],
150
+ hooks: {
151
+ beforeChange: [createRestrictClientChatWrite(slugs)],
152
+ },
96
153
  timestamps: true,
97
154
  }
98
155
  }
@@ -122,11 +122,17 @@ export function createClientSummariesCollection(slugs: CollectionSlugs): Collect
122
122
  admin: { readOnly: true },
123
123
  },
124
124
  ],
125
+ // Staff-only, on the SAME source of truth as every other collection and as
126
+ // `requireAdmin`: `slugs.users`. The literal `'users'` this used to compare
127
+ // against is the DEFAULT slug, not the configured one — on a host app whose
128
+ // staff collection is renamed (`collectionSlugs.users: 'admins'`) it named
129
+ // the front-office collection instead, opening read/create/update/delete on
130
+ // AI-generated client intelligence to it while locking the real agents out.
125
131
  access: {
126
- create: ({ req }) => req.user?.collection === 'users',
127
- read: ({ req }) => req.user?.collection === 'users',
128
- update: ({ req }) => req.user?.collection === 'users',
129
- delete: ({ req }) => req.user?.collection === 'users',
132
+ create: ({ req }) => req.user?.collection === slugs.users,
133
+ read: ({ req }) => req.user?.collection === slugs.users,
134
+ update: ({ req }) => req.user?.collection === slugs.users,
135
+ delete: ({ req }) => req.user?.collection === slugs.users,
130
136
  },
131
137
  timestamps: true,
132
138
  }
@@ -91,12 +91,33 @@ function createEnforce2FA(slugs: CollectionSlugs): CollectionBeforeLoginHook {
91
91
  }
92
92
  }
93
93
 
94
+ /**
95
+ * Field-level write guard for the commercial / security fields.
96
+ *
97
+ * `access.update` on this collection intentionally lets a support-client PATCH
98
+ * their OWN document (the portal profile page writes firstName, phone, the
99
+ * notification toggles and the 2FA switch). Payload persists every other field of
100
+ * that same PATCH too, so without a field guard a client could self-promote
101
+ * (`tier`), rewrite the sales notes, or — worst — set `googleId`, which the Google
102
+ * OAuth callback resolves BEFORE the email and would hand them another account.
103
+ *
104
+ * Internal writers (2FA endpoints, OAuth linking, beforeLogin) all go through
105
+ * `overrideAccess: true`, which bypasses field access, so they are unaffected.
106
+ * `twoFactorEnabled` is deliberately NOT locked: it is a legitimate self-service
107
+ * toggle on the portal profile page.
108
+ */
109
+ function createAdminOnlyFieldUpdate(slugs: CollectionSlugs) {
110
+ return ({ req }: { req: { user?: { collection?: string } | null } }) =>
111
+ req.user?.collection === slugs.users
112
+ }
113
+
94
114
  // ─── Collection factory ──────────────────────────────────
95
115
 
96
116
  export function createSupportClientsCollection(
97
117
  slugs: CollectionSlugs,
98
118
  options?: { capabilities?: SupportCapabilities },
99
119
  ): CollectionConfig {
120
+ const adminOnlyFieldUpdate = createAdminOnlyFieldUpdate(slugs)
100
121
  return {
101
122
  slug: slugs.supportClients,
102
123
  labels: {
@@ -226,6 +247,7 @@ export function createSupportClientsCollection(
226
247
  access: {
227
248
  // Admin-only: never serialize commercial notes onto the client's own doc.
228
249
  read: ({ req }) => req.user?.collection === slugs.users,
250
+ update: adminOnlyFieldUpdate,
229
251
  },
230
252
  admin: {
231
253
  description: 'Notes commerciales (visible uniquement par les admins)',
@@ -235,6 +257,7 @@ export function createSupportClientsCollection(
235
257
  name: 'tier',
236
258
  type: 'select',
237
259
  label: 'Plan',
260
+ access: { update: adminOnlyFieldUpdate },
238
261
  options: [
239
262
  { label: 'Free', value: 'free' },
240
263
  { label: 'Basic', value: 'basic' },
@@ -248,6 +271,7 @@ export function createSupportClientsCollection(
248
271
  type: 'relationship',
249
272
  relationTo: slugs.users,
250
273
  label: 'Agent referent',
274
+ access: { update: adminOnlyFieldUpdate },
251
275
  admin: { position: 'sidebar' },
252
276
  },
253
277
  {
@@ -263,17 +287,20 @@ export function createSupportClientsCollection(
263
287
  {
264
288
  name: 'twoFactorCode',
265
289
  type: 'text',
290
+ access: { update: adminOnlyFieldUpdate },
266
291
  admin: { hidden: true },
267
292
  },
268
293
  {
269
294
  name: 'twoFactorExpiry',
270
295
  type: 'date',
296
+ access: { update: adminOnlyFieldUpdate },
271
297
  admin: { hidden: true },
272
298
  },
273
299
  {
274
300
  // Set by /support/2fa "verify"; gates login via the beforeLogin hook.
275
301
  name: 'twoFactorVerifiedAt',
276
302
  type: 'date',
303
+ access: { update: adminOnlyFieldUpdate },
277
304
  admin: { hidden: true },
278
305
  },
279
306
  {
@@ -281,6 +308,7 @@ export function createSupportClientsCollection(
281
308
  name: 'googleId',
282
309
  type: 'text',
283
310
  index: true,
311
+ access: { update: adminOnlyFieldUpdate },
284
312
  admin: { hidden: true },
285
313
  },
286
314
  {
@@ -352,6 +380,7 @@ export function createSupportClientsCollection(
352
380
  access: {
353
381
  // Admin-only: never serialize internal notes onto the client's own doc.
354
382
  read: ({ req }) => req.user?.collection === slugs.users,
383
+ update: adminOnlyFieldUpdate,
355
384
  },
356
385
  admin: {
357
386
  description: 'Visible uniquement par les admins',
@@ -32,7 +32,12 @@ export function createTicketCollaboratorsCollection(slugs: CollectionSlugs): Col
32
32
  }
33
33
  return false
34
34
  },
35
- create: ({ req }) => !!req.user,
35
+ // Staff only. This collection is the source of truth for
36
+ // resolveAccessibleTicketIds(), which widens `read` on tickets, messages
37
+ // and the activity log — so letting any authenticated principal POST here
38
+ // let a support client grant themselves access to any ticket by id.
39
+ // The /invite endpoint is unaffected: it writes with overrideAccess: true.
40
+ create: ({ req }) => req.user?.collection === slugs.users,
36
41
  update: () => false,
37
42
  delete: ({ req }) => req.user?.collection === slugs.users,
38
43
  },
@@ -1,8 +1,8 @@
1
1
  import type { CollectionConfig, CollectionBeforeChangeHook, CollectionAfterChangeHook, Where } from 'payload'
2
+ import { APIError } from 'payload'
2
3
  import type { CollectionSlugs } from '../utils/slugs'
3
4
  import type { SupportCapabilities } from '../types'
4
5
  import { escapeHtml, emailWrapper, emailButton, emailQuote, emailParagraph, emailRichContent, emailTrackingPixel } from '../utils/emailTemplate'
5
- import { fireWebhooks } from '../utils/fireWebhooks'
6
6
  import { createAdminNotification } from '../utils/adminNotification'
7
7
  import { dispatchWebhook } from '../utils/webhookDispatcher'
8
8
  import { readSupportSettings } from '../utils/readSettings'
@@ -13,6 +13,41 @@ import { queueClientNotification } from '../utils/notificationQueue'
13
13
  import { sendPushToUser } from '../utils/push'
14
14
  import { dbFind, dbCreate, dbFindByID } from '../utils/db'
15
15
 
16
+ /**
17
+ * Cross-tenant write guard for support-clients.
18
+ *
19
+ * `access.create` only checks WHICH collection the actor belongs to, so any
20
+ * authenticated support-client could POST a message onto an arbitrary ticket id.
21
+ * That is not just a data-integrity issue: the `afterChange` notification hooks
22
+ * would then mail the injected body to the real ticket owner from the legitimate
23
+ * support address (phishing over a trusted channel).
24
+ *
25
+ * We reuse `resolveAccessibleTicketIds()` — the same boundary `access.read` uses —
26
+ * so read and write scopes cannot drift apart. Staff writes and every internal
27
+ * `overrideAccess` write are untouched: they carry no `support-clients` user.
28
+ */
29
+ function createRestrictClientTicketTarget(slugs: CollectionSlugs): CollectionBeforeChangeHook {
30
+ return async ({ data, operation, req, originalDoc }) => {
31
+ if (req.user?.collection !== slugs.supportClients) return data
32
+
33
+ const raw = (data as { ticket?: unknown }).ticket
34
+ ?? (operation === 'update' ? (originalDoc as { ticket?: unknown } | undefined)?.ticket : undefined)
35
+ const targetId = raw && typeof raw === 'object' ? (raw as { id?: unknown }).id : raw
36
+ if (targetId === undefined || targetId === null || targetId === '') {
37
+ throw new APIError('Ticket cible requis.', 400)
38
+ }
39
+
40
+ // 'write' scope: a collaborator invited as `viewer` may READ the thread but
41
+ // must not post into it — the invitation email promises exactly that, and a
42
+ // message from a viewer would fan out the whole notification chain.
43
+ const accessible = await resolveAccessibleTicketIds(req.payload, slugs, req.user.id, 'write')
44
+ if (!accessible.some((id) => String(id) === String(targetId))) {
45
+ throw new APIError('Ticket inaccessible.', 403)
46
+ }
47
+ return data
48
+ }
49
+ }
50
+
16
51
  function createAssignAuthor(slugs: CollectionSlugs): CollectionBeforeChangeHook {
17
52
  return async ({ data, operation, req }) => {
18
53
  if (operation === 'create' && req.user?.collection === slugs.supportClients) {
@@ -300,26 +335,12 @@ function createNotifyAdminOnClientMessage(slugs: CollectionSlugs, notificationSl
300
335
  }
301
336
  }
302
337
 
303
- function createFireMessageWebhooks(slugs: CollectionSlugs): CollectionAfterChangeHook {
304
- return async ({ doc, operation, req }) => {
305
- if (operation !== 'create') return doc
306
- // Don't fire for scheduled messages that haven't been sent yet
307
- if (doc.scheduledAt && !doc.scheduledSent) return doc
308
- // Don't fire for internal notes
309
- if (doc.isInternal) return doc
310
-
311
- const ticketId = typeof doc.ticket === 'object' ? doc.ticket.id : doc.ticket
312
- fireWebhooks(req.payload, slugs, 'ticket_replied', {
313
- ticketId,
314
- messageId: doc.id,
315
- authorType: doc.authorType,
316
- body: doc.body?.length > 500 ? doc.body.slice(0, 500) + '...' : doc.body,
317
- })
318
-
319
- return doc
320
- }
321
- }
322
-
338
+ /**
339
+ * Single outbound-webhook dispatcher for replies. See the matching comment in
340
+ * Tickets.ts: a second, unsigned dispatcher used to fire on this exact hook with
341
+ * the same guards, doubling every delivery. The `body` excerpt of the legacy
342
+ * payload is carried over so existing consumers do not regress.
343
+ */
323
344
  function createDispatchWebhookOnReply(slugs: CollectionSlugs): CollectionAfterChangeHook {
324
345
  return async ({ doc, operation, req }) => {
325
346
  if (operation !== 'create') return doc
@@ -328,7 +349,12 @@ function createDispatchWebhookOnReply(slugs: CollectionSlugs): CollectionAfterCh
328
349
 
329
350
  const ticketId = typeof doc.ticket === 'object' ? doc.ticket.id : doc.ticket
330
351
  dispatchWebhook(
331
- { ticketId, messageId: doc.id, authorType: doc.authorType },
352
+ {
353
+ ticketId,
354
+ messageId: doc.id,
355
+ authorType: doc.authorType,
356
+ body: doc.body?.length > 500 ? doc.body.slice(0, 500) + '...' : doc.body,
357
+ },
332
358
  'ticket_replied',
333
359
  req.payload,
334
360
  slugs,
@@ -534,7 +560,7 @@ export function createTicketMessagesCollection(slugs: CollectionSlugs, options?:
534
560
  ] : []),
535
561
  ],
536
562
  hooks: {
537
- beforeChange: [createSanitizeMessageHtml(), createResolveMentions(slugs), createAssignAuthor(slugs)],
563
+ beforeChange: [createRestrictClientTicketTarget(slugs), createSanitizeMessageHtml(), createResolveMentions(slugs), createAssignAuthor(slugs)],
538
564
  afterChange: [
539
565
  ...(options?.capabilities?.sms ? [createSmsNotification(slugs, options.capabilities.sms)] : []),
540
566
  createAutoUpdateStatus(slugs),
@@ -543,7 +569,6 @@ export function createTicketMessagesCollection(slugs: CollectionSlugs, options?:
543
569
  createCheckSlaOnReply(slugs, notificationSlug),
544
570
  createSyncTicketReplyToChat(slugs),
545
571
  createNotifyAdminOnClientMessage(slugs, notificationSlug),
546
- createFireMessageWebhooks(slugs),
547
572
  createDispatchWebhookOnReply(slugs),
548
573
  createNotifyMentions(slugs),
549
574
  ...(options?.capabilities?.threadCleanup ? [createThreadCleanup(options.capabilities.threadCleanup)] : []),