@consilioweb/payload-support 2.0.0 → 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 (222) hide show
  1. package/README.md +656 -355
  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/hooks/useTranslation.js +22 -3
  29. package/dist/components/TicketConversation/index.d.ts +4 -0
  30. package/dist/components/TicketConversation/index.js +2 -2
  31. package/dist/components/TicketConversation/types.d.ts +56 -0
  32. package/dist/components/TicketConversation/utils.d.ts +2 -0
  33. package/dist/index.cjs +392 -320
  34. package/dist/index.d.cts +100 -3
  35. package/dist/index.d.ts +100 -3
  36. package/dist/index.js +388 -321
  37. package/dist/utils/features.d.ts +123 -0
  38. package/dist/utils/features.js +147 -0
  39. package/dist/views/BillingView/client.d.ts +2 -0
  40. package/dist/views/BillingView/index.d.ts +4 -0
  41. package/dist/views/ChatView/client.d.ts +2 -0
  42. package/dist/views/ChatView/client.js +9 -9
  43. package/dist/views/ChatView/index.d.ts +4 -0
  44. package/dist/views/CrmView/client.d.ts +2 -0
  45. package/dist/views/CrmView/client.js +6 -9
  46. package/dist/views/CrmView/index.d.ts +4 -0
  47. package/dist/views/EmailTrackingView/client.d.ts +2 -0
  48. package/dist/views/EmailTrackingView/index.d.ts +4 -0
  49. package/dist/views/ImportConversationView/client.d.ts +2 -0
  50. package/dist/views/ImportConversationView/index.d.ts +4 -0
  51. package/dist/views/LogsView/client.d.ts +2 -0
  52. package/dist/views/LogsView/index.d.ts +4 -0
  53. package/dist/views/NewTicketView/client.d.ts +2 -0
  54. package/dist/views/NewTicketView/index.d.ts +4 -0
  55. package/dist/views/PendingEmailsView/client.d.ts +2 -0
  56. package/dist/views/PendingEmailsView/index.d.ts +4 -0
  57. package/dist/views/SupportDashboardView/client.d.ts +2 -0
  58. package/dist/views/SupportDashboardView/client.js +1 -1
  59. package/dist/views/SupportDashboardView/index.d.ts +4 -0
  60. package/dist/views/TicketDetailView/NextActionItem.d.ts +6 -0
  61. package/dist/views/TicketDetailView/RewriteDropdown.d.ts +7 -0
  62. package/dist/views/TicketDetailView/client.d.ts +2 -0
  63. package/dist/views/TicketDetailView/client.js +2 -2
  64. package/dist/views/TicketDetailView/constants.d.ts +13 -0
  65. package/dist/views/TicketDetailView/helpers.d.ts +2 -0
  66. package/dist/views/TicketDetailView/index.d.ts +4 -0
  67. package/dist/views/TicketDetailView/types.d.ts +40 -0
  68. package/dist/views/TicketInboxView/client.d.ts +2 -0
  69. package/dist/views/TicketInboxView/client.js +5 -5
  70. package/dist/views/TicketInboxView/index.d.ts +4 -0
  71. package/dist/views/TicketingSettingsView/client.d.ts +2 -0
  72. package/dist/views/TicketingSettingsView/client.js +6 -5
  73. package/dist/views/TicketingSettingsView/index.d.ts +4 -0
  74. package/dist/views/TimeDashboardView/client.d.ts +2 -0
  75. package/dist/views/TimeDashboardView/index.d.ts +4 -0
  76. package/dist/views/shared/AdminViewHeader.d.ts +13 -0
  77. package/dist/views/shared/ErrorBoundary.d.ts +17 -0
  78. package/dist/views/shared/Skeleton.d.ts +21 -0
  79. package/dist/views/shared/StatusPill.d.ts +8 -0
  80. package/dist/views/shared/adminTokens.d.ts +19 -0
  81. package/dist/views/shared/config.d.ts +8 -0
  82. package/dist/views/shared/config.js +1 -40
  83. package/dist/views/shared/dateLocale.d.ts +5 -0
  84. package/dist/views/shared/index.d.ts +12 -0
  85. package/dist/views/shared/index.js +2 -1
  86. package/dist/views/shared/locales/en.json +708 -0
  87. package/dist/views/shared/locales/fr.json +708 -0
  88. package/dist/views/shared/sla.d.ts +14 -0
  89. package/dist/views.d.ts +13 -13
  90. package/package.json +26 -41
  91. package/src/collections/SupportClients.ts +29 -0
  92. package/src/collections/TicketCollaborators.ts +6 -1
  93. package/src/collections/TicketMessages.ts +46 -24
  94. package/src/collections/Tickets.ts +117 -79
  95. package/src/collections/TimeEntries.ts +57 -32
  96. package/src/components/TicketConversation/config.ts +16 -80
  97. package/src/components/TicketConversation/hooks/useFeatures.ts +109 -0
  98. package/src/components/TicketConversation/hooks/useTranslation.ts +38 -4
  99. package/src/components/TicketConversation/index.tsx +2 -2
  100. package/src/endpoints/ai.ts +2 -1
  101. package/src/endpoints/client-intelligence.ts +2 -1
  102. package/src/endpoints/process-scheduled.ts +13 -8
  103. package/src/endpoints/round-robin-config.ts +30 -20
  104. package/src/endpoints/settings.ts +28 -77
  105. package/src/index.ts +6 -2
  106. package/src/portal/LiveChat.tsx +33 -22
  107. package/src/portal/auth/tickets/detail/TicketPolling.tsx +5 -1
  108. package/src/types/lucide-react.d.ts +10 -2
  109. package/src/utils/aiAgent.ts +2 -1
  110. package/src/utils/aiProvider.ts +21 -0
  111. package/src/utils/features.ts +297 -0
  112. package/src/utils/fireWebhooks.ts +6 -0
  113. package/src/utils/generateTicketSynthesis.ts +2 -1
  114. package/src/utils/index.ts +4 -2
  115. package/src/utils/readSettings.ts +82 -9
  116. package/src/utils/webhookDispatcher.ts +19 -7
  117. package/src/views/ChatView/client.tsx +13 -9
  118. package/src/views/CrmView/client.tsx +10 -11
  119. package/src/views/SupportDashboardView/client.tsx +1 -1
  120. package/src/views/TicketDetailView/client.tsx +2 -2
  121. package/src/views/TicketInboxView/client.tsx +10 -5
  122. package/src/views/TicketingSettingsView/client.tsx +9 -7
  123. package/src/views/shared/config.ts +14 -80
  124. package/src/views/shared/index.ts +3 -1
  125. package/src/views/shared/locales/en.json +2 -2
  126. package/src/views/shared/locales/fr.json +2 -2
  127. package/dist/components/RichTextEditor/index.cjs +0 -279
  128. package/dist/components/TicketConversation/RewriteDropdown.cjs +0 -98
  129. package/dist/components/TicketConversation/SkeletonText.cjs +0 -21
  130. package/dist/components/TicketConversation/components/AISummaryPanel.cjs +0 -85
  131. package/dist/components/TicketConversation/components/ActionPanels.cjs +0 -193
  132. package/dist/components/TicketConversation/components/ActivityLog.cjs +0 -24
  133. package/dist/components/TicketConversation/components/ClientBar.cjs +0 -43
  134. package/dist/components/TicketConversation/components/ClientHistory.cjs +0 -138
  135. package/dist/components/TicketConversation/components/CodeBlock.cjs +0 -171
  136. package/dist/components/TicketConversation/components/CodeBlockInserter.cjs +0 -156
  137. package/dist/components/TicketConversation/components/QuickActions.cjs +0 -87
  138. package/dist/components/TicketConversation/components/TicketHeader.cjs +0 -93
  139. package/dist/components/TicketConversation/components/TimeTrackingPanel.cjs +0 -134
  140. package/dist/components/TicketConversation/config.cjs +0 -44
  141. package/dist/components/TicketConversation/constants.cjs +0 -127
  142. package/dist/components/TicketConversation/context.cjs +0 -13
  143. package/dist/components/TicketConversation/hooks/useAI.cjs +0 -196
  144. package/dist/components/TicketConversation/hooks/useDocumentIdFromUrl.cjs +0 -20
  145. package/dist/components/TicketConversation/hooks/useMessageActions.cjs +0 -136
  146. package/dist/components/TicketConversation/hooks/useReply.cjs +0 -188
  147. package/dist/components/TicketConversation/hooks/useTicketActions.cjs +0 -290
  148. package/dist/components/TicketConversation/hooks/useTimeTracking.cjs +0 -123
  149. package/dist/components/TicketConversation/hooks/useTranslation.cjs +0 -78
  150. package/dist/components/TicketConversation/index.cjs +0 -1114
  151. package/dist/components/TicketConversation/types.cjs +0 -2
  152. package/dist/components/TicketConversation/utils.cjs +0 -27
  153. package/dist/views/BillingView/client.cjs +0 -469
  154. package/dist/views/BillingView/index.cjs +0 -34
  155. package/dist/views/ChatView/client.cjs +0 -301
  156. package/dist/views/ChatView/index.cjs +0 -34
  157. package/dist/views/CrmView/client.cjs +0 -458
  158. package/dist/views/CrmView/index.cjs +0 -34
  159. package/dist/views/EmailTrackingView/client.cjs +0 -171
  160. package/dist/views/EmailTrackingView/index.cjs +0 -34
  161. package/dist/views/ImportConversationView/client.cjs +0 -238
  162. package/dist/views/ImportConversationView/index.cjs +0 -34
  163. package/dist/views/LogsView/client.cjs +0 -148
  164. package/dist/views/LogsView/index.cjs +0 -32
  165. package/dist/views/NewTicketView/client.cjs +0 -213
  166. package/dist/views/NewTicketView/index.cjs +0 -32
  167. package/dist/views/PendingEmailsView/client.cjs +0 -471
  168. package/dist/views/PendingEmailsView/index.cjs +0 -34
  169. package/dist/views/SupportDashboardView/client.cjs +0 -437
  170. package/dist/views/SupportDashboardView/index.cjs +0 -34
  171. package/dist/views/TicketDetailView/NextActionItem.cjs +0 -40
  172. package/dist/views/TicketDetailView/RewriteDropdown.cjs +0 -80
  173. package/dist/views/TicketDetailView/client.cjs +0 -1412
  174. package/dist/views/TicketDetailView/constants.cjs +0 -20
  175. package/dist/views/TicketDetailView/helpers.cjs +0 -23
  176. package/dist/views/TicketDetailView/index.cjs +0 -34
  177. package/dist/views/TicketDetailView/types.cjs +0 -2
  178. package/dist/views/TicketInboxView/client.cjs +0 -359
  179. package/dist/views/TicketInboxView/index.cjs +0 -32
  180. package/dist/views/TicketingSettingsView/client.cjs +0 -740
  181. package/dist/views/TicketingSettingsView/index.cjs +0 -34
  182. package/dist/views/TimeDashboardView/client.cjs +0 -180
  183. package/dist/views/TimeDashboardView/index.cjs +0 -34
  184. package/dist/views/shared/AdminViewHeader.cjs +0 -68
  185. package/dist/views/shared/ErrorBoundary.cjs +0 -55
  186. package/dist/views/shared/Skeleton.cjs +0 -77
  187. package/dist/views/shared/StatusPill.cjs +0 -47
  188. package/dist/views/shared/adminTokens.cjs +0 -34
  189. package/dist/views/shared/config.cjs +0 -44
  190. package/dist/views/shared/dateLocale.cjs +0 -5
  191. package/dist/views/shared/index.cjs +0 -72
  192. package/dist/views/shared/sla.cjs +0 -37
  193. package/dist/views.cjs +0 -86
  194. package/dist/views.d.cts +0 -13
  195. package/src/__tests__/aiSummaryRendering.test.ts +0 -21
  196. package/src/__tests__/authResponses.test.ts +0 -69
  197. package/src/__tests__/capabilities.test.ts +0 -71
  198. package/src/__tests__/generateTrackingToken.test.ts +0 -67
  199. package/src/__tests__/integration/ai-agent.test.ts +0 -46
  200. package/src/__tests__/integration/auth.test.ts +0 -43
  201. package/src/__tests__/integration/automation-rules.test.ts +0 -68
  202. package/src/__tests__/integration/buildTestPayload.ts +0 -52
  203. package/src/__tests__/integration/channels.test.ts +0 -52
  204. package/src/__tests__/integration/core-behaviors.test.ts +0 -81
  205. package/src/__tests__/integration/dashboard-volume.test.ts +0 -31
  206. package/src/__tests__/integration/digest.test.ts +0 -48
  207. package/src/__tests__/integration/invoice-pdf.test.ts +0 -30
  208. package/src/__tests__/integration/isolation.test.ts +0 -81
  209. package/src/__tests__/integration/mentions-invoice.test.ts +0 -50
  210. package/src/__tests__/integration/nps.test.ts +0 -37
  211. package/src/__tests__/integration/per-team-sla.test.ts +0 -44
  212. package/src/__tests__/integration/push.test.ts +0 -75
  213. package/src/__tests__/integration/sanitization.test.ts +0 -35
  214. package/src/__tests__/integration/sla-pause.test.ts +0 -39
  215. package/src/__tests__/integration/smoke.test.ts +0 -26
  216. package/src/__tests__/integration/snooze.test.ts +0 -68
  217. package/src/__tests__/integration/teams.test.ts +0 -38
  218. package/src/__tests__/rateLimiter.test.ts +0 -83
  219. package/src/__tests__/sanitizeHtml.test.ts +0 -165
  220. package/src/__tests__/sla.test.ts +0 -258
  221. package/src/__tests__/trackOpenEndpoint.test.ts +0 -54
  222. package/src/__tests__/webhookSecurity.test.ts +0 -58
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Ticketing feature flags — single source of truth (client + server).
3
+ *
4
+ * Flags are stored SERVER-side, in the `support-settings` row of
5
+ * `payload-preferences` (see `readSettings.ts` and `endpoints/settings.ts`).
6
+ * A flag toggled on one machine therefore applies to every browser and to
7
+ * server-side code (hooks, cron endpoints) alike.
8
+ *
9
+ * `localStorage` is still used, but demoted to a plain CACHE: it lets admin
10
+ * views paint synchronously on first render and keeps them usable when the
11
+ * settings endpoint is unreachable. It is never authoritative.
12
+ *
13
+ * Two flags are *projections* of settings that already lived server-side long
14
+ * before this module, and are deliberately NOT stored twice:
15
+ * - `autoClose` ← `settings.autoClose.enabled`
16
+ * - `autoCloseDays` ← `settings.autoClose.daysBeforeClose`
17
+ * The `autoClose` block stays authoritative; the flags are recomputed on read
18
+ * and dropped on write. See `projectAutoClose` / `stripProjectedFeatures`.
19
+ */
20
+ /**
21
+ * Feature flags for the ticketing module.
22
+ * Each feature can be enabled/disabled by the admin.
23
+ * When disabled, the corresponding UI section is hidden entirely.
24
+ */
25
+ export interface TicketingFeatures {
26
+ /** Time tracking: timer, manual entries, billing */
27
+ timeTracking: boolean;
28
+ /** AI features: sentiment, synthesis, suggestion, rewrite */
29
+ ai: boolean;
30
+ /** Satisfaction surveys: CSAT rating after resolution */
31
+ satisfaction: boolean;
32
+ /** Live chat integration: chat → ticket conversion */
33
+ chat: boolean;
34
+ /** Email tracking: pixel tracking, open/sent status per message */
35
+ emailTracking: boolean;
36
+ /** Canned responses: quick reply templates */
37
+ canned: boolean;
38
+ /** Ticket merge: combine two tickets into one */
39
+ merge: boolean;
40
+ /** Snooze: temporarily hide a ticket */
41
+ snooze: boolean;
42
+ /** External messages: add messages received outside the system */
43
+ externalMessages: boolean;
44
+ /** Client history: past tickets, projects, notes sidebar */
45
+ clientHistory: boolean;
46
+ /** Activity log: audit trail of actions on the ticket */
47
+ activityLog: boolean;
48
+ /** Split ticket: extract a message into a new ticket */
49
+ splitTicket: boolean;
50
+ /** Scheduled replies: send a message at a future date */
51
+ scheduledReplies: boolean;
52
+ /** Auto-close: automatically resolve inactive tickets (projection of settings.autoClose.enabled) */
53
+ autoClose: boolean;
54
+ /** Auto-close delay in days (projection of settings.autoClose.daysBeforeClose) */
55
+ autoCloseDays: number;
56
+ /** Round-robin: distribute new tickets evenly among agents */
57
+ roundRobin: boolean;
58
+ }
59
+ /** Default features — all enabled except round-robin */
60
+ export declare const DEFAULT_TICKETING_FEATURES: TicketingFeatures;
61
+ /** Flags derived from another settings block — never persisted inside `features`. */
62
+ declare const PROJECTED_FEATURE_KEYS: readonly ["autoClose", "autoCloseDays"];
63
+ /**
64
+ * Coerce arbitrary input (stale localStorage, hand-edited preference row, older
65
+ * plugin version) into a complete, well-typed feature set. Unknown keys are
66
+ * dropped and wrongly-typed values fall back to their default.
67
+ */
68
+ export declare function normalizeFeatures(raw: unknown): TicketingFeatures;
69
+ /** Shape of the `autoClose` settings block the two projected flags mirror. */
70
+ export interface AutoCloseProjectionSource {
71
+ enabled?: boolean;
72
+ daysBeforeClose?: number;
73
+ }
74
+ /** Recompute the projected flags from the authoritative `autoClose` block. */
75
+ export declare function projectAutoClose(features: TicketingFeatures, autoClose: AutoCloseProjectionSource | undefined): TicketingFeatures;
76
+ /**
77
+ * Drop the projected flags before persisting, so `features` and the `autoClose`
78
+ * block can never disagree.
79
+ */
80
+ export declare function stripProjectedFeatures(features: TicketingFeatures): Omit<TicketingFeatures, (typeof PROJECTED_FEATURE_KEYS)[number]>;
81
+ /** localStorage key. Was the source of truth before 2.1 — now only a cache. */
82
+ export declare const FEATURES_CACHE_KEY = "ticketing_features";
83
+ /** Set once the pre-2.1 localStorage values have been pushed to the server. */
84
+ export declare const FEATURES_ADOPTED_KEY = "ticketing_features_adopted";
85
+ /** Admin-only settings endpoint backing the flags. */
86
+ export declare const FEATURES_ENDPOINT = "/api/support/settings";
87
+ /** Read the cached flags, or `null` when nothing usable is cached. */
88
+ export declare function readFeaturesCache(): TicketingFeatures | null;
89
+ /** Refresh the cache with the values the server just confirmed. */
90
+ export declare function writeFeaturesCache(features: TicketingFeatures): void;
91
+ /**
92
+ * Synchronous read used for first paint. Returns the cache when present, the
93
+ * defaults otherwise. Callers must still refresh from the server — see
94
+ * `useFeatures`.
95
+ */
96
+ export declare function getFeatures(): TicketingFeatures;
97
+ /** True once this browser's legacy flags have been handed over to the server. */
98
+ export declare function hasAdoptedLegacyFeatures(): boolean;
99
+ export declare function markLegacyFeaturesAdopted(): void;
100
+ /**
101
+ * Flags this browser configured before the server became authoritative, or
102
+ * `null` when there is nothing to migrate (no local values, or already done).
103
+ */
104
+ export declare function readLegacyFeatures(): TicketingFeatures | null;
105
+ export interface FetchedFeatures {
106
+ features: TicketingFeatures;
107
+ /** False when the server has never had feature flags saved (fresh install). */
108
+ configured: boolean;
109
+ /** True when the values come from the cache/defaults because the call failed. */
110
+ offline: boolean;
111
+ }
112
+ /**
113
+ * Read the flags from the server. Falls back to the local cache, then to the
114
+ * defaults: a failed settings call must never leave a ticket screen unusable.
115
+ */
116
+ export declare function fetchFeatures(signal?: AbortSignal): Promise<FetchedFeatures>;
117
+ /**
118
+ * Persist the flags server-side, then refresh the local cache.
119
+ *
120
+ * @returns true when the server accepted the write.
121
+ */
122
+ export declare function saveFeatures(features: TicketingFeatures): Promise<boolean>;
123
+ export {};
@@ -0,0 +1,147 @@
1
+ const DEFAULT_TICKETING_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 BOOLEAN_FEATURE_KEYS = [
20
+ "timeTracking",
21
+ "ai",
22
+ "satisfaction",
23
+ "chat",
24
+ "emailTracking",
25
+ "canned",
26
+ "merge",
27
+ "snooze",
28
+ "externalMessages",
29
+ "clientHistory",
30
+ "activityLog",
31
+ "splitTicket",
32
+ "scheduledReplies",
33
+ "autoClose",
34
+ "roundRobin"
35
+ ];
36
+ const PROJECTED_FEATURE_KEYS = ["autoClose", "autoCloseDays"];
37
+ function normalizeFeatures(raw) {
38
+ const out = { ...DEFAULT_TICKETING_FEATURES };
39
+ if (!raw || typeof raw !== "object") return out;
40
+ const src = raw;
41
+ for (const key of BOOLEAN_FEATURE_KEYS) {
42
+ if (typeof src[key] === "boolean") out[key] = src[key];
43
+ }
44
+ const days = src.autoCloseDays;
45
+ if (typeof days === "number" && Number.isFinite(days) && days >= 1) {
46
+ out.autoCloseDays = Math.floor(days);
47
+ }
48
+ return out;
49
+ }
50
+ function projectAutoClose(features, autoClose) {
51
+ if (!autoClose) return features;
52
+ return {
53
+ ...features,
54
+ autoClose: typeof autoClose.enabled === "boolean" ? autoClose.enabled : features.autoClose,
55
+ autoCloseDays: typeof autoClose.daysBeforeClose === "number" && autoClose.daysBeforeClose >= 1 ? Math.floor(autoClose.daysBeforeClose) : features.autoCloseDays
56
+ };
57
+ }
58
+ function stripProjectedFeatures(features) {
59
+ const out = { ...features };
60
+ for (const key of PROJECTED_FEATURE_KEYS) delete out[key];
61
+ return out;
62
+ }
63
+ const FEATURES_CACHE_KEY = "ticketing_features";
64
+ const FEATURES_ADOPTED_KEY = "ticketing_features_adopted";
65
+ const FEATURES_ENDPOINT = "/api/support/settings";
66
+ function storage() {
67
+ try {
68
+ if (typeof window === "undefined" || !window.localStorage) return null;
69
+ return window.localStorage;
70
+ } catch {
71
+ return null;
72
+ }
73
+ }
74
+ function readFeaturesCache() {
75
+ const store = storage();
76
+ if (!store) return null;
77
+ try {
78
+ const stored = store.getItem(FEATURES_CACHE_KEY);
79
+ if (!stored) return null;
80
+ return normalizeFeatures(JSON.parse(stored));
81
+ } catch {
82
+ return null;
83
+ }
84
+ }
85
+ function writeFeaturesCache(features) {
86
+ const store = storage();
87
+ if (!store) return;
88
+ try {
89
+ store.setItem(FEATURES_CACHE_KEY, JSON.stringify(features));
90
+ } catch {
91
+ }
92
+ }
93
+ function getFeatures() {
94
+ return readFeaturesCache() ?? { ...DEFAULT_TICKETING_FEATURES };
95
+ }
96
+ function hasAdoptedLegacyFeatures() {
97
+ const store = storage();
98
+ if (!store) return true;
99
+ try {
100
+ return store.getItem(FEATURES_ADOPTED_KEY) === "1";
101
+ } catch {
102
+ return true;
103
+ }
104
+ }
105
+ function markLegacyFeaturesAdopted() {
106
+ const store = storage();
107
+ if (!store) return;
108
+ try {
109
+ store.setItem(FEATURES_ADOPTED_KEY, "1");
110
+ } catch {
111
+ }
112
+ }
113
+ function readLegacyFeatures() {
114
+ if (hasAdoptedLegacyFeatures()) return null;
115
+ return readFeaturesCache();
116
+ }
117
+ async function fetchFeatures(signal) {
118
+ try {
119
+ const res = await fetch(FEATURES_ENDPOINT, { credentials: "include", signal });
120
+ if (res.ok) {
121
+ const data = await res.json();
122
+ const features = projectAutoClose(normalizeFeatures(data.features), data.autoClose);
123
+ writeFeaturesCache(features);
124
+ return { features, configured: data.featuresConfigured === true, offline: false };
125
+ }
126
+ } catch {
127
+ }
128
+ return { features: getFeatures(), configured: false, offline: true };
129
+ }
130
+ async function saveFeatures(features) {
131
+ const normalized = normalizeFeatures(features);
132
+ try {
133
+ const res = await fetch(FEATURES_ENDPOINT, {
134
+ method: "POST",
135
+ headers: { "Content-Type": "application/json" },
136
+ credentials: "include",
137
+ body: JSON.stringify({ features: normalized })
138
+ });
139
+ if (!res.ok) return false;
140
+ writeFeaturesCache(normalized);
141
+ return true;
142
+ } catch {
143
+ return false;
144
+ }
145
+ }
146
+
147
+ export { DEFAULT_TICKETING_FEATURES, FEATURES_ADOPTED_KEY, FEATURES_CACHE_KEY, FEATURES_ENDPOINT, fetchFeatures, getFeatures, hasAdoptedLegacyFeatures, markLegacyFeaturesAdopted, normalizeFeatures, projectAutoClose, readFeaturesCache, readLegacyFeatures, saveFeatures, stripProjectedFeatures, writeFeaturesCache };
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const BillingClient: React.FC;
@@ -0,0 +1,4 @@
1
+ import type { AdminViewServerProps } from 'payload';
2
+ import React from 'react';
3
+ export declare const BillingView: React.FC<AdminViewServerProps>;
4
+ export default BillingView;
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const ChatViewClient: React.FC;
@@ -21,6 +21,7 @@ const ChatViewClient = () => {
21
21
  const sessionsPollTimeout = useRef(void 0);
22
22
  const messagesPollInterval = useRef(3e3);
23
23
  const messagesPollTimeout = useRef(void 0);
24
+ const sessionsSnapshotRef = useRef("");
24
25
  const [sessionExpired, setSessionExpired] = useState(false);
25
26
  const fetchSessions = useCallback(async () => {
26
27
  let hadChanges = false;
@@ -32,15 +33,14 @@ const ChatViewClient = () => {
32
33
  }
33
34
  if (res.ok) {
34
35
  const data = await res.json();
35
- setSessions((prev) => {
36
- const newActive = data.active || [];
37
- const newClosed = data.closed || [];
38
- if (JSON.stringify(prev.active) !== JSON.stringify(newActive) || JSON.stringify(prev.closed) !== JSON.stringify(newClosed)) {
39
- hadChanges = true;
40
- return { active: newActive, closed: newClosed };
41
- }
42
- return prev;
43
- });
36
+ const newActive = data.active || [];
37
+ const newClosed = data.closed || [];
38
+ const snapshot = JSON.stringify([newActive, newClosed]);
39
+ if (snapshot !== sessionsSnapshotRef.current) {
40
+ sessionsSnapshotRef.current = snapshot;
41
+ hadChanges = true;
42
+ setSessions({ active: newActive, closed: newClosed });
43
+ }
44
44
  }
45
45
  } catch {
46
46
  }
@@ -0,0 +1,4 @@
1
+ import type { AdminViewServerProps } from 'payload';
2
+ import React from 'react';
3
+ export declare const ChatView: React.FC<AdminViewServerProps>;
4
+ export default ChatView;
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const CrmClient: React.FC;
@@ -92,21 +92,18 @@ const CrmClient = () => {
92
92
  const fetchDetail = useCallback(async (clientId) => {
93
93
  setDetailLoading(true);
94
94
  try {
95
- const [clientRes, ticketsRes, projectsRes, timeRes] = await Promise.all([
95
+ const [clientRes, ticketsRes, projectsRes] = await Promise.all([
96
96
  fetch(`/api/support-clients/${clientId}?depth=0`),
97
97
  fetch(`/api/tickets?where[client][equals]=${clientId}&sort=-createdAt&limit=20&depth=0`),
98
- fetch(`/api/projects?where[client][equals]=${clientId}&depth=0`),
99
- fetch(`/api/time-entries?limit=0&depth=0`)
98
+ fetch(`/api/projects?where[client][equals]=${clientId}&depth=0`)
100
99
  ]);
101
100
  const client = clientRes.ok ? await clientRes.json() : null;
102
101
  const tickets = ticketsRes.ok ? (await ticketsRes.json()).docs || [] : [];
103
102
  const projects = projectsRes.ok ? (await projectsRes.json()).docs || [] : [];
104
- const ticketIds = tickets.map((t2) => t2.id);
105
- let totalTimeMinutes = 0;
106
- if (timeRes.ok) {
107
- const timeData = await timeRes.json();
108
- totalTimeMinutes = timeData.docs.filter((e) => ticketIds.includes(e.ticket)).reduce((sum, e) => sum + (e.duration || 0), 0);
109
- }
103
+ const totalTimeMinutes = tickets.reduce(
104
+ (sum, t2) => sum + (t2.totalTimeMinutes || 0),
105
+ 0
106
+ );
110
107
  const openTickets = tickets.filter(
111
108
  (t2) => ["open", "waiting_client"].includes(t2.status)
112
109
  ).length;
@@ -0,0 +1,4 @@
1
+ import type { AdminViewServerProps } from 'payload';
2
+ import React from 'react';
3
+ export declare const CrmView: React.FC<AdminViewServerProps>;
4
+ export default CrmView;
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const EmailTrackingClient: React.FC;
@@ -0,0 +1,4 @@
1
+ import type { AdminViewServerProps } from 'payload';
2
+ import React from 'react';
3
+ export declare const EmailTrackingView: React.FC<AdminViewServerProps>;
4
+ export default EmailTrackingView;
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare function ImportConversationClient(): React.JSX.Element;
@@ -0,0 +1,4 @@
1
+ import type { AdminViewServerProps } from 'payload';
2
+ import React from 'react';
3
+ export declare const ImportConversationView: React.FC<AdminViewServerProps>;
4
+ export default ImportConversationView;
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const LogsClient: React.FC;
@@ -0,0 +1,4 @@
1
+ import type { AdminViewServerProps } from 'payload';
2
+ import React from 'react';
3
+ export declare const LogsView: React.FC<AdminViewServerProps>;
4
+ export default LogsView;
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const NewTicketClient: React.FC;
@@ -0,0 +1,4 @@
1
+ import type { AdminViewServerProps } from 'payload';
2
+ import React from 'react';
3
+ export declare const NewTicketView: React.FC<AdminViewServerProps>;
4
+ export default NewTicketView;
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const PendingEmailsClient: React.FC;
@@ -0,0 +1,4 @@
1
+ import type { AdminViewServerProps } from 'payload';
2
+ import React from 'react';
3
+ export declare const PendingEmailsView: React.FC<AdminViewServerProps>;
4
+ export default PendingEmailsView;
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const SupportDashboardClient: React.FC;
@@ -400,7 +400,7 @@ const SupportDashboardClient = () => {
400
400
  /* @__PURE__ */ jsx(VolumeChart, { data: volumeData })
401
401
  ] }),
402
402
  /* @__PURE__ */ jsxs("div", { className: styles.panel, children: [
403
- /* @__PURE__ */ jsx("h2", { className: styles.panelTitle, children: t("dashboard.csat") }),
403
+ /* @__PURE__ */ jsx("h2", { className: styles.panelTitle, children: t("dashboard.csatTitle") }),
404
404
  /* @__PURE__ */ jsx(CSATRing, { score: stats.satisfactionAvg, count: stats.satisfactionCount })
405
405
  ] })
406
406
  ] })
@@ -0,0 +1,4 @@
1
+ import type { AdminViewServerProps } from 'payload';
2
+ import React from 'react';
3
+ export declare const SupportDashboardView: React.FC<AdminViewServerProps>;
4
+ export default SupportDashboardView;
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ export declare function NextActionItem({ action, index, onToggle }: {
3
+ action: any;
4
+ index: number;
5
+ onToggle: (done: boolean) => Promise<void>;
6
+ }): React.JSX.Element;
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ export declare const RewriteDropdown: React.FC<{
3
+ disabled: boolean;
4
+ loading: boolean;
5
+ onSelect: (style: string) => void;
6
+ toolbarBtnClass?: string;
7
+ }>;
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const TicketDetailClient: React.FC;
@@ -6,7 +6,7 @@ import Link from 'next/link';
6
6
  import { RichTextEditor } from '../../components/RichTextEditor/index.js';
7
7
  import { hasCodeBlocks, CodeBlockRendererHtml, MessageWithCodeBlocks } from '../../components/TicketConversation/components/CodeBlock.js';
8
8
  import { CodeBlockInserter } from '../../components/TicketConversation/components/CodeBlockInserter.js';
9
- import { getFeatures } from '../shared/config.js';
9
+ import { useFeatures } from '../../components/TicketConversation/hooks/useFeatures.js';
10
10
  import { computeSlaState, formatSlaRemaining } from '../shared/sla.js';
11
11
  import { useTranslation } from '../../components/TicketConversation/hooks/useTranslation.js';
12
12
  import { DATE_LOCALE } from '../shared/dateLocale.js';
@@ -20,7 +20,7 @@ const TicketDetailClient = () => {
20
20
  const { t } = useTranslation();
21
21
  const searchParams = useSearchParams();
22
22
  const ticketId = searchParams.get("id");
23
- const features = getFeatures();
23
+ const { features } = useFeatures();
24
24
  const editorRef = useRef(null);
25
25
  const threadEndRef = useRef(null);
26
26
  const dropdownRef = useRef(null);
@@ -0,0 +1,13 @@
1
+ export declare const ALIASES: Array<{
2
+ value: string;
3
+ label: string;
4
+ }>;
5
+ export declare const STATUS_STYLE: Record<string, {
6
+ bg: string;
7
+ color: string;
8
+ }>;
9
+ export declare const REWRITE_STYLES: {
10
+ id: string;
11
+ label: string;
12
+ desc: string;
13
+ }[];
@@ -0,0 +1,2 @@
1
+ export declare function timeAgo(d: string): string;
2
+ export declare function dateLabel(d: string): string;
@@ -0,0 +1,4 @@
1
+ import type { AdminViewServerProps } from 'payload';
2
+ import React from 'react';
3
+ export declare const TicketDetailView: React.FC<AdminViewServerProps>;
4
+ export default TicketDetailView;
@@ -0,0 +1,40 @@
1
+ export interface Message {
2
+ id: string | number;
3
+ body: string;
4
+ bodyHtml?: string;
5
+ authorType: 'client' | 'admin' | 'email';
6
+ isInternal?: boolean;
7
+ isSolution?: boolean;
8
+ createdAt: string;
9
+ fromChat?: boolean;
10
+ fromAlias?: string;
11
+ attachments?: Array<{
12
+ file: {
13
+ id: number;
14
+ url?: string;
15
+ filename?: string;
16
+ mimeType?: string;
17
+ } | number;
18
+ }>;
19
+ }
20
+ export interface ClientInfo {
21
+ id: number;
22
+ company: string;
23
+ firstName: string;
24
+ lastName: string;
25
+ email: string;
26
+ phone?: string;
27
+ }
28
+ export interface TimeEntry {
29
+ id: string | number;
30
+ duration: number;
31
+ description?: string;
32
+ date: string;
33
+ }
34
+ export interface ActivityEntry {
35
+ id: string | number;
36
+ action: string;
37
+ detail?: string;
38
+ actorType?: string;
39
+ createdAt: string;
40
+ }
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const TicketInboxClient: React.FC;
@@ -141,11 +141,11 @@ const TicketInboxClient = () => {
141
141
  try {
142
142
  const sn = `where[and][0][or][0][snoozeUntil][exists]=false&where[and][0][or][1][snoozeUntil][less_than_equal]=${encodeURIComponent((/* @__PURE__ */ new Date()).toISOString())}`;
143
143
  const [all, openRes, waiting, resolved, breach] = await Promise.all([
144
- fetch(`/api/tickets?limit=0&depth=0&${sn}`, { credentials: "include" }),
145
- fetch(`/api/tickets?limit=0&depth=0&where[status][equals]=open&${sn}`, { credentials: "include" }),
146
- fetch(`/api/tickets?limit=0&depth=0&where[status][equals]=waiting_client&${sn}`, { credentials: "include" }),
147
- fetch(`/api/tickets?limit=0&depth=0&where[status][equals]=resolved&${sn}`, { credentials: "include" }),
148
- fetch(`/api/tickets?limit=0&depth=0&where[or][0][slaFirstResponseBreached][equals]=true&where[or][1][slaResolutionBreached][equals]=true&${sn}`, { credentials: "include" })
144
+ fetch(`/api/tickets/count?${sn}`, { credentials: "include" }),
145
+ fetch(`/api/tickets/count?where[status][equals]=open&${sn}`, { credentials: "include" }),
146
+ fetch(`/api/tickets/count?where[status][equals]=waiting_client&${sn}`, { credentials: "include" }),
147
+ fetch(`/api/tickets/count?where[status][equals]=resolved&${sn}`, { credentials: "include" }),
148
+ fetch(`/api/tickets/count?where[or][0][slaFirstResponseBreached][equals]=true&where[or][1][slaResolutionBreached][equals]=true&${sn}`, { credentials: "include" })
149
149
  ]);
150
150
  const [a, o, w, r, b] = await Promise.all([all.json(), openRes.json(), waiting.json(), resolved.json(), breach.json()]);
151
151
  setCounts({
@@ -0,0 +1,4 @@
1
+ import type { AdminViewServerProps } from 'payload';
2
+ import React from 'react';
3
+ export declare const TicketInboxView: React.FC<AdminViewServerProps>;
4
+ export default TicketInboxView;
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const TicketingSettingsClient: React.FC;
@@ -4,7 +4,8 @@ import React, { useState, useEffect } from 'react';
4
4
  import { Settings, Mail, Bot, Clock, Timer, Globe, FileSignature } from 'lucide-react';
5
5
  import { btnStyle, V } from '../shared/adminTokens.js';
6
6
  import { AdminViewHeader } from '../shared/AdminViewHeader.js';
7
- import { getFeatures, saveFeatures, DEFAULT_FEATURES } from '../../components/TicketConversation/config.js';
7
+ import { DEFAULT_FEATURES } from '../../components/TicketConversation/config.js';
8
+ import { useFeatures } from '../../components/TicketConversation/hooks/useFeatures.js';
8
9
  import { useTranslation } from '../../components/TicketConversation/hooks/useTranslation.js';
9
10
  import ts from '../../styles/TicketingSettings.module.scss';
10
11
 
@@ -206,7 +207,7 @@ const TicketingSettingsClient = () => {
206
207
  const { t } = useTranslation();
207
208
  const FEATURE_LIST = React.useMemo(() => getFeatureList(t), [t]);
208
209
  const CATEGORIES = React.useMemo(() => getCategories(t), [t]);
209
- const [features, setFeatures] = useState(() => getFeatures());
210
+ const { features, setFeatures, save: saveFeatures } = useFeatures();
210
211
  const [settings, setSettings] = useState(DEFAULT_SETTINGS);
211
212
  const [signature, setSignature] = useState("");
212
213
  const [saved, setSaved] = useState(false);
@@ -253,13 +254,13 @@ const TicketingSettingsClient = () => {
253
254
  };
254
255
  const handleSave = async () => {
255
256
  setSaving(true);
256
- saveFeatures(features);
257
+ const featuresOk = await saveFeatures(features);
257
258
  const [settingsOk, prefsOk] = await Promise.all([
258
259
  saveSettingsToAPI(settings),
259
260
  saveUserPrefs({ locale: settings.locale.language, signature })
260
261
  ]);
261
262
  setSaving(false);
262
- if (settingsOk && prefsOk) {
263
+ if (featuresOk && settingsOk && prefsOk) {
263
264
  setSaved(true);
264
265
  setTimeout(() => setSaved(false), 3e3);
265
266
  }
@@ -299,7 +300,7 @@ const TicketingSettingsClient = () => {
299
300
  /* @__PURE__ */ jsxs(
300
301
  CollapsibleSection,
301
302
  {
302
- title: t("settingsView.features"),
303
+ title: t("settingsView.featuresTitle"),
303
304
  icon: /* @__PURE__ */ jsx(Settings, { size: 16 }),
304
305
  color: V.blue,
305
306
  badge: /* @__PURE__ */ jsxs("span", { className: ts.badge, style: { backgroundColor: "#dbeafe", color: "#1e40af" }, children: [
@@ -0,0 +1,4 @@
1
+ import type { AdminViewServerProps } from 'payload';
2
+ import React from 'react';
3
+ export declare const TicketingSettingsView: React.FC<AdminViewServerProps>;
4
+ export default TicketingSettingsView;
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const TimeDashboardClient: React.FC;
@@ -0,0 +1,4 @@
1
+ import type { AdminViewServerProps } from 'payload';
2
+ import React from 'react';
3
+ export declare const TimeDashboardView: React.FC<AdminViewServerProps>;
4
+ export default TimeDashboardView;
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ interface AdminViewHeaderProps {
3
+ icon: React.ReactNode;
4
+ title: string;
5
+ subtitle?: string;
6
+ breadcrumb?: {
7
+ label: string;
8
+ href: string;
9
+ };
10
+ actions?: React.ReactNode;
11
+ }
12
+ export declare const AdminViewHeader: React.FC<AdminViewHeaderProps>;
13
+ export {};