@consilioweb/payload-support 4.0.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/README.md +272 -35
  2. package/dist/components/RichTextEditor/index.js +1 -1
  3. package/dist/components/TicketConversation/RewriteDropdown.js +1 -1
  4. package/dist/components/TicketConversation/components/AISummaryPanel.js +3 -0
  5. package/dist/components/TicketConversation/components/ActionPanels.js +13 -8
  6. package/dist/components/TicketConversation/components/ClientHistory.js +1 -0
  7. package/dist/components/TicketConversation/components/CodeBlock.js +1 -0
  8. package/dist/components/TicketConversation/components/CodeBlockInserter.js +1 -1
  9. package/dist/components/TicketConversation/components/QuickActions.js +10 -8
  10. package/dist/components/TicketConversation/components/TicketHeader.js +2 -0
  11. package/dist/components/TicketConversation/components/TimeTrackingPanel.js +15 -9
  12. package/dist/components/TicketConversation/index.d.ts +10 -0
  13. package/dist/components/TicketConversation/index.js +25 -7
  14. package/dist/index.cjs +421 -73
  15. package/dist/index.d.cts +127 -38
  16. package/dist/index.d.ts +127 -38
  17. package/dist/index.js +415 -72
  18. package/dist/styles/BillingView.module.scss +11 -11
  19. package/dist/styles/ChatView.module.scss +7 -7
  20. package/dist/styles/CommandPalette.module.scss +1 -1
  21. package/dist/styles/CrmView.module.scss +9 -9
  22. package/dist/styles/EmailTracking.module.scss +6 -6
  23. package/dist/styles/ImportConversation.module.scss +5 -5
  24. package/dist/styles/Logs.module.scss +5 -5
  25. package/dist/styles/NewTicket.module.scss +2 -2
  26. package/dist/styles/PendingEmails.module.scss +13 -13
  27. package/dist/styles/SupportDashboard.module.scss +24 -8
  28. package/dist/styles/TicketDetail.module.scss +20 -20
  29. package/dist/styles/TicketInbox.module.scss +6 -6
  30. package/dist/styles/TicketingSettings.module.scss +10 -10
  31. package/dist/styles/TimeDashboard.module.scss +6 -6
  32. package/dist/styles/_tokens.scss +3 -1
  33. package/dist/utils/db.d.ts +34 -0
  34. package/dist/utils/readSettings.d.ts +90 -0
  35. package/dist/views/BillingView/client.js +15 -8
  36. package/dist/views/BillingView/index.js +4 -4
  37. package/dist/views/ChatView/client.js +2 -0
  38. package/dist/views/ChatView/index.js +4 -4
  39. package/dist/views/CrmView/client.js +2 -0
  40. package/dist/views/CrmView/index.js +4 -4
  41. package/dist/views/EmailTrackingView/client.js +23 -11
  42. package/dist/views/EmailTrackingView/index.js +4 -4
  43. package/dist/views/ImportConversationView/client.js +1 -0
  44. package/dist/views/ImportConversationView/index.js +4 -4
  45. package/dist/views/LogsView/client.js +1 -1
  46. package/dist/views/LogsView/index.js +4 -2
  47. package/dist/views/NewTicketView/client.js +20 -13
  48. package/dist/views/NewTicketView/index.js +4 -2
  49. package/dist/views/PendingEmailsView/client.js +9 -3
  50. package/dist/views/PendingEmailsView/index.js +4 -4
  51. package/dist/views/SupportDashboardView/client.js +17 -19
  52. package/dist/views/SupportDashboardView/index.js +4 -4
  53. package/dist/views/TicketDetailView/client.js +23 -11
  54. package/dist/views/TicketDetailView/index.js +4 -4
  55. package/dist/views/TicketInboxView/client.js +52 -26
  56. package/dist/views/TicketInboxView/index.js +4 -2
  57. package/dist/views/TicketingSettingsView/TicketingSettings.module.scss +7 -7
  58. package/dist/views/TicketingSettingsView/client.js +44 -26
  59. package/dist/views/TicketingSettingsView/index.js +4 -4
  60. package/dist/views/TimeDashboardView/client.js +10 -7
  61. package/dist/views/TimeDashboardView/index.js +4 -4
  62. package/dist/views/shared/ErrorBoundary.d.ts +36 -1
  63. package/dist/views/shared/ErrorBoundary.js +59 -28
  64. package/dist/views/shared/adminTokens.d.ts +15 -5
  65. package/dist/views/shared/adminTokens.js +23 -7
  66. package/dist/views/shared/icons.d.ts +49 -0
  67. package/dist/views/shared/icons.js +92 -0
  68. package/dist/views/shared/locales/en.json +15 -1
  69. package/dist/views/shared/locales/fr.json +15 -1
  70. package/dist/views/shared/viewAccess.d.ts +29 -0
  71. package/dist/views/shared/viewAccess.js +24 -0
  72. package/package.json +31 -21
  73. package/scripts/uninstall-data.mjs +178 -0
  74. package/scripts/uninstall.mjs +184 -0
  75. package/src/collections/Tickets.ts +7 -0
  76. package/src/collections/TimeEntries.ts +11 -3
  77. package/src/components/RichTextEditor/index.tsx +1 -1
  78. package/src/components/TicketConversation/RewriteDropdown.tsx +1 -1
  79. package/src/components/TicketConversation/components/AISummaryPanel.tsx +3 -0
  80. package/src/components/TicketConversation/components/ActionPanels.tsx +13 -8
  81. package/src/components/TicketConversation/components/ClientHistory.tsx +1 -0
  82. package/src/components/TicketConversation/components/CodeBlock.tsx +1 -0
  83. package/src/components/TicketConversation/components/CodeBlockInserter.tsx +1 -1
  84. package/src/components/TicketConversation/components/QuickActions.tsx +10 -8
  85. package/src/components/TicketConversation/components/TicketHeader.tsx +2 -0
  86. package/src/components/TicketConversation/components/TimeTrackingPanel.tsx +17 -9
  87. package/src/components/TicketConversation/index.tsx +41 -7
  88. package/src/endpoints/auth-2fa.ts +53 -8
  89. package/src/endpoints/capabilities.ts +2 -4
  90. package/src/endpoints/chatbot.ts +2 -2
  91. package/src/endpoints/delete-account.ts +191 -47
  92. package/src/endpoints/export-data.ts +104 -4
  93. package/src/endpoints/import-conversation.ts +2 -2
  94. package/src/endpoints/login.ts +13 -3
  95. package/src/endpoints/purge-logs.ts +6 -15
  96. package/src/endpoints/push.ts +14 -1
  97. package/src/endpoints/statuses.ts +17 -0
  98. package/src/index.ts +2 -0
  99. package/src/plugin.ts +51 -1
  100. package/src/portal/LiveChat.tsx +1 -1
  101. package/src/portal/auth/profile/page.tsx +3 -2
  102. package/src/portal/icons.tsx +91 -0
  103. package/src/portal/login/page.tsx +14 -4
  104. package/src/styles/BillingView.module.scss +11 -11
  105. package/src/styles/ChatView.module.scss +7 -7
  106. package/src/styles/CommandPalette.module.scss +1 -1
  107. package/src/styles/CrmView.module.scss +9 -9
  108. package/src/styles/EmailTracking.module.scss +6 -6
  109. package/src/styles/ImportConversation.module.scss +5 -5
  110. package/src/styles/Logs.module.scss +5 -5
  111. package/src/styles/NewTicket.module.scss +2 -2
  112. package/src/styles/PendingEmails.module.scss +13 -13
  113. package/src/styles/SupportDashboard.module.scss +24 -8
  114. package/src/styles/TicketDetail.module.scss +20 -20
  115. package/src/styles/TicketInbox.module.scss +6 -6
  116. package/src/styles/TicketingSettings.module.scss +10 -10
  117. package/src/styles/TimeDashboard.module.scss +6 -6
  118. package/src/styles/_tokens.scss +3 -1
  119. package/src/types.ts +20 -0
  120. package/src/utils/push.ts +22 -0
  121. package/src/utils/rateLimiter.ts +98 -0
  122. package/src/utils/retention.ts +111 -0
  123. package/src/utils/slugs.ts +15 -0
  124. package/src/utils/twoFactorChallenge.ts +6 -1
  125. package/src/utils/urlSafety.ts +36 -1
  126. package/src/views/BillingView/client.tsx +17 -8
  127. package/src/views/BillingView/index.tsx +4 -4
  128. package/src/views/ChatView/client.tsx +2 -0
  129. package/src/views/ChatView/index.tsx +4 -4
  130. package/src/views/CrmView/client.tsx +2 -0
  131. package/src/views/CrmView/index.tsx +4 -4
  132. package/src/views/EmailTrackingView/client.tsx +18 -7
  133. package/src/views/EmailTrackingView/index.tsx +4 -4
  134. package/src/views/ImportConversationView/client.tsx +1 -0
  135. package/src/views/ImportConversationView/index.tsx +4 -4
  136. package/src/views/LogsView/client.tsx +1 -1
  137. package/src/views/LogsView/index.tsx +4 -2
  138. package/src/views/NewTicketView/client.tsx +22 -13
  139. package/src/views/NewTicketView/index.tsx +4 -2
  140. package/src/views/PendingEmailsView/client.tsx +9 -3
  141. package/src/views/PendingEmailsView/index.tsx +4 -4
  142. package/src/views/SupportDashboardView/client.tsx +11 -5
  143. package/src/views/SupportDashboardView/index.tsx +4 -4
  144. package/src/views/TicketDetailView/client.tsx +25 -11
  145. package/src/views/TicketDetailView/index.tsx +4 -4
  146. package/src/views/TicketInboxView/client.tsx +47 -7
  147. package/src/views/TicketInboxView/index.tsx +4 -2
  148. package/src/views/TicketingSettingsView/TicketingSettings.module.scss +7 -7
  149. package/src/views/TicketingSettingsView/client.tsx +201 -144
  150. package/src/views/TicketingSettingsView/index.tsx +4 -4
  151. package/src/views/TimeDashboardView/client.tsx +10 -7
  152. package/src/views/TimeDashboardView/index.tsx +4 -4
  153. package/src/views/shared/ErrorBoundary.tsx +95 -31
  154. package/src/views/shared/adminTokens.ts +28 -11
  155. package/src/views/shared/icons.tsx +146 -0
  156. package/src/views/shared/locales/en.json +15 -1
  157. package/src/views/shared/locales/fr.json +15 -1
  158. package/src/views/shared/viewAccess.ts +73 -0
  159. package/src/types/lucide-react.d.ts +0 -42
@@ -1,14 +1,14 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import { DefaultTemplate } from '@payloadcms/next/templates';
3
3
  import { redirect } from 'next/navigation';
4
+ import { supportViewRedirectTarget } from '../shared/viewAccess.js';
4
5
  import { AdminErrorBoundary } from '../shared/ErrorBoundary.js';
5
6
  import { CrmClient } from './client.js';
6
7
 
7
8
  const CrmView = ({ initPageResult }) => {
8
9
  const { req, visibleEntities } = initPageResult;
9
- if (!req.user) {
10
- redirect("/admin/login");
11
- }
10
+ const redirectTo = supportViewRedirectTarget(initPageResult);
11
+ if (redirectTo) redirect(redirectTo);
12
12
  return /* @__PURE__ */ jsx(
13
13
  DefaultTemplate,
14
14
  {
@@ -18,7 +18,7 @@ const CrmView = ({ initPageResult }) => {
18
18
  payload: req.payload,
19
19
  permissions: initPageResult.permissions,
20
20
  searchParams: {},
21
- user: req.user,
21
+ user: req.user ?? void 0,
22
22
  visibleEntities,
23
23
  children: /* @__PURE__ */ jsx(AdminErrorBoundary, { viewName: "CrmView", children: /* @__PURE__ */ jsx(CrmClient, {}) })
24
24
  }
@@ -2,6 +2,7 @@
2
2
  import { jsx, jsxs } from 'react/jsx-runtime';
3
3
  import React, { useState, useCallback, useEffect } from 'react';
4
4
  import { useTranslation } from '../../components/TicketConversation/hooks/useTranslation.js';
5
+ import { V } from '../shared/adminTokens.js';
5
6
  import { DATE_LOCALE } from '../shared/dateLocale.js';
6
7
  import s from '../../styles/EmailTracking.module.scss';
7
8
 
@@ -107,7 +108,7 @@ const EmailTrackingClient = () => {
107
108
  v,
108
109
  "j"
109
110
  ] }, v)) }),
110
- /* @__PURE__ */ jsx("input", { type: "text", className: s.searchInput, placeholder: t("emailTracking.searchPlaceholder"), value: search, onChange: (e) => setSearch(e.target.value) })
111
+ /* @__PURE__ */ jsx("input", { type: "text", className: s.searchInput, "aria-label": t("emailTracking.searchLabel"), placeholder: t("emailTracking.searchPlaceholder"), value: search, onChange: (e) => setSearch(e.target.value) })
111
112
  ] }),
112
113
  logs.length === 0 ? /* @__PURE__ */ jsx("div", { className: s.empty, children: t("emailTracking.noLogs") }) : /* @__PURE__ */ jsxs("table", { className: s.table, children: [
113
114
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
@@ -120,7 +121,7 @@ const EmailTrackingClient = () => {
120
121
  /* @__PURE__ */ jsx("th", { style: { textAlign: "right" }, children: t("emailTracking.tableHeaders.time") })
121
122
  ] }) }),
122
123
  /* @__PURE__ */ jsx("tbody", { children: logs.map((log) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
123
- /* @__PURE__ */ jsxs("tr", { onClick: () => log.status === "error" && log.errorMessage ? setExpandedRow(expandedRow === log.id ? null : log.id) : null, style: { cursor: log.status === "error" && log.errorMessage ? "pointer" : "default" }, children: [
124
+ /* @__PURE__ */ jsxs("tr", { children: [
124
125
  /* @__PURE__ */ jsx("td", { style: { fontSize: 12, whiteSpace: "nowrap" }, children: fmtDate(log.createdAt) }),
125
126
  /* @__PURE__ */ jsx("td", { style: { maxWidth: 200, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, title: log.recipientEmail, children: log.recipientEmail || "\u2014" }),
126
127
  /* @__PURE__ */ jsx("td", { style: { maxWidth: 250, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, title: log.subject, children: log.subject || "\u2014" }),
@@ -132,14 +133,25 @@ const EmailTrackingClient = () => {
132
133
  log.processingTimeMs,
133
134
  "ms"
134
135
  ] }) : "\u2014",
135
- log.status === "error" && log.errorMessage && /* @__PURE__ */ jsx("button", { className: s.expandBtn, onClick: (e) => {
136
- e.stopPropagation();
137
- setExpandedRow(expandedRow === log.id ? null : log.id);
138
- }, children: expandedRow === log.id ? "\u25B4" : "\u25BE" })
136
+ log.status === "error" && log.errorMessage && /* The row itself carries no handler: a `<tr onClick>` is
137
+ * unreachable by keyboard and role="button" on a row would
138
+ * destroy the table semantics. This button is the control. */
139
+ /* @__PURE__ */ jsx(
140
+ "button",
141
+ {
142
+ type: "button",
143
+ className: s.expandBtn,
144
+ "aria-expanded": expandedRow === log.id,
145
+ "aria-controls": `email-error-${log.id}`,
146
+ "aria-label": t("emailTracking.toggleError"),
147
+ onClick: () => setExpandedRow(expandedRow === log.id ? null : log.id),
148
+ children: expandedRow === log.id ? "\u25B4" : "\u25BE"
149
+ }
150
+ )
139
151
  ] })
140
152
  ] }),
141
- expandedRow === log.id && log.errorMessage && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: 7, children: /* @__PURE__ */ jsxs("div", { className: s.errorDetail, children: [
142
- "Erreur",
153
+ expandedRow === log.id && log.errorMessage && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: 7, children: /* @__PURE__ */ jsxs("div", { className: s.errorDetail, id: `email-error-${log.id}`, children: [
154
+ t("emailTracking.errorLabel"),
143
155
  log.httpStatus ? ` (HTTP ${log.httpStatus})` : "",
144
156
  ": ",
145
157
  log.errorMessage
@@ -147,8 +159,8 @@ const EmailTrackingClient = () => {
147
159
  ] }, log.id)) })
148
160
  ] }),
149
161
  totalDocs > 0 && /* @__PURE__ */ jsxs("div", { className: s.pagination, children: [
150
- /* @__PURE__ */ jsx("button", { className: s.pageBtn, onClick: () => setPage((p) => Math.max(1, p - 1)), disabled: page <= 1, children: t("common.previous") }),
151
- /* @__PURE__ */ jsxs("span", { style: { fontSize: 12, color: "var(--theme-elevation-500)", alignSelf: "center" }, children: [
162
+ /* @__PURE__ */ jsx("button", { type: "button", className: s.pageBtn, onClick: () => setPage((p) => Math.max(1, p - 1)), disabled: page <= 1, children: t("common.previous") }),
163
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: 12, color: V.textSecondary, alignSelf: "center" }, children: [
152
164
  t("common.page"),
153
165
  " ",
154
166
  page,
@@ -157,7 +169,7 @@ const EmailTrackingClient = () => {
157
169
  " ",
158
170
  t("common.results")
159
171
  ] }),
160
- /* @__PURE__ */ jsx("button", { className: s.pageBtn, onClick: () => setPage((p) => p + 1), disabled: !hasMore, children: t("common.next") })
172
+ /* @__PURE__ */ jsx("button", { type: "button", className: s.pageBtn, onClick: () => setPage((p) => p + 1), disabled: !hasMore, children: t("common.next") })
161
173
  ] })
162
174
  ] });
163
175
  };
@@ -1,14 +1,14 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import { DefaultTemplate } from '@payloadcms/next/templates';
3
3
  import { redirect } from 'next/navigation';
4
+ import { supportViewRedirectTarget } from '../shared/viewAccess.js';
4
5
  import { AdminErrorBoundary } from '../shared/ErrorBoundary.js';
5
6
  import { EmailTrackingClient } from './client.js';
6
7
 
7
8
  const EmailTrackingView = ({ initPageResult }) => {
8
9
  const { req, visibleEntities } = initPageResult;
9
- if (!req.user) {
10
- redirect("/admin/login");
11
- }
10
+ const redirectTo = supportViewRedirectTarget(initPageResult);
11
+ if (redirectTo) redirect(redirectTo);
12
12
  return /* @__PURE__ */ jsx(
13
13
  DefaultTemplate,
14
14
  {
@@ -18,7 +18,7 @@ const EmailTrackingView = ({ initPageResult }) => {
18
18
  payload: req.payload,
19
19
  permissions: initPageResult.permissions,
20
20
  searchParams: {},
21
- user: req.user,
21
+ user: req.user ?? void 0,
22
22
  visibleEntities,
23
23
  children: /* @__PURE__ */ jsx(AdminErrorBoundary, { viewName: "EmailTrackingView", children: /* @__PURE__ */ jsx(EmailTrackingClient, {}) })
24
24
  }
@@ -132,6 +132,7 @@ function ImportConversationClient() {
132
132
  ref: fileRef,
133
133
  type: "file",
134
134
  accept: ".md,.txt",
135
+ "aria-label": t("import.acceptedFormats"),
135
136
  onChange: onFileChange,
136
137
  style: { display: "none" }
137
138
  }
@@ -1,14 +1,14 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import { DefaultTemplate } from '@payloadcms/next/templates';
3
3
  import { redirect } from 'next/navigation';
4
+ import { supportViewRedirectTarget } from '../shared/viewAccess.js';
4
5
  import { AdminErrorBoundary } from '../shared/ErrorBoundary.js';
5
6
  import { ImportConversationClient } from './client.js';
6
7
 
7
8
  const ImportConversationView = ({ initPageResult }) => {
8
9
  const { req, visibleEntities } = initPageResult;
9
- if (!req.user) {
10
- redirect("/admin/login");
11
- }
10
+ const redirectTo = supportViewRedirectTarget(initPageResult);
11
+ if (redirectTo) redirect(redirectTo);
12
12
  return /* @__PURE__ */ jsx(
13
13
  DefaultTemplate,
14
14
  {
@@ -18,7 +18,7 @@ const ImportConversationView = ({ initPageResult }) => {
18
18
  payload: req.payload,
19
19
  permissions: initPageResult.permissions,
20
20
  searchParams: {},
21
- user: req.user,
21
+ user: req.user ?? void 0,
22
22
  visibleEntities,
23
23
  children: /* @__PURE__ */ jsx(AdminErrorBoundary, { viewName: "ImportConversationView", children: /* @__PURE__ */ jsx(ImportConversationClient, {}) })
24
24
  }
@@ -99,7 +99,7 @@ const LogsClient = () => {
99
99
  }, children: log.status === "success" ? t("logs.statusSuccess") : log.status === "error" ? t("logs.statusError") : t("logs.statusIgnored") }) }),
100
100
  /* @__PURE__ */ jsx("td", { className: s.truncate, title: log.recipientEmail, children: log.recipientEmail || "\u2014" }),
101
101
  /* @__PURE__ */ jsx("td", { className: s.truncate, title: log.subject, children: log.subject || "\u2014" }),
102
- /* @__PURE__ */ jsx("td", { style: { fontSize: 12, color: "var(--theme-elevation-500)" }, children: log.action || "\u2014" }),
102
+ /* @__PURE__ */ jsx("td", { style: { fontSize: 12, color: "var(--theme-elevation-650)" }, children: log.action || "\u2014" }),
103
103
  /* @__PURE__ */ jsx("td", { style: { textAlign: "right" }, children: log.processingTimeMs != null ? /* @__PURE__ */ jsxs("span", { className: s.mono, style: { color: log.processingTimeMs > 2e3 ? "#dc2626" : log.processingTimeMs > 500 ? "#d97706" : "#16a34a" }, children: [
104
104
  log.processingTimeMs,
105
105
  "ms"
@@ -1,12 +1,14 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import { DefaultTemplate } from '@payloadcms/next/templates';
3
3
  import { redirect } from 'next/navigation';
4
+ import { supportViewRedirectTarget } from '../shared/viewAccess.js';
4
5
  import { AdminErrorBoundary } from '../shared/ErrorBoundary.js';
5
6
  import { LogsClient } from './client.js';
6
7
 
7
8
  const LogsView = ({ initPageResult }) => {
8
9
  const { req, visibleEntities } = initPageResult;
9
- if (!req.user) redirect("/admin/login");
10
+ const redirectTo = supportViewRedirectTarget(initPageResult);
11
+ if (redirectTo) redirect(redirectTo);
10
12
  return /* @__PURE__ */ jsx(
11
13
  DefaultTemplate,
12
14
  {
@@ -16,7 +18,7 @@ const LogsView = ({ initPageResult }) => {
16
18
  payload: req.payload,
17
19
  permissions: initPageResult.permissions,
18
20
  searchParams: {},
19
- user: req.user,
21
+ user: req.user ?? void 0,
20
22
  visibleEntities,
21
23
  children: /* @__PURE__ */ jsx(AdminErrorBoundary, { viewName: "LogsView", children: /* @__PURE__ */ jsx(LogsClient, {}) })
22
24
  }
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
- import { useState, useEffect } from 'react';
3
+ import { useId, useState, useEffect } from 'react';
4
4
  import Link from 'next/link';
5
5
  import { useRouter } from 'next/navigation';
6
6
  import { useTranslation } from '../../components/TicketConversation/hooks/useTranslation.js';
@@ -22,6 +22,12 @@ const PRIORITY_KEYS = [
22
22
  ];
23
23
  const NewTicketClient = () => {
24
24
  const { t } = useTranslation();
25
+ const clientSearchId = useId();
26
+ const subjectId = useId();
27
+ const categoryId = useId();
28
+ const priorityId = useId();
29
+ const projectId_ = useId();
30
+ const descriptionId = useId();
25
31
  const router = useRouter();
26
32
  const [subject, setSubject] = useState("");
27
33
  const [description, setDescription] = useState("");
@@ -121,7 +127,7 @@ const NewTicketClient = () => {
121
127
  error && /* @__PURE__ */ jsx("div", { className: s.error, children: error }),
122
128
  /* @__PURE__ */ jsxs("form", { className: s.form, onSubmit: handleSubmit, children: [
123
129
  /* @__PURE__ */ jsxs("div", { className: s.fieldGroup, children: [
124
- /* @__PURE__ */ jsxs("label", { className: s.label, children: [
130
+ /* @__PURE__ */ jsxs("label", { className: s.label, htmlFor: clientSearchId, children: [
125
131
  t("newTicket.clientLabel"),
126
132
  " ",
127
133
  /* @__PURE__ */ jsx("span", { className: s.required, children: "*" })
@@ -134,7 +140,7 @@ const NewTicketClient = () => {
134
140
  " \u2014 ",
135
141
  selectedClient.company
136
142
  ] }),
137
- /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "var(--theme-elevation-500)" }, children: selectedClient.email }),
143
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "var(--theme-elevation-650)" }, children: selectedClient.email }),
138
144
  /* @__PURE__ */ jsx("button", { type: "button", onClick: () => {
139
145
  setSelectedClient(null);
140
146
  setClientId(null);
@@ -144,6 +150,7 @@ const NewTicketClient = () => {
144
150
  /* @__PURE__ */ jsx(
145
151
  "input",
146
152
  {
153
+ id: clientSearchId,
147
154
  type: "text",
148
155
  className: s.input,
149
156
  placeholder: t("newTicket.clientSearchPlaceholder"),
@@ -171,33 +178,33 @@ const NewTicketClient = () => {
171
178
  ] })
172
179
  ] }),
173
180
  /* @__PURE__ */ jsxs("div", { className: s.fieldGroup, children: [
174
- /* @__PURE__ */ jsxs("label", { className: s.label, children: [
181
+ /* @__PURE__ */ jsxs("label", { className: s.label, htmlFor: subjectId, children: [
175
182
  t("newTicket.subjectLabel"),
176
183
  " ",
177
184
  /* @__PURE__ */ jsx("span", { className: s.required, children: "*" })
178
185
  ] }),
179
- /* @__PURE__ */ jsx("input", { type: "text", className: s.input, placeholder: t("newTicket.subjectPlaceholder"), value: subject, onChange: (e) => setSubject(e.target.value) })
186
+ /* @__PURE__ */ jsx("input", { id: subjectId, type: "text", className: s.input, placeholder: t("newTicket.subjectPlaceholder"), value: subject, onChange: (e) => setSubject(e.target.value) })
180
187
  ] }),
181
188
  /* @__PURE__ */ jsxs("div", { className: s.row3, children: [
182
189
  /* @__PURE__ */ jsxs("div", { className: s.fieldGroup, children: [
183
- /* @__PURE__ */ jsx("label", { className: s.label, children: t("newTicket.categoryLabel") }),
184
- /* @__PURE__ */ jsx("select", { className: s.select, value: category, onChange: (e) => setCategory(e.target.value), children: CATEGORY_KEYS.map((c) => /* @__PURE__ */ jsx("option", { value: c.value, children: t(c.key) }, c.value)) })
190
+ /* @__PURE__ */ jsx("label", { className: s.label, htmlFor: categoryId, children: t("newTicket.categoryLabel") }),
191
+ /* @__PURE__ */ jsx("select", { id: categoryId, className: s.select, value: category, onChange: (e) => setCategory(e.target.value), children: CATEGORY_KEYS.map((c) => /* @__PURE__ */ jsx("option", { value: c.value, children: t(c.key) }, c.value)) })
185
192
  ] }),
186
193
  /* @__PURE__ */ jsxs("div", { className: s.fieldGroup, children: [
187
- /* @__PURE__ */ jsx("label", { className: s.label, children: t("newTicket.priorityLabel") }),
188
- /* @__PURE__ */ jsx("select", { className: s.select, value: priority, onChange: (e) => setPriority(e.target.value), children: PRIORITY_KEYS.map((p) => /* @__PURE__ */ jsx("option", { value: p.value, children: t(p.key) }, p.value)) })
194
+ /* @__PURE__ */ jsx("label", { className: s.label, htmlFor: priorityId, children: t("newTicket.priorityLabel") }),
195
+ /* @__PURE__ */ jsx("select", { id: priorityId, className: s.select, value: priority, onChange: (e) => setPriority(e.target.value), children: PRIORITY_KEYS.map((p) => /* @__PURE__ */ jsx("option", { value: p.value, children: t(p.key) }, p.value)) })
189
196
  ] }),
190
197
  /* @__PURE__ */ jsxs("div", { className: s.fieldGroup, children: [
191
- /* @__PURE__ */ jsx("label", { className: s.label, children: t("newTicket.projectLabel") }),
192
- /* @__PURE__ */ jsxs("select", { className: s.select, value: projectId || "", onChange: (e) => setProjectId(e.target.value ? Number(e.target.value) : null), children: [
198
+ /* @__PURE__ */ jsx("label", { className: s.label, htmlFor: projectId_, children: t("newTicket.projectLabel") }),
199
+ /* @__PURE__ */ jsxs("select", { id: projectId_, className: s.select, value: projectId || "", onChange: (e) => setProjectId(e.target.value ? Number(e.target.value) : null), children: [
193
200
  /* @__PURE__ */ jsx("option", { value: "", children: t("newTicket.noProject") }),
194
201
  projects.map((p) => /* @__PURE__ */ jsx("option", { value: p.id, children: p.name }, p.id))
195
202
  ] })
196
203
  ] })
197
204
  ] }),
198
205
  /* @__PURE__ */ jsxs("div", { className: s.fieldGroup, children: [
199
- /* @__PURE__ */ jsx("label", { className: s.label, children: t("newTicket.descriptionLabel") }),
200
- /* @__PURE__ */ jsx("textarea", { className: s.textarea, placeholder: t("newTicket.descriptionPlaceholder"), value: description, onChange: (e) => setDescription(e.target.value) })
206
+ /* @__PURE__ */ jsx("label", { className: s.label, htmlFor: descriptionId, children: t("newTicket.descriptionLabel") }),
207
+ /* @__PURE__ */ jsx("textarea", { id: descriptionId, className: s.textarea, placeholder: t("newTicket.descriptionPlaceholder"), value: description, onChange: (e) => setDescription(e.target.value) })
201
208
  ] }),
202
209
  /* @__PURE__ */ jsx("button", { type: "submit", className: s.submitBtn, disabled: submitting, children: submitting ? t("newTicket.submitting") : t("newTicket.submitButton") })
203
210
  ] })
@@ -1,12 +1,14 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import { DefaultTemplate } from '@payloadcms/next/templates';
3
3
  import { redirect } from 'next/navigation';
4
+ import { supportViewRedirectTarget } from '../shared/viewAccess.js';
4
5
  import { AdminErrorBoundary } from '../shared/ErrorBoundary.js';
5
6
  import { NewTicketClient } from './client.js';
6
7
 
7
8
  const NewTicketView = ({ initPageResult }) => {
8
9
  const { req, visibleEntities } = initPageResult;
9
- if (!req.user) redirect("/admin/login");
10
+ const redirectTo = supportViewRedirectTarget(initPageResult);
11
+ if (redirectTo) redirect(redirectTo);
10
12
  return /* @__PURE__ */ jsx(
11
13
  DefaultTemplate,
12
14
  {
@@ -16,7 +18,7 @@ const NewTicketView = ({ initPageResult }) => {
16
18
  payload: req.payload,
17
19
  permissions: initPageResult.permissions,
18
20
  searchParams: {},
19
- user: req.user,
21
+ user: req.user ?? void 0,
20
22
  visibleEntities,
21
23
  children: /* @__PURE__ */ jsx(AdminErrorBoundary, { viewName: "NewTicketView", children: /* @__PURE__ */ jsx(NewTicketClient, {}) })
22
24
  }
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import { useState, useCallback, useEffect } from 'react';
4
- import { Inbox, Paperclip, ChevronUp, ChevronDown, Plus, Link2, X, Search } from 'lucide-react';
4
+ import { Inbox, Paperclip, ChevronUp, ChevronDown, Plus, Link2, X, Search } from '../shared/icons.js';
5
5
  import { SkeletonDashboard } from '../shared/Skeleton.js';
6
6
  import { useTranslation } from '../../components/TicketConversation/hooks/useTranslation.js';
7
7
  import styles from '../../styles/PendingEmails.module.scss';
@@ -52,7 +52,7 @@ function TicketSearchModal({
52
52
  return /* @__PURE__ */ jsx("div", { className: styles.overlay, onClick: onClose, children: /* @__PURE__ */ jsxs("div", { className: styles.modal, onClick: (e) => e.stopPropagation(), children: [
53
53
  /* @__PURE__ */ jsxs("div", { className: styles.modalHeader, children: [
54
54
  /* @__PURE__ */ jsx("h3", { className: styles.modalTitle, children: "Rattacher a un ticket" }),
55
- /* @__PURE__ */ jsx("button", { onClick: onClose, className: styles.modalClose, children: /* @__PURE__ */ jsx(X, { size: 20 }) })
55
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Fermer", onClick: onClose, className: styles.modalClose, children: /* @__PURE__ */ jsx(X, { size: 20 }) })
56
56
  ] }),
57
57
  suggestions.length > 0 && /* @__PURE__ */ jsxs("div", { className: styles.sectionBlock, children: [
58
58
  /* @__PURE__ */ jsx("div", { className: styles.sectionLabel, children: "Suggestions" }),
@@ -71,6 +71,7 @@ function TicketSearchModal({
71
71
  "input",
72
72
  {
73
73
  type: "text",
74
+ "aria-label": "Rechercher un ticket",
74
75
  placeholder: "Rechercher un ticket (TK-0042, sujet...)...",
75
76
  value: search,
76
77
  onChange: (e) => setSearch(e.target.value),
@@ -163,7 +164,7 @@ function ClientPickerModal({
163
164
  return /* @__PURE__ */ jsx("div", { className: styles.overlay, onClick: onClose, children: /* @__PURE__ */ jsxs("div", { className: styles.modal, onClick: (e) => e.stopPropagation(), children: [
164
165
  /* @__PURE__ */ jsxs("div", { className: styles.modalHeader, children: [
165
166
  /* @__PURE__ */ jsx("h3", { className: styles.modalTitle, children: "Choisir un client" }),
166
- /* @__PURE__ */ jsx("button", { onClick: onClose, className: styles.modalClose, children: /* @__PURE__ */ jsx(X, { size: 20 }) })
167
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Fermer", onClick: onClose, className: styles.modalClose, children: /* @__PURE__ */ jsx(X, { size: 20 }) })
167
168
  ] }),
168
169
  detectedClient && /* @__PURE__ */ jsxs("div", { className: styles.sectionBlock, children: [
169
170
  /* @__PURE__ */ jsx("div", { className: styles.sectionLabel, children: "Client detecte" }),
@@ -181,6 +182,7 @@ function ClientPickerModal({
181
182
  "input",
182
183
  {
183
184
  type: "text",
185
+ "aria-label": "Rechercher un client",
184
186
  placeholder: "Rechercher un client (nom, email, entreprise)...",
185
187
  value: search,
186
188
  onChange: (e) => setSearch(e.target.value),
@@ -205,6 +207,7 @@ function ClientPickerModal({
205
207
  "input",
206
208
  {
207
209
  type: "text",
210
+ "aria-label": "Prenom",
208
211
  placeholder: "Prenom *",
209
212
  value: newClient.firstName,
210
213
  onChange: (e) => setNewClient((p) => ({ ...p, firstName: e.target.value })),
@@ -215,6 +218,7 @@ function ClientPickerModal({
215
218
  "input",
216
219
  {
217
220
  type: "text",
221
+ "aria-label": "Nom",
218
222
  placeholder: "Nom",
219
223
  value: newClient.lastName,
220
224
  onChange: (e) => setNewClient((p) => ({ ...p, lastName: e.target.value })),
@@ -226,6 +230,7 @@ function ClientPickerModal({
226
230
  "input",
227
231
  {
228
232
  type: "email",
233
+ "aria-label": "Email",
229
234
  placeholder: "Email *",
230
235
  value: newClient.email,
231
236
  onChange: (e) => setNewClient((p) => ({ ...p, email: e.target.value })),
@@ -236,6 +241,7 @@ function ClientPickerModal({
236
241
  "input",
237
242
  {
238
243
  type: "text",
244
+ "aria-label": "Entreprise",
239
245
  placeholder: "Entreprise *",
240
246
  value: newClient.company,
241
247
  onChange: (e) => setNewClient((p) => ({ ...p, company: e.target.value })),
@@ -1,14 +1,14 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import { DefaultTemplate } from '@payloadcms/next/templates';
3
3
  import { redirect } from 'next/navigation';
4
+ import { supportViewRedirectTarget } from '../shared/viewAccess.js';
4
5
  import { AdminErrorBoundary } from '../shared/ErrorBoundary.js';
5
6
  import { PendingEmailsClient } from './client.js';
6
7
 
7
8
  const PendingEmailsView = ({ initPageResult }) => {
8
9
  const { req, visibleEntities } = initPageResult;
9
- if (!req.user) {
10
- redirect("/admin/login");
11
- }
10
+ const redirectTo = supportViewRedirectTarget(initPageResult);
11
+ if (redirectTo) redirect(redirectTo);
12
12
  return /* @__PURE__ */ jsx(
13
13
  DefaultTemplate,
14
14
  {
@@ -18,7 +18,7 @@ const PendingEmailsView = ({ initPageResult }) => {
18
18
  payload: req.payload,
19
19
  permissions: initPageResult.permissions,
20
20
  searchParams: {},
21
- user: req.user,
21
+ user: req.user ?? void 0,
22
22
  visibleEntities,
23
23
  children: /* @__PURE__ */ jsx(AdminErrorBoundary, { viewName: "PendingEmailsView", children: /* @__PURE__ */ jsx(PendingEmailsClient, {}) })
24
24
  }
@@ -372,25 +372,23 @@ const SupportDashboardClient = () => {
372
372
  /* @__PURE__ */ jsx("th", { children: t("dashboard.tableHeaders.modified") }),
373
373
  /* @__PURE__ */ jsx("th", {})
374
374
  ] }) }),
375
- /* @__PURE__ */ jsx("tbody", { children: tickets.map((tk) => /* @__PURE__ */ jsxs(
376
- "tr",
377
- {
378
- onClick: () => {
379
- window.location.href = `/admin/support/ticket?id=${tk.id}`;
380
- },
381
- children: [
382
- /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("span", { className: `${styles.statusDot} ${getStatusDotClass(tk.status)}` }) }),
383
- /* @__PURE__ */ jsxs("td", { className: styles.ticketNum, children: [
384
- "#",
385
- tk.ticketNumber
386
- ] }),
387
- /* @__PURE__ */ jsx("td", { className: styles.ticketSubject, children: tk.subject }),
388
- /* @__PURE__ */ jsx("td", { className: styles.ticketClient, children: getClientName(tk) }),
389
- /* @__PURE__ */ jsx("td", { className: styles.ticketTime, children: timeAgo(tk.updatedAt) }),
390
- /* @__PURE__ */ jsx("td", { className: styles.ticketArrow, children: "\u2192" })
391
- ]
392
- },
393
- tk.id
375
+ /* @__PURE__ */ jsx("tbody", { children: tickets.map((tk) => (
376
+ /*
377
+ * The row is not the control: a `<tr onClick>` is invisible to
378
+ * the keyboard, and turning it into role="button" would cost
379
+ * the table semantics. The link lives in the subject cell.
380
+ */
381
+ /* @__PURE__ */ jsxs("tr", { children: [
382
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("span", { className: `${styles.statusDot} ${getStatusDotClass(tk.status)}` }) }),
383
+ /* @__PURE__ */ jsxs("td", { className: styles.ticketNum, children: [
384
+ "#",
385
+ tk.ticketNumber
386
+ ] }),
387
+ /* @__PURE__ */ jsx("td", { className: styles.ticketSubject, children: /* @__PURE__ */ jsx("a", { className: styles.ticketLink, href: `/admin/support/ticket?id=${tk.id}`, children: tk.subject }) }),
388
+ /* @__PURE__ */ jsx("td", { className: styles.ticketClient, children: getClientName(tk) }),
389
+ /* @__PURE__ */ jsx("td", { className: styles.ticketTime, children: timeAgo(tk.updatedAt) }),
390
+ /* @__PURE__ */ jsx("td", { className: styles.ticketArrow, children: "\u2192" })
391
+ ] }, tk.id)
394
392
  )) })
395
393
  ] })
396
394
  ] }),
@@ -1,14 +1,14 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import { DefaultTemplate } from '@payloadcms/next/templates';
3
3
  import { redirect } from 'next/navigation';
4
+ import { supportViewRedirectTarget } from '../shared/viewAccess.js';
4
5
  import { AdminErrorBoundary } from '../shared/ErrorBoundary.js';
5
6
  import { SupportDashboardClient } from './client.js';
6
7
 
7
8
  const SupportDashboardView = ({ initPageResult }) => {
8
9
  const { req, visibleEntities } = initPageResult;
9
- if (!req.user) {
10
- redirect("/admin/login");
11
- }
10
+ const redirectTo = supportViewRedirectTarget(initPageResult);
11
+ if (redirectTo) redirect(redirectTo);
12
12
  return /* @__PURE__ */ jsx(
13
13
  DefaultTemplate,
14
14
  {
@@ -18,7 +18,7 @@ const SupportDashboardView = ({ initPageResult }) => {
18
18
  payload: req.payload,
19
19
  permissions: initPageResult.permissions,
20
20
  searchParams: {},
21
- user: req.user,
21
+ user: req.user ?? void 0,
22
22
  visibleEntities,
23
23
  children: /* @__PURE__ */ jsx(AdminErrorBoundary, { viewName: "SupportDashboardView", children: /* @__PURE__ */ jsx(SupportDashboardClient, {}) })
24
24
  }