@consilioweb/payload-support 2.0.1 → 3.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 (215) hide show
  1. package/README.md +636 -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 +392 -320
  33. package/dist/index.d.cts +100 -3
  34. package/dist/index.d.ts +100 -3
  35. package/dist/index.js +388 -321
  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/SupportClients.ts +29 -0
  88. package/src/collections/TicketCollaborators.ts +6 -1
  89. package/src/collections/TicketMessages.ts +46 -24
  90. package/src/collections/Tickets.ts +117 -79
  91. package/src/collections/TimeEntries.ts +57 -32
  92. package/src/components/TicketConversation/config.ts +16 -80
  93. package/src/components/TicketConversation/hooks/useFeatures.ts +109 -0
  94. package/src/components/TicketConversation/index.tsx +2 -2
  95. package/src/endpoints/ai.ts +2 -1
  96. package/src/endpoints/client-intelligence.ts +2 -1
  97. package/src/endpoints/process-scheduled.ts +13 -8
  98. package/src/endpoints/round-robin-config.ts +30 -20
  99. package/src/endpoints/settings.ts +28 -77
  100. package/src/index.ts +6 -2
  101. package/src/portal/LiveChat.tsx +33 -22
  102. package/src/portal/auth/tickets/detail/TicketPolling.tsx +5 -1
  103. package/src/types/lucide-react.d.ts +10 -2
  104. package/src/utils/aiAgent.ts +2 -1
  105. package/src/utils/aiProvider.ts +21 -0
  106. package/src/utils/features.ts +297 -0
  107. package/src/utils/fireWebhooks.ts +6 -0
  108. package/src/utils/generateTicketSynthesis.ts +2 -1
  109. package/src/utils/index.ts +4 -2
  110. package/src/utils/readSettings.ts +82 -9
  111. package/src/utils/webhookDispatcher.ts +19 -7
  112. package/src/views/ChatView/client.tsx +13 -9
  113. package/src/views/CrmView/client.tsx +10 -11
  114. package/src/views/TicketDetailView/client.tsx +2 -2
  115. package/src/views/TicketInboxView/client.tsx +10 -5
  116. package/src/views/TicketingSettingsView/client.tsx +8 -6
  117. package/src/views/shared/config.ts +14 -80
  118. package/src/views/shared/index.ts +3 -1
  119. package/dist/components/RichTextEditor/index.cjs +0 -279
  120. package/dist/components/TicketConversation/RewriteDropdown.cjs +0 -98
  121. package/dist/components/TicketConversation/SkeletonText.cjs +0 -21
  122. package/dist/components/TicketConversation/components/AISummaryPanel.cjs +0 -85
  123. package/dist/components/TicketConversation/components/ActionPanels.cjs +0 -193
  124. package/dist/components/TicketConversation/components/ActivityLog.cjs +0 -24
  125. package/dist/components/TicketConversation/components/ClientBar.cjs +0 -43
  126. package/dist/components/TicketConversation/components/ClientHistory.cjs +0 -138
  127. package/dist/components/TicketConversation/components/CodeBlock.cjs +0 -171
  128. package/dist/components/TicketConversation/components/CodeBlockInserter.cjs +0 -156
  129. package/dist/components/TicketConversation/components/QuickActions.cjs +0 -87
  130. package/dist/components/TicketConversation/components/TicketHeader.cjs +0 -93
  131. package/dist/components/TicketConversation/components/TimeTrackingPanel.cjs +0 -134
  132. package/dist/components/TicketConversation/config.cjs +0 -44
  133. package/dist/components/TicketConversation/constants.cjs +0 -127
  134. package/dist/components/TicketConversation/context.cjs +0 -13
  135. package/dist/components/TicketConversation/hooks/useAI.cjs +0 -196
  136. package/dist/components/TicketConversation/hooks/useDocumentIdFromUrl.cjs +0 -20
  137. package/dist/components/TicketConversation/hooks/useMessageActions.cjs +0 -136
  138. package/dist/components/TicketConversation/hooks/useReply.cjs +0 -188
  139. package/dist/components/TicketConversation/hooks/useTicketActions.cjs +0 -290
  140. package/dist/components/TicketConversation/hooks/useTimeTracking.cjs +0 -123
  141. package/dist/components/TicketConversation/hooks/useTranslation.cjs +0 -101
  142. package/dist/components/TicketConversation/index.cjs +0 -1114
  143. package/dist/components/TicketConversation/types.cjs +0 -2
  144. package/dist/components/TicketConversation/utils.cjs +0 -27
  145. package/dist/views/BillingView/client.cjs +0 -469
  146. package/dist/views/BillingView/index.cjs +0 -34
  147. package/dist/views/ChatView/client.cjs +0 -301
  148. package/dist/views/ChatView/index.cjs +0 -34
  149. package/dist/views/CrmView/client.cjs +0 -458
  150. package/dist/views/CrmView/index.cjs +0 -34
  151. package/dist/views/EmailTrackingView/client.cjs +0 -171
  152. package/dist/views/EmailTrackingView/index.cjs +0 -34
  153. package/dist/views/ImportConversationView/client.cjs +0 -238
  154. package/dist/views/ImportConversationView/index.cjs +0 -34
  155. package/dist/views/LogsView/client.cjs +0 -148
  156. package/dist/views/LogsView/index.cjs +0 -32
  157. package/dist/views/NewTicketView/client.cjs +0 -213
  158. package/dist/views/NewTicketView/index.cjs +0 -32
  159. package/dist/views/PendingEmailsView/client.cjs +0 -471
  160. package/dist/views/PendingEmailsView/index.cjs +0 -34
  161. package/dist/views/SupportDashboardView/client.cjs +0 -437
  162. package/dist/views/SupportDashboardView/index.cjs +0 -34
  163. package/dist/views/TicketDetailView/NextActionItem.cjs +0 -40
  164. package/dist/views/TicketDetailView/RewriteDropdown.cjs +0 -80
  165. package/dist/views/TicketDetailView/client.cjs +0 -1412
  166. package/dist/views/TicketDetailView/constants.cjs +0 -20
  167. package/dist/views/TicketDetailView/helpers.cjs +0 -23
  168. package/dist/views/TicketDetailView/index.cjs +0 -34
  169. package/dist/views/TicketDetailView/types.cjs +0 -2
  170. package/dist/views/TicketInboxView/client.cjs +0 -359
  171. package/dist/views/TicketInboxView/index.cjs +0 -32
  172. package/dist/views/TicketingSettingsView/client.cjs +0 -740
  173. package/dist/views/TicketingSettingsView/index.cjs +0 -34
  174. package/dist/views/TimeDashboardView/client.cjs +0 -180
  175. package/dist/views/TimeDashboardView/index.cjs +0 -34
  176. package/dist/views/shared/AdminViewHeader.cjs +0 -68
  177. package/dist/views/shared/ErrorBoundary.cjs +0 -55
  178. package/dist/views/shared/Skeleton.cjs +0 -77
  179. package/dist/views/shared/StatusPill.cjs +0 -47
  180. package/dist/views/shared/adminTokens.cjs +0 -34
  181. package/dist/views/shared/config.cjs +0 -44
  182. package/dist/views/shared/dateLocale.cjs +0 -5
  183. package/dist/views/shared/index.cjs +0 -72
  184. package/dist/views/shared/sla.cjs +0 -37
  185. package/dist/views.cjs +0 -86
  186. package/dist/views.d.cts +0 -13
  187. package/src/__tests__/aiSummaryRendering.test.ts +0 -21
  188. package/src/__tests__/authResponses.test.ts +0 -69
  189. package/src/__tests__/capabilities.test.ts +0 -71
  190. package/src/__tests__/generateTrackingToken.test.ts +0 -67
  191. package/src/__tests__/integration/ai-agent.test.ts +0 -46
  192. package/src/__tests__/integration/auth.test.ts +0 -43
  193. package/src/__tests__/integration/automation-rules.test.ts +0 -68
  194. package/src/__tests__/integration/buildTestPayload.ts +0 -52
  195. package/src/__tests__/integration/channels.test.ts +0 -52
  196. package/src/__tests__/integration/core-behaviors.test.ts +0 -81
  197. package/src/__tests__/integration/dashboard-volume.test.ts +0 -31
  198. package/src/__tests__/integration/digest.test.ts +0 -48
  199. package/src/__tests__/integration/invoice-pdf.test.ts +0 -30
  200. package/src/__tests__/integration/isolation.test.ts +0 -81
  201. package/src/__tests__/integration/mentions-invoice.test.ts +0 -50
  202. package/src/__tests__/integration/nps.test.ts +0 -37
  203. package/src/__tests__/integration/per-team-sla.test.ts +0 -44
  204. package/src/__tests__/integration/push.test.ts +0 -75
  205. package/src/__tests__/integration/sanitization.test.ts +0 -35
  206. package/src/__tests__/integration/sla-pause.test.ts +0 -39
  207. package/src/__tests__/integration/smoke.test.ts +0 -26
  208. package/src/__tests__/integration/snooze.test.ts +0 -68
  209. package/src/__tests__/integration/teams.test.ts +0 -38
  210. package/src/__tests__/rateLimiter.test.ts +0 -83
  211. package/src/__tests__/sanitizeHtml.test.ts +0 -165
  212. package/src/__tests__/sla.test.ts +0 -258
  213. package/src/__tests__/trackOpenEndpoint.test.ts +0 -54
  214. package/src/__tests__/translations.test.ts +0 -67
  215. 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": "3.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",
@@ -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,38 @@ 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
+ const accessible = await resolveAccessibleTicketIds(req.payload, slugs, req.user.id)
41
+ if (!accessible.some((id) => String(id) === String(targetId))) {
42
+ throw new APIError('Ticket inaccessible.', 403)
43
+ }
44
+ return data
45
+ }
46
+ }
47
+
16
48
  function createAssignAuthor(slugs: CollectionSlugs): CollectionBeforeChangeHook {
17
49
  return async ({ data, operation, req }) => {
18
50
  if (operation === 'create' && req.user?.collection === slugs.supportClients) {
@@ -300,26 +332,12 @@ function createNotifyAdminOnClientMessage(slugs: CollectionSlugs, notificationSl
300
332
  }
301
333
  }
302
334
 
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
-
335
+ /**
336
+ * Single outbound-webhook dispatcher for replies. See the matching comment in
337
+ * Tickets.ts: a second, unsigned dispatcher used to fire on this exact hook with
338
+ * the same guards, doubling every delivery. The `body` excerpt of the legacy
339
+ * payload is carried over so existing consumers do not regress.
340
+ */
323
341
  function createDispatchWebhookOnReply(slugs: CollectionSlugs): CollectionAfterChangeHook {
324
342
  return async ({ doc, operation, req }) => {
325
343
  if (operation !== 'create') return doc
@@ -328,7 +346,12 @@ function createDispatchWebhookOnReply(slugs: CollectionSlugs): CollectionAfterCh
328
346
 
329
347
  const ticketId = typeof doc.ticket === 'object' ? doc.ticket.id : doc.ticket
330
348
  dispatchWebhook(
331
- { ticketId, messageId: doc.id, authorType: doc.authorType },
349
+ {
350
+ ticketId,
351
+ messageId: doc.id,
352
+ authorType: doc.authorType,
353
+ body: doc.body?.length > 500 ? doc.body.slice(0, 500) + '...' : doc.body,
354
+ },
332
355
  'ticket_replied',
333
356
  req.payload,
334
357
  slugs,
@@ -534,7 +557,7 @@ export function createTicketMessagesCollection(slugs: CollectionSlugs, options?:
534
557
  ] : []),
535
558
  ],
536
559
  hooks: {
537
- beforeChange: [createSanitizeMessageHtml(), createResolveMentions(slugs), createAssignAuthor(slugs)],
560
+ beforeChange: [createRestrictClientTicketTarget(slugs), createSanitizeMessageHtml(), createResolveMentions(slugs), createAssignAuthor(slugs)],
538
561
  afterChange: [
539
562
  ...(options?.capabilities?.sms ? [createSmsNotification(slugs, options.capabilities.sms)] : []),
540
563
  createAutoUpdateStatus(slugs),
@@ -543,7 +566,6 @@ export function createTicketMessagesCollection(slugs: CollectionSlugs, options?:
543
566
  createCheckSlaOnReply(slugs, notificationSlug),
544
567
  createSyncTicketReplyToChat(slugs),
545
568
  createNotifyAdminOnClientMessage(slugs, notificationSlug),
546
- createFireMessageWebhooks(slugs),
547
569
  createDispatchWebhookOnReply(slugs),
548
570
  createNotifyMentions(slugs),
549
571
  ...(options?.capabilities?.threadCleanup ? [createThreadCleanup(options.capabilities.threadCleanup)] : []),