@consilioweb/payload-support 5.0.0 → 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/README.md +249 -22
  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 +306 -60
  15. package/dist/index.d.cts +107 -38
  16. package/dist/index.d.ts +107 -38
  17. package/dist/index.js +300 -59
  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.js +20 -0
  34. package/dist/utils/readSettings.js +150 -0
  35. package/dist/views/BillingView/client.js +15 -8
  36. package/dist/views/ChatView/client.js +2 -0
  37. package/dist/views/CrmView/client.js +2 -0
  38. package/dist/views/EmailTrackingView/client.js +23 -11
  39. package/dist/views/ImportConversationView/client.js +1 -0
  40. package/dist/views/LogsView/client.js +1 -1
  41. package/dist/views/NewTicketView/client.js +20 -13
  42. package/dist/views/PendingEmailsView/client.js +9 -3
  43. package/dist/views/SupportDashboardView/client.js +17 -19
  44. package/dist/views/TicketDetailView/client.js +23 -11
  45. package/dist/views/TicketInboxView/client.js +52 -26
  46. package/dist/views/TicketingSettingsView/TicketingSettings.module.scss +7 -7
  47. package/dist/views/TicketingSettingsView/client.js +44 -26
  48. package/dist/views/TimeDashboardView/client.js +10 -7
  49. package/dist/views/shared/ErrorBoundary.d.ts +36 -1
  50. package/dist/views/shared/ErrorBoundary.js +59 -28
  51. package/dist/views/shared/adminTokens.d.ts +15 -5
  52. package/dist/views/shared/adminTokens.js +23 -7
  53. package/dist/views/shared/icons.d.ts +49 -0
  54. package/dist/views/shared/icons.js +92 -0
  55. package/dist/views/shared/locales/en.json +15 -1
  56. package/dist/views/shared/locales/fr.json +15 -1
  57. package/package.json +9 -4
  58. package/scripts/copy-subpath-types.mjs +103 -0
  59. package/scripts/uninstall-data.mjs +178 -0
  60. package/scripts/uninstall.mjs +184 -0
  61. package/scripts/verify-dist-imports.mjs +84 -0
  62. package/src/collections/Tickets.ts +7 -0
  63. package/src/collections/TimeEntries.ts +11 -3
  64. package/src/components/RichTextEditor/index.tsx +1 -1
  65. package/src/components/TicketConversation/RewriteDropdown.tsx +1 -1
  66. package/src/components/TicketConversation/components/AISummaryPanel.tsx +3 -0
  67. package/src/components/TicketConversation/components/ActionPanels.tsx +13 -8
  68. package/src/components/TicketConversation/components/ClientHistory.tsx +1 -0
  69. package/src/components/TicketConversation/components/CodeBlock.tsx +1 -0
  70. package/src/components/TicketConversation/components/CodeBlockInserter.tsx +1 -1
  71. package/src/components/TicketConversation/components/QuickActions.tsx +10 -8
  72. package/src/components/TicketConversation/components/TicketHeader.tsx +2 -0
  73. package/src/components/TicketConversation/components/TimeTrackingPanel.tsx +17 -9
  74. package/src/components/TicketConversation/index.tsx +41 -7
  75. package/src/endpoints/delete-account.ts +191 -47
  76. package/src/endpoints/export-data.ts +104 -4
  77. package/src/endpoints/purge-logs.ts +6 -15
  78. package/src/index.ts +2 -0
  79. package/src/plugin.ts +51 -1
  80. package/src/portal/LiveChat.tsx +1 -1
  81. package/src/portal/auth/profile/page.tsx +3 -2
  82. package/src/portal/icons.tsx +91 -0
  83. package/src/styles/BillingView.module.scss +11 -11
  84. package/src/styles/ChatView.module.scss +7 -7
  85. package/src/styles/CommandPalette.module.scss +1 -1
  86. package/src/styles/CrmView.module.scss +9 -9
  87. package/src/styles/EmailTracking.module.scss +6 -6
  88. package/src/styles/ImportConversation.module.scss +5 -5
  89. package/src/styles/Logs.module.scss +5 -5
  90. package/src/styles/NewTicket.module.scss +2 -2
  91. package/src/styles/PendingEmails.module.scss +13 -13
  92. package/src/styles/SupportDashboard.module.scss +24 -8
  93. package/src/styles/TicketDetail.module.scss +20 -20
  94. package/src/styles/TicketInbox.module.scss +6 -6
  95. package/src/styles/TicketingSettings.module.scss +10 -10
  96. package/src/styles/TimeDashboard.module.scss +6 -6
  97. package/src/styles/_tokens.scss +3 -1
  98. package/src/types.ts +20 -0
  99. package/src/utils/retention.ts +111 -0
  100. package/src/utils/slugs.ts +15 -0
  101. package/src/views/BillingView/client.tsx +17 -8
  102. package/src/views/ChatView/client.tsx +2 -0
  103. package/src/views/CrmView/client.tsx +2 -0
  104. package/src/views/EmailTrackingView/client.tsx +18 -7
  105. package/src/views/ImportConversationView/client.tsx +1 -0
  106. package/src/views/LogsView/client.tsx +1 -1
  107. package/src/views/NewTicketView/client.tsx +22 -13
  108. package/src/views/PendingEmailsView/client.tsx +9 -3
  109. package/src/views/SupportDashboardView/client.tsx +11 -5
  110. package/src/views/TicketDetailView/client.tsx +25 -11
  111. package/src/views/TicketInboxView/client.tsx +47 -7
  112. package/src/views/TicketingSettingsView/TicketingSettings.module.scss +7 -7
  113. package/src/views/TicketingSettingsView/client.tsx +201 -144
  114. package/src/views/TimeDashboardView/client.tsx +10 -7
  115. package/src/views/shared/ErrorBoundary.tsx +95 -31
  116. package/src/views/shared/adminTokens.ts +28 -11
  117. package/src/views/shared/icons.tsx +146 -0
  118. package/src/views/shared/locales/en.json +15 -1
  119. package/src/views/shared/locales/fr.json +15 -1
  120. package/src/types/lucide-react.d.ts +0 -42
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import React, { useState, useEffect, useCallback, useRef } from 'react'
3
+ import React, { useState, useEffect, useCallback, useId, useRef } from 'react'
4
4
  import { useSearchParams } from 'next/navigation'
5
5
  import Link from 'next/link'
6
6
  import { RichTextEditor, type RichTextEditorHandle } from '../../components/RichTextEditor/index'
@@ -26,6 +26,13 @@ export const TicketDetailClient: React.FC = () => {
26
26
  const threadEndRef = useRef<HTMLDivElement>(null)
27
27
  const dropdownRef = useRef<HTMLDivElement>(null)
28
28
  const fileInputRef = useRef<HTMLInputElement>(null)
29
+ // Generated ids so every control below carries the name of the text next to
30
+ // it. useId, never a literal: this view can be mounted more than once.
31
+ const manualTimeId = useId()
32
+ const billingTypeId = useId()
33
+ const flatAmountId = useId()
34
+ const billedAmountId = useId()
35
+ const paymentStatusId = useId()
29
36
 
30
37
  const [ticket, setTicket] = useState<Record<string, unknown> | null>(null)
31
38
  const [messages, setMessages] = useState<Message[]>([])
@@ -721,7 +728,7 @@ const [clientTyping, setClientTyping] = useState(false)
721
728
  const file = typeof att.file === 'object' ? att.file : null
722
729
  if (!file) return null
723
730
  return (file.mimeType || '').startsWith('image/')
724
- ? <a key={i} href={file.url || '#'} target="_blank" rel="noopener noreferrer"><img src={file.url || ''} alt="" className={s.attachmentImg} /></a>
731
+ ? <a key={i} href={file.url || '#'} target="_blank" rel="noopener noreferrer"><img src={file.url || ''} alt={file.filename || t('detail.file')} className={s.attachmentImg} /></a>
725
732
  : <a key={i} href={file.url || '#'} target="_blank" rel="noopener noreferrer" className={s.attachmentFile}>PJ {file.filename || 'Fichier'}</a>
726
733
  })}
727
734
  </div>
@@ -810,7 +817,7 @@ const [clientTyping, setClientTyping] = useState(false)
810
817
  />
811
818
  {/* #5 — File upload button */}
812
819
  <button className={s.toolbarBtn} data-tooltip={t('detail.file')} aria-label={t('detail.file')} onClick={() => fileInputRef.current?.click()} style={{ fontSize: 11, fontWeight: 700, padding: '4px 10px', width: 'auto' }}>📎 {t('detail.file')}</button>
813
- <input ref={fileInputRef} type="file" multiple className={s.hiddenFileInput} onChange={(e) => handleFileSelect(e.target.files)} />
820
+ <input ref={fileInputRef} type="file" multiple aria-label={t('detail.file')} className={s.hiddenFileInput} onChange={(e) => handleFileSelect(e.target.files)} />
814
821
  {macros.length > 0 && (
815
822
  <select
816
823
  className={s.cannedSelect}
@@ -871,6 +878,7 @@ const [clientTyping, setClientTyping] = useState(false)
871
878
  <div className={s.composerFooter}>
872
879
  <div className={s.composerOptions}>
873
880
  <select
881
+ aria-label={t('detail.sendAs')}
874
882
  value={sendAsClient ? 'client' : 'admin'}
875
883
  onChange={(e) => {
876
884
  const asClient = e.target.value === 'client'
@@ -1089,9 +1097,9 @@ const [clientTyping, setClientTyping] = useState(false)
1089
1097
  </div>
1090
1098
  {/* Manual time entry */}
1091
1099
  <div style={{ display: 'flex', gap: 6, marginTop: 8, alignItems: 'center' }}>
1092
- <input type="number" min="1" placeholder="min" style={{ width: 60, padding: '4px 8px', borderRadius: 6, border: '1px solid var(--theme-elevation-200)', fontSize: 12, color: 'var(--theme-text)', background: 'var(--theme-elevation-0)' }} id="manual-time-input" />
1100
+ <input type="number" min="1" placeholder="min" aria-label={t('detail.manualMinutes')} style={{ width: 60, padding: '4px 8px', borderRadius: 6, border: '1px solid var(--theme-elevation-200)', fontSize: 12, color: 'var(--theme-text)', background: 'var(--theme-elevation-0)' }} id={manualTimeId} />
1093
1101
  <button className={`${s.timerBtn} ${s.timerBtnGhost}`} onClick={async () => {
1094
- const input = document.getElementById('manual-time-input') as HTMLInputElement
1102
+ const input = document.getElementById(manualTimeId) as HTMLInputElement
1095
1103
  const mins = Number(input?.value)
1096
1104
  if (!mins || mins < 1 || !ticketId) return
1097
1105
  try {
@@ -1102,7 +1110,7 @@ const [clientTyping, setClientTyping] = useState(false)
1102
1110
  }} style={{ fontSize: 11 }}>{t('detail.addTime')}</button>
1103
1111
  </div>
1104
1112
  {/* Billing info */}
1105
- <div style={{ marginTop: 8, fontSize: 11, color: 'var(--theme-elevation-500)' }}>
1113
+ <div style={{ marginTop: 8, fontSize: 11, color: 'var(--theme-elevation-650)' }}>
1106
1114
  <div style={{ display: 'flex', justifyContent: 'space-between', padding: '2px 0', alignItems: 'center' }}>
1107
1115
  <span>{t('detail.billing.billable')}</span>
1108
1116
  <button
@@ -1126,7 +1134,7 @@ const [clientTyping, setClientTyping] = useState(false)
1126
1134
  {timeEntries.length > 0 && (
1127
1135
  <div style={{ marginTop: 8, fontSize: 11 }}>
1128
1136
  {timeEntries.slice(0, 6).map((e) => (
1129
- <div key={e.id} style={{ display: 'flex', justifyContent: 'space-between', padding: '3px 0', color: 'var(--theme-elevation-500)' }}>
1137
+ <div key={e.id} style={{ display: 'flex', justifyContent: 'space-between', padding: '3px 0', color: 'var(--theme-elevation-650)' }}>
1130
1138
  <span>{new Date(e.date).toLocaleDateString(DATE_LOCALE, { day: 'numeric', month: 'short' })}</span>
1131
1139
  <span title={e.description} style={{ fontWeight: 600, cursor: e.description ? 'help' : 'default' }}>{e.duration}min</span>
1132
1140
  </div>
@@ -1149,6 +1157,7 @@ const [clientTyping, setClientTyping] = useState(false)
1149
1157
  {addingTag ? (
1150
1158
  <input
1151
1159
  className={s.tagInput}
1160
+ aria-label={t('detail.addTag')}
1152
1161
  value={newTagValue}
1153
1162
  onChange={(e) => setNewTagValue(e.target.value)}
1154
1163
  onKeyDown={(e) => { if (e.key === 'Enter') handleAddTag(); if (e.key === 'Escape') { setAddingTag(false); setNewTagValue('') } }}
@@ -1167,8 +1176,9 @@ const [clientTyping, setClientTyping] = useState(false)
1167
1176
  <div className={s.sideSectionTitle}>{t('detail.billing.title')}</div>
1168
1177
  <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
1169
1178
  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
1170
- <span style={{ fontSize: 12 }}>{t('detail.billing.type')}</span>
1179
+ <label style={{ fontSize: 12 }} htmlFor={billingTypeId}>{t('detail.billing.type')}</label>
1171
1180
  <select
1181
+ id={billingTypeId}
1172
1182
  value={(ticket as any)?.billingType || 'hourly'}
1173
1183
  onChange={async (e) => {
1174
1184
  try {
@@ -1188,9 +1198,10 @@ const [clientTyping, setClientTyping] = useState(false)
1188
1198
  </div>
1189
1199
  {(ticket as any)?.billingType === 'flat' && (
1190
1200
  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
1191
- <span style={{ fontSize: 12 }}>{t('detail.billing.flatAmount')}</span>
1201
+ <label style={{ fontSize: 12 }} htmlFor={flatAmountId}>{t('detail.billing.flatAmount')}</label>
1192
1202
  <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
1193
1203
  <input
1204
+ id={flatAmountId}
1194
1205
  type="number"
1195
1206
  defaultValue={(ticket as any)?.flatRateAmount ?? ''}
1196
1207
  placeholder="0"
@@ -1212,9 +1223,10 @@ const [clientTyping, setClientTyping] = useState(false)
1212
1223
  </div>
1213
1224
  )}
1214
1225
  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
1215
- <span style={{ fontSize: 12 }}>{t('detail.billing.billedAmount')}</span>
1226
+ <label style={{ fontSize: 12 }} htmlFor={billedAmountId}>{t('detail.billing.billedAmount')}</label>
1216
1227
  <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
1217
1228
  <input
1229
+ id={billedAmountId}
1218
1230
  type="number"
1219
1231
  defaultValue={(ticket as any)?.billedAmount ?? ''}
1220
1232
  placeholder="0"
@@ -1235,8 +1247,9 @@ const [clientTyping, setClientTyping] = useState(false)
1235
1247
  </div>
1236
1248
  </div>
1237
1249
  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
1238
- <span style={{ fontSize: 12 }}>{t('detail.billing.payment')}</span>
1250
+ <label style={{ fontSize: 12 }} htmlFor={paymentStatusId}>{t('detail.billing.payment')}</label>
1239
1251
  <select
1252
+ id={paymentStatusId}
1240
1253
  value={(ticket as any)?.paymentStatus || 'unpaid'}
1241
1254
  onChange={async (e) => {
1242
1255
  try {
@@ -1358,6 +1371,7 @@ const [clientTyping, setClientTyping] = useState(false)
1358
1371
  <div className={s.splitPreview}>{splitModal.preview}</div>
1359
1372
  <input
1360
1373
  className={s.splitInput}
1374
+ aria-label={t('detail.split.subjectLabel')}
1361
1375
  value={splitSubject}
1362
1376
  onChange={(e) => setSplitSubject(e.target.value)}
1363
1377
  onKeyDown={(e) => { if (e.key === 'Enter') handleSplitConfirm() }}
@@ -31,6 +31,9 @@ interface Ticket {
31
31
 
32
32
  type Tab = 'all' | 'open' | 'waiting_client' | 'resolved' | 'sla_breach'
33
33
 
34
+ // Single id: this view is a full admin route, mounted once.
35
+ const INBOX_TABPANEL_ID = 'inbox-tabpanel'
36
+
34
37
  const PRIORITY_COLORS: Record<string, string> = {
35
38
  urgent: 'var(--theme-error-500)',
36
39
  high: 'var(--theme-warning-500)',
@@ -239,6 +242,7 @@ export const TicketInboxClient: React.FC = () => {
239
242
  <input
240
243
  type="text"
241
244
  className={s.searchInput}
245
+ aria-label={t('inbox.searchLabel')}
242
246
  placeholder={t('inbox.searchPlaceholder')}
243
247
  value={search}
244
248
  onChange={(e) => setSearch(e.target.value)}
@@ -250,14 +254,48 @@ export const TicketInboxClient: React.FC = () => {
250
254
  </div>
251
255
 
252
256
  {/* Tabs */}
253
- <div className={s.tabs} role="tablist">
257
+ {/*
258
+ A tablist is ONE tab stop: the inactive tabs carry `tabIndex={-1}` and
259
+ the arrow keys move between them. That is the ARIA pattern, but the
260
+ roving tabIndex is only half of it — without this handler the four
261
+ inactive tabs are simply unreachable from the keyboard, which is worse
262
+ than the plain tab order it replaced.
263
+ */}
264
+ <div
265
+ className={s.tabs}
266
+ role="tablist"
267
+ aria-label={t('inbox.tabsLabel')}
268
+ onKeyDown={(e) => {
269
+ const keys = ['ArrowLeft', 'ArrowRight', 'Home', 'End']
270
+ if (!keys.includes(e.key)) return
271
+ e.preventDefault()
272
+ const i = tabs.findIndex((tk) => tk.key === tab)
273
+ const next =
274
+ e.key === 'Home' ? 0
275
+ : e.key === 'End' ? tabs.length - 1
276
+ : e.key === 'ArrowLeft' ? (i - 1 + tabs.length) % tabs.length
277
+ : (i + 1) % tabs.length
278
+ const target = tabs[next]
279
+ if (!target) return
280
+ setTab(target.key)
281
+ setSelectedIdx(-1)
282
+ // Focus follows selection, as the pattern expects for an
283
+ // automatically-activated tablist.
284
+ document.getElementById(`inbox-tab-${target.key}`)?.focus()
285
+ }}
286
+ >
254
287
  {tabs.map((tk) => {
255
288
  const isAlert = tk.alert && tk.count > 0
256
289
  return (
257
290
  <button
258
291
  key={tk.key}
292
+ type="button"
259
293
  role="tab"
294
+ id={`inbox-tab-${tk.key}`}
260
295
  aria-selected={tab === tk.key}
296
+ aria-controls={INBOX_TABPANEL_ID}
297
+ // Roving tabindex: a tablist is one tab stop, arrows move inside.
298
+ tabIndex={tab === tk.key ? 0 : -1}
261
299
  className={`${s.tab} ${tab === tk.key ? s.tabActive : ''} ${isAlert ? s.tabAlert : ''}`}
262
300
  onClick={() => { setTab(tk.key); setSelectedIdx(-1) }}
263
301
  >
@@ -273,17 +311,17 @@ export const TicketInboxClient: React.FC = () => {
273
311
  {checkedIds.size > 0 ? (
274
312
  <div style={{ display: 'flex', gap: 8, alignItems: 'center', flex: 1 }}>
275
313
  <span style={{ fontSize: 13, fontWeight: 600, color: 'var(--theme-text)' }}>{checkedIds.size > 1 ? t('inbox.selectedPlural', { count: String(checkedIds.size) }) : t('inbox.selected', { count: String(checkedIds.size) })}</span>
276
- <button className={s.sortSelect} onClick={() => handleBulkAction('close')} disabled={bulkProcessing}>{t('inbox.closeAction')}</button>
277
- <button className={s.sortSelect} onClick={() => handleBulkAction('reopen')} disabled={bulkProcessing}>{t('inbox.reopenAction')}</button>
278
- <select className={s.sortSelect} value={bulkAction} onChange={(e) => { if (e.target.value) handleBulkAction(e.target.value); setBulkAction('') }}>
314
+ <button type="button" className={s.sortSelect} onClick={() => handleBulkAction('close')} disabled={bulkProcessing}>{t('inbox.closeAction')}</button>
315
+ <button type="button" className={s.sortSelect} onClick={() => handleBulkAction('reopen')} disabled={bulkProcessing}>{t('inbox.reopenAction')}</button>
316
+ <select className={s.sortSelect} aria-label={t('inbox.moreActions')} value={bulkAction} onChange={(e) => { if (e.target.value) handleBulkAction(e.target.value); setBulkAction('') }}>
279
317
  <option value="">{t('inbox.moreActions')}</option>
280
318
  <option value="set_priority">{t('inbox.changePriority')}</option>
281
319
  <option value="delete">{t('inbox.deleteAction')}</option>
282
320
  </select>
283
- <button className={s.sortSelect} onClick={() => setCheckedIds(new Set())} style={{ marginLeft: 'auto' }}>{t('inbox.deselect')}</button>
321
+ <button type="button" className={s.sortSelect} onClick={() => setCheckedIds(new Set())} style={{ marginLeft: 'auto' }}>{t('inbox.deselect')}</button>
284
322
  </div>
285
323
  ) : (
286
- <select className={s.sortSelect} value={sort} onChange={(e) => setSort(e.target.value)}>
324
+ <select className={s.sortSelect} aria-label={t('inbox.sortLabel')} value={sort} onChange={(e) => setSort(e.target.value)}>
287
325
  <option value="-updatedAt">{t('inbox.sort.newest')}</option>
288
326
  <option value="updatedAt">{t('inbox.sort.oldest')}</option>
289
327
  <option value="-createdAt">{t('inbox.sort.created')}</option>
@@ -292,7 +330,8 @@ export const TicketInboxClient: React.FC = () => {
292
330
  )}
293
331
  </div>
294
332
 
295
- {/* List */}
333
+ {/* List — the panel the tablist above controls */}
334
+ <div role="tabpanel" id={INBOX_TABPANEL_ID} aria-labelledby={`inbox-tab-${tab}`}>
296
335
  {loading ? (
297
336
  <div className={s.loading}>{t('common.loading')}</div>
298
337
  ) : tickets.length === 0 ? (
@@ -361,6 +400,7 @@ export const TicketInboxClient: React.FC = () => {
361
400
  })}
362
401
  </div>
363
402
  )}
403
+ </div>
364
404
 
365
405
  {/* Keyboard hints */}
366
406
  <div className={s.keyboardHints}>
@@ -70,7 +70,7 @@
70
70
 
71
71
  .sectionDescription {
72
72
  font-size: 0.8125rem;
73
- color: var(--theme-elevation-500);
73
+ color: var(--theme-elevation-650);
74
74
  margin: 0 0 1rem;
75
75
  line-height: 1.5;
76
76
  }
@@ -143,7 +143,7 @@
143
143
 
144
144
  .featureDesc {
145
145
  font-size: 0.75rem;
146
- color: var(--theme-elevation-500);
146
+ color: var(--theme-elevation-650);
147
147
  margin-top: 0.125rem;
148
148
  }
149
149
 
@@ -166,7 +166,7 @@
166
166
 
167
167
  .fieldDescription {
168
168
  font-size: 0.75rem;
169
- color: var(--theme-elevation-500);
169
+ color: var(--theme-elevation-650);
170
170
  font-weight: 400;
171
171
  margin-top: 0.125rem;
172
172
  }
@@ -194,7 +194,7 @@
194
194
  .inputReadonly {
195
195
  composes: input;
196
196
  background: var(--theme-elevation-50);
197
- color: var(--theme-elevation-500);
197
+ color: var(--theme-elevation-650);
198
198
  cursor: not-allowed;
199
199
  }
200
200
 
@@ -230,7 +230,7 @@
230
230
 
231
231
  .inlineDesc {
232
232
  font-size: 0.75rem;
233
- color: var(--theme-elevation-500);
233
+ color: var(--theme-elevation-650);
234
234
  margin-top: 0.125rem;
235
235
  }
236
236
 
@@ -244,7 +244,7 @@
244
244
 
245
245
  .slaHint {
246
246
  font-size: 0.75rem;
247
- color: var(--theme-elevation-500);
247
+ color: var(--theme-elevation-650);
248
248
  }
249
249
 
250
250
  // ── API Key ───────────────────────────────────
@@ -290,7 +290,7 @@
290
290
  .aiToggleDesc {
291
291
  display: block;
292
292
  font-size: 0.6875rem;
293
- color: var(--theme-elevation-500);
293
+ color: var(--theme-elevation-650);
294
294
  }
295
295
 
296
296
  // ── Signature ─────────────────────────────────